背景
最近几天准备学习thrift,百度百科解释:
Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个远程过程调用(RPC)框架来使用,是由Facebook为“大规模跨语言服务开发”而开发的。
RPC又是啥?,忘光了,先从学习RPC开始
RPC概念
RPC,远程过程调用是一种技术思想而非一种规范或协议,常见的RPC框架和技术有:
- 应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
- 远程通信协议:RMI、Socket、SOAP(HTTP XML)、- REST(HTTP JSON)。
- 通信框架:MINA 和 Netty。
目前流行的开源 RPC 框架还是比较多的,有阿里巴巴的 Dubbo、Facebook 的 Thrift、Google 的 gRPC、Twitter 的 Finagle 等。
其中,Thrift是 Facebook 的开源 RPC 框架,主要是一个跨语言的服务开发框架。
用户只要在其之上进行二次开发就行,应用对于底层的 RPC 通讯等都是透明的。不过这个对于用户来说需要学习特定领域语言这个特性,还是有一定成本的。
RPC实现
如果系统是单体应用,因为咋同一个进程,A想要调用B接口直接调用B()就可以了
如果采用分布式应用,就不能这样直接调用了
服务A和服务B并没有在同一个进程,必须采用网络调用
REST接口调用
模仿B/S架构,B应用暴露接口给A应用,A、B之间通过HTTPS来进行调用,但是A如果想使用HTTPS,在使用前就必须要编写HTTPS连接的代码,会造成很多重复代码,可以使用AOP切面编程,在业务逻辑开始和结束后,连接和释放连接。
RPC调用
如果采用HTTPS的方式,每次调用都要新建连接和释放连接。我们可以使用Socket。都可以,RPC并没有规定说你要用何种协议进行通讯。
- Service A的应用层代码中,调用了Calculator的一个实现类的add方法,希望执行一个加法运算;
- 这个Calculator实现类,内部并不是直接实现计算器的加减乘除逻辑,而是通过远程调用Service B的RPC接口,来获取运算结果,因此称之为Stub;
- Stub怎么和Service B建立远程通讯呢?这时候就要用到远程通讯工具了,也就是图中的Run-time Library,这个工具将帮你实现远程通讯的功能,比如Java的Socket,就是这样一个库,当然,你也可以用基于Http协议的HttpClient,或者其他通讯工具类,都可以,RPC并没有规定说你要用何种协议进行通讯;
- Stub通过调用通讯工具提供的方法,和Service B建立起了通讯,然后将请求数据发给Service B。需要注意的是,由于底层的网络通讯是基于二进制格式的,因此这里Stub传给通讯工具类的数据也必须是二进制,比如calculator.add(1,2),你必须把参数值1和2放到一个Request对象里头(这个Request对象当然不只这些信息,还包括要调用哪个服务的哪个RPC接口等其他信息),然后序列化为二进制,再传给通讯工具类,这一点也将在下面的代码实现中体现;
- 二进制的数据传到Service B这一边了,Service B当然也有自己的通讯工具,通过这个通讯工具接收二进制的请求;
- 既然数据是二进制的,那么自然要进行反序列化了,将二进制的数据反序列化为请求对象,然后将这个请求对象交给Service B的Stub处理;
- 和之前的Service A的Stub一样,这里的Stub也同样是个“假玩意”,它所负责的,只是去解析请求对象,知道调用方要调的是哪个RPC接口,传进来的参数又是什么,然后再把这些参数传给对应的RPC接口,也就是Calculator的实际实现类去执行。很明显,如果是Java,那这里肯定用到了反射。
- RPC接口执行完毕,返回执行结果,现在轮到Service B要把数据发给Service A了,怎么发?一样的道理,一样的流程,只是现在Service B变成了Client,Service A变成了Server而已:Service B反序列化执行结果->传输给Service A->Service A反序列化执行结果 -> 将结果返回给Application,完毕。
RPC需要考虑的问题
要实现一个RPC不算难,难的是实现一个高性能高可靠的RPC框架。
比如,既然是分布式了,那么一个服务可能有多个实例,你在调用时,要如何获取这些实例的地址呢?
这时候就需要一个服务注册中心,比如在Dubbo里头,就可以使用Zookeeper作为注册中心,在调用时,从Zookeeper获取服务的实例列表,再从中选择一个进行调用。
那么选哪个调用好呢?这时候就需要负载均衡了,于是你又得考虑如何实现复杂均衡,比如Dubbo就提供了好几种负载均衡策略。
这还没完,总不能每次调用时都去注册中心查询实例列表吧,这样效率多低呀,于是又有了缓存,有了缓存,就要考虑缓存的更新问题,blablabla......
你以为就这样结束了,没呢,还有这些:
客户端总不能每次调用完都干等着服务端返回数据吧,于是就要支持异步调用;
服务端的接口修改了,老的接口还有人在用,怎么办?总不能让他们都改了吧?这就需要版本控制了;
服务端总不能每次接到请求都马上启动一个线程去处理吧?于是就需要线程池;
服务端关闭时,还没处理完的请求怎么办?是直接结束呢,还是等全部请求处理完再关闭呢?
......
如此种种,都是一个优秀的RPC框架需要考虑的问题。
可以直接使用现有的RPC框架,以Thrift为例。
Thrift 概述
Thrift最初由Facebook开发的,后来提交给了Apache基金会将Thrift作为一个开源项目。当时facebook开发使用它是为了解决系统中各系统间大数据量的传输通信以及系统之间语言环境不同需要跨平台的特性,所以Thrift是支持跨语言,比如C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml都支持。Thrift是一个典型的CS结构,客户端和服务端可以使用不同的语言开发。既然客户端和服务端能使用不同的语言开发,那么一定就要有一种中间语言来关联客户端和服务端的语言,没错,这种语言就是IDL(Interface Description Language)。
thrift-0.9.3.exe https://www.cnblogs.com/newboys/p/9366762.html
新建一个Thrift文件夹,将下载的thrift-0.9.1.exe重新命名为thrift.exe后放到Thrift文件夹下
配置环境变量,通过cmd thrift -version检查是否配置成功
Thrift DEMO
1、编写Apple.thrift(thrift idl的用法可转https://blog.csdn.net/u011642663/article/details/56015576
namespace java service.demo
service Apple{
string appleString(1:string para);
i32 appleAdd(1:i32 para);
i32 appleMult(1:i32 para1,2:i32 para2);
}
2、使用thrift命令将idl转为java
thrift -r -gen java Apple.thrift
3、将生成的Apple.java复制到项目中,如果有@Override异常,去掉即可。
4、编写实现类
public class AppleServiceImpl implements Apple.Iface {
public String appleString(String para) throws TException {
return "apple print hello " + para;
}
public int appleAdd(int para) throws TException {
return para+10;
}
public int appleMult(int para1, int para2) throws TException {
return para1 - para2;
}
}
5、编写Server
public class AppleServiceServer {
public static void main(String[] args) throws TTransportException {
System.out.println("apple 服务端开启。。");
TProcessor tprocessor = new Apple.Processor<Apple.Iface>(new AppleServiceImpl());
TServerSocket serverTransport = new TServerSocket(9000);
TServer.Args tArgs = new TServer.Args(serverTransport);
tArgs.processor(tprocessor);
tArgs.protocolFactory(new TBinaryProtocol.Factory());
TServer server = new TSimpleServer(tArgs);
server.serve();
}
}
6、编写Client端
public class AppleServiceClient {
public static void main(String[] args) {
System.out.println("客户端开始。。");
TTransport transport = null;
try{
transport = new TSocket("localhost",9000,3000);
TProtocol protocol = new TBinaryProtocol(transport);
Apple.Client client = new Apple.Client(protocol);
transport.open();
String result = client.appleString("abc");
System.out.println("服务端返回 。 " + result);
int a = client.appleAdd(8);
int b = client.appleMult(29, 3);
System.out.println("a= " + a + " b="+b);
}catch(TTransportException e){
e.printStackTrace();
}catch(TException e){
e.printStackTrace();
}finally{
if(null!=transport){
transport.close();
}
}
}
}
7、运行Server和Client