0 前言
Thrift 是一个软件框架(远程过程调用框架),用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引 擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。 thrift最初由facebook开发,07年四月开放源码,08年5月进入apache孵化器,现在是 Apache 基金会的顶级项目 thrift允许你定义一个简单的定义文件中的数据类型和服务接口,以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。。 著名的 Key-Value 存储服务器 Cassandra 就是使用 Thrift 作为其客户端API的。 |
1 编译环境
参考 http://thrift.apache.org/docs/install/centos
一步一步来就行了。
看到如下的提示就OK了。
2 脚本文件
2.1 编写
文件清单: Hello.thrift
namespace java service.demo service Hello{ string helloString(1:string para) i32 helloInt(1:i32 para) bool helloBoolean(1:bool para) void helloVoid() string helloNull() } |
说明:
定义了服务Hello的5个方法。
方法格式: 返回类型 方法名 参数列表
参数格式: 参数序号 参数类型 参数名
2.2 编译
使用Thrift工具编译Hello.thrift,就可以生成对应的Hello.java文件,该文件包含了在 Hello.thrift 文件中描述的服务 Hello 的接口定义,即 Hello.Iface 接口,以及服务调用的底层通信细节,包括客户端的调用逻辑 Hello.Client 以及服务器端的处理逻辑 Hello.Processor,用于构建客户端和服务器端的功能。
具体编译过程:
可以看到,编译成功,生成了一个文件夹gen-java.
我们来看看这个文件夹下面有什么。
可以看到产生了一个文件
3 Thrift例子
3.1 新建工程
新建一个java project.
然后按照http://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/
的做法,在工程里引入HelloServiceImpl.java,HelloServiceServer.java,HelloServiceClient.java。
不要忘了要引入根据脚本生成的那个Hello.java.
这样,工程里就有4个文件。
3.2 引入thrift源码
此时,eclipse肯定报错,因为我们还没有引入Thrift的源码。
下载地址
http://www.apache.org/dyn/closer.cgi?path=/thrift/0.9.2/thrift-0.9.2.tar.gz
解压缩后,源码位置在
将文件夹复制到eclipse里即可。
3.3 依赖包
然后加入所依赖的包
3.4 兼容性
最后还有最后一个问题,就是HelloServiceServer报错。
提示
这个构造方法不存在,修改成下面的
TThreadPoolServer.Args tArgs = new TThreadPoolServer.Args(serverTransport); tArgs.protocolFactory(proFactory); tArgs.processor(processor); TServer server = new TThreadPoolServer(tArgs); |
3.5 运行
然后开始运行整个程序。
启动服务器:
再启动client
程序顺利执行,client的方法没有输出。
我们在client里添加代码如下
String str=client.helloString("hello,Thrift"); System.out.println("resp:---"+str); |
观察client输出
至此,Thrift顺利完成运行过程。
下面我们需要来通过研究这个例子的源码来研究Thrift的运行原理。
4 异步IO改造
为了研究Thrift源码,上面的例子需要修改,因为上面的例子没有用到很过高级功能,所以我们需要改造成高级功能,这里参考了文章 http://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/
HelloServiceAsyncServer.java修改后的源码如下所示:
package service.server;
import org.apache.thrift.TProcessor; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.server.THsHaServer; import org.apache.thrift.server.TServer; import org.apache.thrift.server.TThreadedSelectorServer; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TNonblockingServerSocket; import org.apache.thrift.transport.TNonblockingServerTransport; import org.apache.thrift.transport.TTransportException;
import service.demo.Hello; import service.demo.HelloServiceImpl;
public class HelloServiceAsyncServer {
public static void main(String[] args) { try {
// TServerSocket serverTransport = new TServerSocket(7911); TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(7911); TFramedTransport.Factory transportFactory=new TFramedTransport.Factory(); //TNonblockingTransport. // 设置协议工厂为 TBinaryProtocol.Factory TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory(); // 关联处理器与 Hello 服务的实现 TProcessor processor = new Hello.Processor(new HelloServiceImpl());
TThreadedSelectorServer.Args tArgs = new TThreadedSelectorServer.Args(serverTransport); tArgs.transportFactory(transportFactory); tArgs.protocolFactory(protocolFactory); tArgs.processor(processor); THsHaServer a;
// TServer server = new TThreadPoolServer(tArgs); TServer server = new TThreadedSelectorServer(tArgs); System.out.println("Start server on port 7911..."); server.serve(); } catch (TTransportException e) { e.printStackTrace(); } } } |
HelloServiceSyncClient.java修改后
package service.client;
import org.apache.thrift.protocol.TBinaryProtocol; 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 service.demo.Hello;
public class HelloServiceSyncClient {
public static void main(String[] args) throws Exception {
// 设置传输通道,对于非阻塞服务,需要使用TFramedTransport,它将数据分块发送 TTransport transport = new TFramedTransport(new TSocket("192.168.56.102", 7911)); transport.open(); // 使用二进制协议 TProtocol protocol = new TBinaryProtocol(transport); // 创建Client Hello.Client client = new Hello.Client(protocol); long start = System.currentTimeMillis(); for (int i = 0; i < 1; i++) { System.out.println("client.helloBoolean(false)---"+client.helloBoolean(false)); //System.out.println("client.helloInt(111)---"+client.helloInt(111)); //client.helloNull(); System.out.println("client.helloString(\"360buy\")---"+client.helloString("360buy")); client.helloVoid(); } System.out.println("耗时:" + (System.currentTimeMillis() - start)); // 关闭资源 transport.close(); } } |
Hello.java & HelloServiceImpl.java 内容保持不变。
读者可自行分析修改过的地方。
运行之,看看是否OK。
5 调试环境Linux
Java提供了强大的jdb工具来调试源码,可以执行程序,进入函数,以及打印当前值。
6 原理分析
6.1 线程模型
Thrift的线程模型是怎样的,下面开始讲解
先看代码段1
@Override
protected boolean startThreads() {
try {
for (int i = 0; i < args.selectorThreads; ++i) {
selectorThreads.add(new SelectorThread(args.acceptQueueSizePerThread));
}
acceptThread = new AcceptThread((TNonblockingServerTransport) serverTransport_,
createSelectorThreadLoadBalancer(selectorThreads));
for (SelectorThread thread : selectorThreads) {
thread.start();
}
acceptThread.start();
return true;
} catch (IOException e) {
LOGGER.error("Failed to start threads!", e);
return false;
}
}
可以看到这里启动了若干线程。
再看代码段2
/** * Create the server with the specified Args configuration */ public TThreadedSelectorServer(Args args) { super(args); args.validate(); invoker = args.executorService == null ? createDefaultExecutor(args) : args.executorService; this.args = args; } /** * Helper to create the invoker if one is not specified */ protected static ExecutorService createDefaultExecutor(Args options) { return (options.workerThreads > 0) ? Executors.newFixedThreadPool(options.workerThreads) : null; } |
所以,总的线程图如下图所示:
|
6 |
3 |
Socket |
6.2 处理模型
1)Accept线程通过accept获取一个client.
2)Accept线程通过一定的负载策略(比如轮询)发给某个Selector线程。
3)Selector线程读取新建的Client,注册它的read事件。
4)Selector线程轮询所有注册的Socket的读写事件,当读条件满足时,触发Client的读取事件,拿到一个完整的Frame,封装成Runnable对象,通过(4.1)抛给ExecutorService线程组。同时屏蔽此socket的read事件。
5)ExecutorService执行完毕后,保存执行结果在此Runnable对象里,然后返回给Selector线程。
6)Selector线程检查自己的某个写就绪队列,读取,然后注册对应SocketChannel的write事件。
7)当写条件满足时,会在4中触发写事件,然后将内容发给远程client,发送完毕后,会屏蔽socket的写事件,注册读事件。这样重新来一个新的循环。
7 客户端连接池
可以采用第三方的连接池组件.
http://www.cnblogs.com/lihaozy/archive/2013/04/22/3035113.html
http://www.cnblogs.com/51cto/archive/2010/08/18/thrift_connection_pool.html
8 集群
方案采用KeepAlive + HaProxy
9 序列化协议格式简介
10 关于Thrift的优缺点讨论
http://www.zhihu.com/question/20189791
参考文档
1 http://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/
2 http://dongxicheng.org/search-engine/thrift-guide/
3 http://m.blog.csdn.net/blog/tianwei7518/44115297 (线程模型选择)
4 http://blog.csdn.net/m13321169565/article/details/7836006
5 http://blog.csdn.net/azhao_dn/article/details/8898610 [到底选择哪种网络模型?看这篇文章就可以了,最终定论]
6 http://www.codelast.com/?p=4824