Thrift初学
参考文章:
https://blog.csdn.net/zkp_java/article/details/81879577
https://www.cnblogs.com/newboys/p/9366762.html
本篇文章思路如下:
(如果不明白什么是RPC的,先去大致看一下RPC是做什么的,流程图是什么亚子的https://blog.csdn.net/weixin_41485567/article/details/103179034)
先来说一下thrift是什么?用来做什么?它的优势是什么?
再来说一下它的架构(这里可以不必理解的很透彻,可以看完例子之后,再回想),
写一个thrift的例子(客户端、服务端均使用的Java,没体现出跨语言哈),
把这个例子与它的架构对应上,
思考它在RPC框架上,做了哪些封装(也就是说他帮我们做了哪些工作;我们需要做什么工作即可)。
附:
关于RPC的文章中,提到的RPC流程图为:
化简图:
什么是Thrift?
Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个远程过程调用(RPC)框架来使用,是由Facebook为“大规模跨语言服务开发”而开发的。--百度百科
Apache Thrift软件框架(用于可扩展的跨语言服务开发)将软件堆栈与代码生成引擎结合在一起,以构建可在C ++,Java,Python,PHP,Ruby,Erlang,Perl,Haskell,C#,可可,JavaScript,Node.js,Smalltalk,OCaml和Delphi等语言。--Apache Thrift官网
提取关键字:一个RPC框架,支持跨语言,二进制通讯协议
他是一个典型的C/S结构,通过IDL(Interface Description Language)来关联客户端和服务端
(IDL就是后面写的data.thrift文件的内容)
优势:在高并发、大数据量、多语言环境下有优势
e.g. 相比于HTTP这个文本传输协议,Thrift可用TCP进行二进制传输,节省了一部分开销
Thrift的架构
TTrasport层
Thrift的数据传输方式:
- TSocket:阻塞式Socket
- TFramedTransport:以frame为单位进行传输,非阻塞式服务中使用
- TFileTransport:以文件形式进行传输
TProtocol层
客户端和服务端之间传输数据的协议(传输数据的格式)
- TBinaryProtocol:二进制格式
- TCompactProtocol:压缩格式
- TJSONProtocol:JSON格式
- TSimpleJSONProtocol:提供只写的JSON协议
Server模型
- TSimpleServer:简单的单线程服务模型,常用于测试
- TThreadPoolServer:多线程服务模型,使用标准的阻塞式IO
- TNonBlockingServer:多线程服务模型,使用非阻塞式IO(需要使用TFramedTransport数据传输方式)
- THsHaServer:THsHa引入了线程池去处理,其模型读写任务放到线程池去处理,Half-sync/Half-async处理模式,Half-async是在处理IO事件上(accept/read/write io)异步处理,Half-sync用于handler(数据处理部分/服务实现部分)对rpc的同步处理
一个简单的例子
首先我们需要写一个IDL文件
先普及一下IDL~ https://blog.csdn.net/weixin_41485567/article/details/103165822
data.thrift文件
// data.thrift
namespace java thrift
typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String
// struct关键字用于定义结构体,相当于面向对象编程语言中的类
struct Person {
// 相当于定义类中的成员,并生成相应的get和set方法,optional表示username这个成员可以没有
1: optional String username,
2: optional int age,
3: optional boolean married
}
// 定义一个异常类型,用于接口中可能抛出的异常
exception DataException {
1: optional String message,
2: optional String callStack,
3: optional String date
}
// 定义服务接口
service PersonService {
// service中可以定义若干个服务,相当于Java Interface中定义的方法
Person getPersonByUsername(1: required String username) throws (1: DataException data),
void savePerson(1: required Person person)
}
在IDL文件所在的目录下执行thrift --gen data.thrift生成对应的java代码,并引入到Java工程当中
或者thrift -r --gen data.thrift
生成的文件名就是定义的元素名,这里有三个。
项目的结构图如下:
编写服务端代码
服务(data.thrift的service中定义的两个服务)的实现类
package com.thrift.practice.server;
import thrift.generated.DataException;
import thrift.generated.Person;
import thrift.generated.PersonService;
/**
* 我们在data.thrift里面定义了两个服务(接口)
* 现在要写这两个服务的实现类
*/
public class PersonServiceImpl implements PersonService.Iface {
@Override
public Person getPersonByUsername(String username) throws DataException {
System.out.println("Got Client Param: " + username);
return new Person().setUsername(username).setAge(20).setMarried(false);
}
@Override
public void savePerson(Person person) throws DataException {
System.out.println("Got Client Param:");
System.out.println(person.username);
System.out.println(person.age);
System.out.println(person.married);
}
}
实现一个能监听RPC的服务器
package com.thrift.practice.server;
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import thrift.generated.PersonService;
/**
* 实现一个服务器
*/
public class ThriftServer {
public static void main(String[] args) throws Exception {
// 定义服务器使用的socket类型
TNonblockingServerSocket tNonblockingServerSocket = new TNonblockingServerSocket(8899);
// 创建服务器参数
THsHaServer.Args arg = new THsHaServer.Args(tNonblockingServerSocket).minWorkerThreads(2).maxWorkerThreads(4);
// 配置数据传输的方式
arg.transportFactory(new TFramedTransport.Factory());
// 配置传输数据的格式
arg.protocolFactory(new TCompactProtocol.Factory());
// 请求处理器(就是你实现的那个接口啦~)
PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());
arg.processorFactory(new TProcessorFactory(processor)); // 配置处理器用来处理rpc请求
// 本示例中使用半同步半异步方式的服务器模型
TServer server = new THsHaServer(arg);
System.out.println("Thrift Server Started!");
// 启动服务
server.serve();
}
}
编写客户端
package com.thrift.practice.client;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import thrift.generated.Person;
import thrift.generated.PersonService;
public class ThriftClient {
public static void main(String[] args) throws TException {
TSocket tSocket = new TSocket("localhost", 8899);
tSocket.setTimeout(900);
TTransport tTransport = new TFramedTransport(tSocket);
TProtocol tProtocol = new TCompactProtocol(tTransport);
PersonService.Client client = new PersonService.Client(tProtocol);
tTransport.open();
// 这一句重点体现了RPC!!看似像一个本地调用,实际上这个方法负责发送给服务端,让服务端处理,并接收返回值
Person person = client.getPersonByUsername("zjy");
// 打印接收的结果
System.out.println(person.username);
System.out.println(person.age);
System.out.println(person.married);
System.out.println("-----------------------");
Person newPerson = new Person();
newPerson.username = "yjz";
newPerson.age = 30;
newPerson.married = true;
client.savePerson(newPerson);
tTransport.close();
}
}
将例子与架构对应上
服务器端
客户端
这里我都是按照由底层到高层顺序写的。(这不是必须的)
关于RPC
首先,RPC有如下两个特点:
- 远程调用服务
- 使用时,要像本地调用一样,让使用者感受不到远程调用的逻辑
显然,thrift框架满足第一个特点,那么他是如何满足第二个特点的呢?
服务端代码中
// 这一句重点体现了RPC!!看似像一个本地调用,实际上这个方法负责发送给服务端,让服务端处理,并接收返回值 Person
person = client.getPersonByUsername("zjy");
该方法调用了PersonService(thrift生成)中
public Person getPersonByUsername(String username) throws DataException, org.apache.thrift.TException
{
send_getPersonByUsername(username);
return recv_getPersonByUsername();
}
它负责发送数据,接收结果。
这就满足了RPC的第二个特点。
所以,换个说法就是,thrift框架帮助我们写了远程调用的逻辑,也就是我们只需要实现服务,并调用服务,可以忽略远程调用的逻辑。(当然客户端和服务端的配置还是要编写的)
下一篇: thrift-protocol