当前位置: 首页 > 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.dtcms.com/a/280496.html

相关文章:

  • 亚矩阵云手机:破解 Yandex 广告平台多账号风控难题的利器
  • Redis学习系列之——高并发应用的缓存问题(二)
  • JDK1.8函数式编程实战(附日常工作案例,仅此一篇耐心看完彻底搞懂)
  • 17、鸿蒙Harmony Next开发:状态管理(组件拥有的状态和应用拥有的状态)
  • Vue获取上传Excel文件内容并展示在表格中
  • 【人工智能99问】神经网络的工作原理是什么?(4/99)
  • 使用Pydantic开发时,如何将返回数据由snake_case自动转为camel case
  • Mac IDEA启动报错:Error occurred during initialization of VM
  • Linux操作系统从入门到实战(九)Linux开发工具(中)自动化构建-make/Makefile知识讲解
  • ubuntu部署kvm
  • AI-Compass LLM训练框架生态:整合ms-swift、Unsloth、Megatron-LM等核心框架,涵盖全参数/PEFT训练与分布式优化
  • 正则表达式深度解析:从LeetCode 3136题说起
  • 028_分布式部署架构
  • OpenCV图像自动缩放(Autoscaling)函数autoscaling()
  • 2025.7.15总结
  • 用Python构建机器学习模型预测股票趋势:从数据到部署的实战指南
  • 希尔排序:突破传统排序的边界
  • 【Java】【企业级应用】学生信息管理系统项目介绍
  • Mybatis05-动态sql
  • 深度解析 AI 提示词工程(Prompt Engineering)
  • 2025世界机器人大赛ICode专属训练平台图形化小学组答案
  • 光伏设计全方位指南
  • B/S 架构通信原理详解
  • sqli-labs靶场通关笔记:第17关 POST请求的密码重置
  • 如何配置maven
  • 【Linux手册】重定向是如何实现的?Linux下为什么一切皆文件?
  • flutter下的webview适配rem问题
  • 【NBA】75 Greatest NBA Players of All Time
  • 春秋云镜 initial
  • Uniapp动态切换主题与老年模式详解