[Thrift]学习使用Thrift
Thrift
在接触到Thrift之前我只接触过阿里的Dubbo,但是Dubbo不支持跨语言,所以我在找一门跨语言的RPC框架,最近接触到的有gRPC,Thrift,自己也用Netty实现了一下简单的调用,不过那充其量只是一个玩具。这次打算把之前自己项目里的HTTP调用换掉,换成Thrift。这里来记录一下Thrift的学习。
简介
Thrift是Facebook开发的,现在已经是APACHE的开源项目了,它是一种接口描述语言和二进制通讯协议,用来定义和创建跨语言的服务,也正因为跨语言这个特点,用它来做开发的时候,需要使用它的一个自带工具来生成自己需要的代码。这和以往的非跨语言的RPC框架还是有一点不同的。
Thrift是一套完整的栈,什么是完整的栈了,其实只要用过了就知道,它里面有TProtocol,TTransport这样的类,也就是可以设置传输协议而不需要重新进行编码。除此之外,在服务端,也做了阻塞,非阻塞以及多线程这样的机制。
- Thrift支持的通讯协议
- TBinaryProtocol 一种简单的二进制格式,之所以简单,是因为并没有对空间效率进行优化,但比文本处理起来更快,不易调试。
- TCompactProtocol 顾名思义,是一种带压缩的更紧凑的二进制格式,处理起来更高效。
- TDebugProtocol 一种人类可以理解的文本协议,可以用来协助调试。
- TDenseProtocol 和第二个相似,将传输数据的原信息剥离。
- TJSONProtocol 使用JSON格式。
- TSimpleJSONProtocol 一种只写协议,不能被Thrift解析,适合用脚本语言来解析。
- Thrift支持的传输协议
- TFileTransport 该协议会写文件。
- TFramedTransport 当使用一个非阻塞服务器的时候,需要使用这个协议,顾名思义按帧来发送数据。
- TMemoryTransport 使用阻塞的套接字来进行传输。
- TZlibTransport 用zlib进行压缩,用于连接另一个传输协议。
- Thrift支持的服务模型
- TNonblockingServer 一个多线程的服务器,顾名思义是非阻塞的,上面的TFRamedTransport必须和这个模型一块使用。
- TSimpleServer 一个单线程的服务器,是阻塞的。
- TThreadPoolServer 阻塞的多线程服务器
- THsHaServer 把读写任务放到线程池处理,半同步半异步的处理模式,半同步是在处理IO时间上,比如Socket的读写,连接,半异步是用于Handler对RPC的同步处理。
demo
定义接口和实体
需要用Thrift的IDL来定义一个简单的实体,代表一个学生,此次demo主要就是实现学生信息的查和保存,信息都存在HashMap里。
//指明代码生成之后所处的文件路径,相当于java里的package
namespace java cn.izzer.thriftdemo.common
//将shrift的数据类型格式转换为java习惯的格式
typedef i16 short
typedef i32 int
typedef i64 long
typedef string String
typedef bool boolean
//定义对象
struct Student {
1:optional String name,
2:optional int age,
3:optional String address
}
//定义异常
exception DataException {
//optional 可选 非必传
1:optional int code,
2:optional String message,
3:optional String dateTime
}
//定义后台业务接口
service StudentService {
//根据名称获取学生信息 返回一个学生对象 抛出DataException异常
//required 必传项
Student getStudentByName(1:required String name) throws (1:DataException dataException),
//保存一个学生信息 无返回 抛出DataException异常
void save(1:required Student student) throws (1:DataException dataException)
}
用Thrift工具生成代码
(https://mirror.bit.edu.cn/apache/thrift/0.13.0/thrift-0.13.0.exe
使用命令:
thrift-0.13.0.exe --gen java <path>/Student.thrift
在当前路径下就会产生thrift生成的代码。这个包作为公共模块即可。
服务端
实现上面的生成的StudentService.Iface的接口,初始化的方法里放了两个对象来做测试。
@Service
public class StudentServiceImpl implements cn.izzer.thriftdemo.common.StudentService.Iface {
private HashMap<String,Student> studentMap;
@PostConstruct
public void init(){
studentMap = new HashMap<>();
Student s1 = new Student();
s1.setName("thyin");
s1.setAddress("Shanghai");
s1.setAge(22);
Student s2 = new Student();
s2.setName("abc");
s2.setAddress("Shanghai");
s2.setAge(22);
studentMap.put(s1.getName(),s1);
studentMap.put(s2.getName(),s2);
}
@Override
public Student getStudentByName(String name) {
return studentMap.get(name);
}
@Override
public void save(Student student) throws DataException, TException {
studentMap.put(student.getName(),student);
}
}
然后就是Thrift的服务编写。
@Component
public class ThriftServer {
@Value("${thrift.port}")
private Integer port;
@Value("${thrift.min-thread-pool}")
private Integer minThreadPool;
@Value("${thrift.max-thread-pool}")
private Integer maxThreadPool;
@Autowired
private StudentServiceImpl studentService;
public void start(){
try {
TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(port);
THsHaServer.Args args = new THsHaServer.Args(serverSocket)
.minWorkerThreads(minThreadPool)
.maxWorkerThreads(maxThreadPool);
StudentService.Processor<StudentServiceImpl> processor = new StudentService.Processor<StudentServiceImpl>(studentService);
//二进制压缩协议
args.protocolFactory(new TCompactProtocol.Factory());
args.transportFactory(new TFramedTransport.Factory());
args.processorFactory(new TProcessorFactory(processor));
TServer server = new THsHaServer(args);
System.out.println("Thrift Server Started at port:"+port);
server.serve();
}catch (TTransportException ex){
ex.printStackTrace();
}
}
}
端口和IP设置还是不能忘。
thrift:
port: 7001
min-thread-pool: 100
max-thread-pool: 200
在Springboot启动类里启动Thrift服务。
SpringBootApplication
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
@Bean(initMethod = "start")
public ThriftServer init(){
ThriftServer server = new ThriftServer();
return server;
}
}
客户端
定义StudentService接口,然后实现。
public interface StudentService {
Student getStudentByName(String name);
void save(Student student);
}
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private ThriftClient client;
@Override
public Student getStudentByName(String name) {
try {
client.open();
Student student = client.getService().getStudentByName(name);
System.out.println("获取用户成功,用户信息为:"+student);
return student;
}catch (Exception e){
e.printStackTrace();
}finally {
client.close();
}
return null;
}
@Override
public void save(Student student) {
try {
client.open();
client.getService().save(student);
System.out.println("客户端保存用户信息成功,信息为:"+student);
}catch (Exception e){
e.printStackTrace();
}finally {
client.close();
}
}
}
定义客户端操作的API。
public class ThriftClient {
private Integer port;
private String host;
private TTransport transport;
private TProtocol protocol;
private StudentService.Client client;
private void initClient(){
transport = new TFastFramedTransport(new TSocket(host,port),1000);
protocol = new TCompactProtocol(transport);
client = new StudentService.Client(protocol);
}
public void setPort(Integer port) {
this.port = port;
}
public void setHost(String host) {
this.host = host;
}
public StudentService.Client getService(){
return client;
}
public void open() throws TTransportException{
if(null!=transport&&!transport.isOpen()){
transport.open();
}
}
public void close(){
if(null!=transport&&!transport.isOpen()){
transport.close();
}
}
}
然后用Spring来进行管理。
@Configuration
public class ThrifClientConfig {
@Value("${thrift.port}")
private Integer port;
@Value("${thrift.host}")
private String host;
@Bean(initMethod = "initClient")
public ThriftClient init(){
ThriftClient client = new ThriftClient();
client.setHost(host);
client.setPort(port);
return client;
}
}
thrift的端口和ip设置,这里我使用HTTP接口去调用客户端,所以多一个HTTP服务的端口
thrift:
port: 7001
host: localhost
server:
port: 8001
最后就是业务类的编写了,也没啥太复杂的。
@Controller
@RequestMapping("/rpc")
public class StudentController {
@Autowired
private StudentService studentService;
@RequestMapping("/get/{name}")
@ResponseBody
public Student getStudent(@PathVariable("name")String name){
return studentService.getStudentByName(name);
}
@RequestMapping("/save/{name}/{age}")
@ResponseBody
public String saveStudent(@PathVariable("name")String name,@PathVariable("age")Integer age){
Student student = new Student();
student.setName(name);
student.setAge(age);
studentService.save(student);
return "Success";
}
}
测试
服务端客户端都启动之后,就可以通过POSTMAN或者浏览器来进行调用了。
先调用获取信息接口。
可以看到请求正常执行。那再来试试保存。
再来获取被保存的人的信息。