当前位置: 首页 > news >正文

【橘子分布式】Thrift RPC(编程篇)

一、简介

之前我们研究了一下thrift的一些知识,我们知道他是一个rpc框架,他作为rpc自然是提供了客户端到服务端的访问以及两端数据传输的消息序列化,消息的协议解析和传输,所以我们今天就来了解一下他是如何实现这些功能,并且如何在实际代码中使用。
我们需要搭建环境。

1. 安装Thrift 作用:把IDL语言描述的接口内容,生成对应编程语言的代码,简化开发。我们已经介绍了在mac如何使用brew安装了。
2. 引入依赖    作用:引入thrift针对于某一种编程语言的封装 (网络通信 协议【序列化】)

二、结构和模块

1、Thrift核心对象

1. TTransport作用:底层封装了rpc的网络通信能力,具体实现有下面三种TSocket 阻塞IO通信TNonblockdingTransport 非阻塞网络通信TFramedTransport 加入了封帧的操作 (压缩后 数据边界问题)  
2. TProtocol作用:底层封装了rpc的关于协议的处理 (序列化方式)TBinayProtocol 二进制进行序列化TCompactProtocol 压缩方式 处理二进制 TJSONProtocol  JSON进行序列化这里需要说明一下,当我们的序列化方式走了TCompactProtocol压缩处理的时候,你最好使用TFramedTransport来处理网络通信,因为一旦数据开启了压缩,那就可能传输很大量的数据,数据的模样和原始的不同,此时可能因为缓冲区大小等等出现半包粘包问题。需要解决封帧。其他的方式一般没啥,因为rpc数据量不会很大,一般没有半包粘包,具体问题具体分析吧。而且你能看到他提供多种协议,并不是用的http协议这个最常见的,这个没啥说的,高性能的网络协议,一般都不用http,http的优势是简单通用。
3. TProcessor作用:进行业务处理,把通信数据 和 业务功能整合在一起。换句话说我们的业务代码就在这里,其实就是把数据拿过来了,开始业务处理了。
4. TServer 服务端服务端就是用上面的三个组件,组合起来实现具体的服务端逻辑,根据不同的实现有异步的有同步的等等,并且发布服务出去。

2、项目结构

我们在编码之前需要明确一点,就是作为rpc(Remote Procedure Call),远程调用必然存在一个服务的提供者和一个服务的调用者。所以我们必须有的两个模块是rpc-server和rpc-client。
在实际开发的时候,我们比如说服务调用者和提供者可能有相同的数据比如User这个类,那你不能在两边各自都写一个吧,肯定是写一份维护就好了。所以我们还需要一个rpc-common的模块,用来声明那些公用的类,包括一些公用的实体类,还有发起rpc调用的service代理类等等。
于是我们就抽象出了三个模块。

1. thrift-client  代表的是服务的调用者,客户端要想本地方法那样调用远端方法,底层必然是代理。
2. thrift-server  代表的是服务的提供者2.1. 实现服务接口 :idl语言生成的2.2. 创建服务端代码
3. thrift-common  RPC编程共有的内容 1,实体类型 2,服务接口3.1. 通过IDL语言 定义 client与服务端 共用的数据类型 和 服务接口 3.2. client server端需要引入 common模块

我们这里是以同一个工程下的模块化的形式搭建项目,实际上你也可以单独创建不同的工程。

3、引入依赖

我们这里是一个项目多个模块,所以我直接在父级项目引入了。

<modules><module>thrift-client</module><module>thrift-server</module><module>thrift-common</module>
</modules><properties><thrift.version>0.22.0</thrift.version><javax.annotation.version>1.3.2</javax.annotation.version><slf4j-api.version>1.7.32</slf4j-api.version><logback-classic.version>1.2.9</logback-classic.version><junit.version>3.8.1</junit.version><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties><dependencies><!-- 引入thrift针对于某一种编程语言的封装,java就是maven管理jar,你要是用py开发,那就是pip (网络通信 协议【序列化】)--><dependency><groupId>org.apache.thrift</groupId><artifactId>libthrift</artifactId><version>${thrift.version}</version></dependency><dependency><groupId>javax.annotation</groupId><artifactId>javax.annotation-api</artifactId><version>${javax.annotation.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j-api.version}</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>${logback-classic.version}</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>${junit.version}</version><scope>test</scope></dependency></dependencies>

4、构建thrift文件

此时我们需要构建一些用来编程的代码。首先我们的实体类和service要放在common里面,然后client和server引用common即可。我们实现一个保存用户User和根据用户名查看用户的实现。
因为我们是java开发,所以我们其实目标就是一个User类,和一个service类,service里面提供void save(User user)和User getUserByName(String name)这样的东西。所以我们就基于这个构建IDL,然后用thrift翻译出来java代码即可。根据我们昨天对于IDL的语法梳理,可以这么编写.thrift文件。
名称为rpc-thrift.thrift

# 用来构建rpc的thrift文件namespace java com.levi
struct User {1: required string name,2: required string password
}
service UserService {/*** 注册用户* @param user 用户信息* @return 注册结果*/void registerUser(1: User user)/*** 获取用户信息* @param name 用户名称* @return 用户信息*/User getUser(1: string name)
}

然后我们进入到这个文件的目录,使用如下命令:

thrift --gen java rpc-thrift.thrift

生成如下结果:
在这里插入图片描述
我们把这些转移到类目录下即可,然后删除这些即可。
在这里插入图片描述
然后我们maven install,再把common这个依赖给client和server模块引入即可。

<dependencies><dependency><groupId>com.levi</groupId><artifactId>thrift-common</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>

此时我们就完成了对应的项目结构搭建,下面我们来实现业务。

三、业务实现

1、服务端实现

package com.levi.service;import com.levi.User;
import com.levi.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** 注意这里实现的接口不是UserService,而是UserService.Iface,这是thrift生成的接口* 按照thrift的规范,每个接口都有一个对应的Iface接口,用于封装接口的方法,方便调用*/
public class UserServiceImpl implements UserService.Iface{Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);@Overridepublic void registerUser(User user) {logger.info("register user:{}",user);}@Overridepublic User getUser(String name) {logger.info("get user:{}",name);return new User(name,"123456");}
}

有了业务类之后,就启动服务端。

package com.levi;import com.levi.service.UserServiceImpl;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;public class TestServer {public static void main(String[] args) {// 创建服务端,监听端口8888,其实就是对应的TTransport组件,通信功能在此封装try (TServerSocket tServerSocket = new TServerSocket(8888);){// 协议工厂,用于封装协议,比如二进制协议、JSON协议等,此处使用二进制协议,其实对应的就是TProtocol组件,协议功能在此封装TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();// 处理器工厂,用于封装处理器,比如UserService.Processor,其实对应的就是TProcessor组件,处理器功能在此封装。绑定我们的业务逻辑。UserService.Processor processor = new UserService.Processor(new UserServiceImpl());// 封装参数TSimpleServer.Args arg = new TSimpleServer.Args(tServerSocket).protocolFactory(protocolFactory).processor(processor);// 构建服务端起动器,其实对应的就是TServer组件,服务端功能在此封装,发布服务出去,等待客户端连接TSimpleServer tSimpleServer = new TSimpleServer(arg);tSimpleServer.serve();}catch (Exception e){e.printStackTrace();}}
}

2、客户端实现

package com.levi;import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class TestClient {private static Logger logger = LoggerFactory.getLogger(TestClient.class);public static void main(String[] args) {// 创建服务端,监听端口8888,其实就是对应的TTransport组件,通信功能在此封装try (TTransport tTransport = new TSocket("localhost", 8888);){// 协议工厂,用于封装协议,比如二进制协议、JSON协议等,此处使用二进制协议,其实对应的就是TProtocol组件,协议功能在此封装,和服务端保持一致TProtocol tProtocol = new TBinaryProtocol(tTransport);// rpc的本质,就是像调用本地服务一样调用远程服务,此处就是一个体现,但是本质都是代理模式,只不过这个代理thrift封装了UserService.Client userService = new UserService.Client(tProtocol);tTransport.open();User user = userService.getUser("levi");logger.info("client get user is {}",user);}catch (Exception e){e.printStackTrace();}}
}

启动服务端之后开始监听,然后启动客户端。输出如下:

client get user is User(name:levi, password:123456)

没毛病。

四、重构

实际上开发的时候,我们一般是有些业务公用的,可能RPC调用在用其他的业务,所以最好是封装隔离开,我们一般是在RPC这里直接调用,实在没得用再写。
于是我们把thrift的这个实现改为UserServiceImplRPC

package com.levi.service;import com.levi.User;
import com.levi.UserService;/*** 注意这里实现的接口不是UserService,而是UserService.Iface,这是thrift生成的接口* 按照thrift的规范,每个接口都有一个对应的Iface接口,用于封装接口的方法,方便调用*/
public class UserServiceImplRPC implements UserService.Iface{@Overridepublic void registerUser(User user) {UserServiceImpl userService = new UserServiceImpl();userService.registerUser(user);}@Overridepublic User getUser(String name) {UserServiceImpl userService = new UserServiceImpl();return userService.getUser(name);}
}

常规的业务放在UserServiceImpl

package com.levi.service;import com.levi.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** 注意这里实现的接口不是UserService,而是UserService.Iface,这是thrift生成的接口* 按照thrift的规范,每个接口都有一个对应的Iface接口,用于封装接口的方法,方便调用*/
public class UserServiceImpl {Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);public void registerUser(User user) {logger.info("register user:{}",user);}public User getUser(String name) {logger.info("get user:{}",name);return new User(name,"123456");}
}

这样方便维护也比较清晰。

http://www.lryc.cn/news/588603.html

相关文章:

  • Vim多列操作指南
  • 028_分布式部署架构
  • 淘宝扭蛋机小程序开发:重构电商娱乐化体验的新范式
  • GaussDB 数据库架构师修炼(四) 备份容量估算
  • 【轨物洞见】光伏运维的“无人区”突围战,数据智能是唯一航标
  • Python Docker SDK库详解:从入门到实战
  • docker 方式gost代理搭建以及代理链实施
  • Linux VFS 抽象层全解析:统一接口的力量
  • JAVA学习笔记 使用notepad++开发JAVA-003
  • 微信小程序进度条cavans
  • 虚拟主机CPU占用100导致打不开的一次处理
  • [数据结构]#3 循环链表/双向链表
  • 微信小程序未登录状态下的导航拦截有哪些方法可以实现
  • 暑假Python基础整理 --异常处理及程序调试
  • python原生处理properties文件
  • 电动汽车制动系统及其工作原理
  • slam中的eskf观测矩阵推导
  • LangChain智能体开发实战:从零构建企业级AI助手
  • C++ Boost Aiso TCP 网络聊天(服务端客户端一体化)
  • CMake基础:覆盖项目开发的五大配套工具
  • 【机器学习深度学习】大模型推理速度与私有化部署的价值分析
  • ELK部署与使用详解
  • Docker部署语音转文字(STT)服务并接入Home Assistant
  • Dubbo高阶难题:异步转同步调用链上全局透传参数的丢失问题
  • 设备发出、接收数据帧的工作机制
  • HarmonyOS从入门到精通:动画设计与实现之九 - 实用动画案例详解(上)
  • HarmonyOS从入门到精通:动画设计与实现之九 - 实用动画案例详解(下)
  • 暑假Python基础整理 -- 文件及目录操作
  • keepalive模拟操作部署
  • 2025-7-14-C++ 学习 排序(2)