新接触的项目这边使用了Thrift,于我而言是一个完全陌生的东西,因此在此记录一下自己的学习过程和一点体会。
1.简介
远程调用接口我们当然可以使用HTTP协议,但伴随着服务越来越多,业务越来越复杂,效率就变成了一个很大的问题,于是基于网络传输层的RPC应运而生。Thrift就是一个跨语言的RPC框架,支持很多常用的语言,大大方便了我们的开发。
2.环境介绍
- 操作系统:macOS
- Thrift version:0.11.0
- IDE:IntelliJ IDEA
- JDK:1.8
3.安装Thrift
mac推荐使用brew来安装,命令如下
brew install thrift
如果还没有安装brew工具,可以使用如下命令安装
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
安装完成之后我们执行thrift -version
,看到输出Thrift version 0.11.0
就OK了。
4.编写thrift文件并生成配置
我们使用Thrift IDL(interface definition language)来编写thrift文件。具体的语法和规则可以参见官网的介绍TYPES和IDL,在这里我们举一个简单的例子,新建user.thrift文件,内容如下
namespace java com.atticus //声明命名空间
enum Gender{ //枚举
MALE = 1,
FEMALE = 2
}
struct User{ //定义一个结构
1: required i32 id,
2: required string name,
3: required Gender gender,
4: optional bool married
}
service UserService{ //定义service
User getUser(1:i32 id);
}
然后执行thrift --gen java user.thrift
命令,可以在当前目录下看到一个名为gen-java
的目录,这就是生成的文件了。
5.创建工程
我们在IDEA中创建一个maven工程,在pom.xml文件中加入对Thrift的依赖
<!-- 注意和自己brew安装的thrift版本一致 -->
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.11.0</version>
</dependency>
随后我们把上面生成的gen-java目录下的所有文件都拷贝到我们工程下的src/main/java目录下,整个项目结构如下
6.服务端实现
首先我们来建一个UserServiceImpl
类作为服务端的具体实现类
package com.atticus;
import org.apache.thrift.TException;
public class UserServiceImpl implements UserService.Iface{
@Override
public User getUser(int id) throws TException {
System.out.println("request come in... " + id);
User user = new User();
user.setId(id);
user.setName("atticus");
user.setGender(Gender.MALE);
user.setMarried(false);
return user;
}
}
接下来创建启动类Server
类
package com.atticus;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
public class Server {
public static final int PORT = 8098;
public void start(){
try {
TServerSocket serverSocket = new TServerSocket(PORT);
UserService.Processor processor = new UserService.Processor(new UserServiceImpl());
//
TThreadPoolServer.Args args = new TThreadPoolServer.Args(serverSocket);
args.processor(processor);//具体的处理实现
args.protocolFactory(new TBinaryProtocol.Factory());//二进制
new TThreadPoolServer(args).serve();
} catch (TTransportException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Server().start();
}
}
7.客户端实现
客户端的实现比较简单,我们创建一个Client
类
package com.atticus;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
public class Client {
public static final String HOST = "localhost";
public static final int PORT = 8098;
public void start(){
try {
TTransport tTransport = new TSocket(HOST, PORT);
UserService.Client client = new UserService.Client(new TBinaryProtocol(tTransport));
tTransport.open();
User user = client.getUser(1);
System.out.println(user.toString());
tTransport.close();
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Client().start();
}
}
8.验证
首先启动服务端,再启动客户端,可以看到服务端输出如下
客户端输出如下
证明我们的服务是正常的。
9.简单分析
接下来我们简单分析一下其中的原理,首先来看客户端,UserService.Client
类实现了我们定义的public User getUser(int id){}
方法,具体实现可以追踪到他的父类TServiceClient
中的sendBase和receiveBase
方法
//发送请求
private void sendBase(String methodName, TBase<?,?> args, byte type) throws TException {
//写入方法名(例如getUser),类型(TMessageType.CALL)和一个序列id
oprot_.writeMessageBegin(new TMessage(methodName, type, ++seqid_));
//序列化
args.write(oprot_);
oprot_.writeMessageEnd();
oprot_.getTransport().flush();
}
//接收
protected void receiveBase(TBase<?,?> result, String methodName) throws TException {
TMessage msg = iprot_.readMessageBegin();
if (msg.type == TMessageType.EXCEPTION) {//处理异常
TApplicationException x = new TApplicationException();
x.read(iprot_);
iprot_.readMessageEnd();
throw x;
}
if (msg.seqid != seqid_) {//验证序列号
throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID,
String.format("%s failed: out of sequence response: expected %d but got %d", methodName, seqid_, msg.seqid));
}
result.read(iprot_);//反序列化
iprot_.readMessageEnd();
}
可以看到在方法调用时用一个seqid来保证请求和响应能够对应上。
再看服务端的UserService.Processor
类,我们可以发现这样一行
processMap.put("getUser", new getUser());
也就是说getUser方法在这里被当做一个类处理,
public static class getUser<I extends Iface> extends org.apache.thrift.ProcessFunction<I, getUser_args> {
public getUser() {
super("getUser");
}
public getUser_args getEmptyArgsInstance() {
return new getUser_args();
}
protected boolean isOneway() {
return false;
}
@Override
protected boolean handleRuntimeExceptions() {
return false;
}
//真正的处理方法
public getUser_result getResult(I iface, getUser_args args) throws org.apache.thrift.TException {
getUser_result result = new getUser_result();
result.success = iface.getUser(args.id);//直接调用
return result;
}
}
同时有一个map在processor初始化的时候存储方法名和类,在请求到达的时候可以从map中取出相应的类,调用getResult
方法。
public boolean process(TProtocol in, TProtocol out) throws TException {
TMessage msg = in.readMessageBegin();
ProcessFunction fn = processMap.get(msg.name);//取出方法名对应的类
if (fn == null) {
...
}
fn.process(msg.seqid, in, out, iface);//最终会执行getResult
return true;
}
10.总结
有时间再试试不同语言直接的通信吧。