1 简介
Thrift是一个跨语言的服务部署框架,最初由facebook开发用做系统内各语言之间的RPC(Remote Procedure Call Protocol,远程过程调用协议通信),2007年由facebook贡献到apache基金,08年5月进入apache孵化器。Thrift通过一个中间语言IDL(interface description language, 接口定义语言)来定义RPC的接口和数据类型,然后通过一个编译器生成不同语言的代码,并由生成的代码负责RPC协议层和传输层的实现。支持的语言有C++, Java, Python, PHP,Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml,Delphi等。
2 原理
2.1 架构
Thrift通过代码生成工具将接口定义文件生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在Thirft描述文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后用户实现服务(客户端调用服务,服务器端提服务)便可以了。其中protocol(协议层, 定义数据传输格式,可以为二进制或者XML等)和transport(传输层,定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等)被用作运行时库。
2.2 基本概念
- Thrift中的几个概念
Server 服务模型
Handler 数据处理接口
Processor 数据处理对象
Protocol 数据传输协议
Transport 数据传输方式
- 支持的服务模型[Server]
TSimpleServer – 简单的单线程服务模型,常用于测试
TThreadPoolServer – 多线程服务模型,使用标准的阻塞式IO。
TNonblockingServer – 多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式)
- 支持的传输格式[Protocol]
TBinaryProtocol – 二进制格式
TCompactProtocol – 压缩格式
TJSONProtocol – JSON格式
TSimpleJSONProtocol –提供JSON只写协议, 生成的文件很容易通过脚本语言解析。
TDebugProtocol – 使用易懂的可读的文本格式,以便于debug
- 支持的通信方式(数据传输方式)[Transport]
TFileTransport:文件(日志)传输类,允许client将文件传给server,允许server将收到的数据写到文件中。
THttpTransport:采用Http传输协议进行数据传输
TSocket:采用TCPSocket进行数据传输
TZlibTransport:压缩后对数据进行传输,或者将收到的数据解压
3 简单示例
3.1 下载安装
登录官网下载(http://thrift.apache.org/),windows下载thrift-0.9.3.exe
放到某一路径下,例如:D:\ProgramFiles\thirft\thrift.exe(为了方便执行命令,将文件名改为thrift.exe)
环境变量Path值追加"D:\ProgramFiles\thirft"
打开cmd.exe,输入thrift –help,并回车可以看到帮助信息说明已安装成功
3.2 编写thrift文件,并编译
创建hello.thrift文件:
namespace java com.tongyin.thrift.demo
service HelloWorldService { string sayHello(1:string username) } |
将文件放到某路径下,例:D:\dev\hello.thrift
打开cmd.exe,并执行thrift -r --gen java hello.thrift
在D:\dev\gen-java目录下编译生成了java文件,文件所在的包com.tongyin.thrift.demo是hello.thrift文件定义的namespace,
再将生成的文件拷贝到项目中
3.3 服务端代码
3.3.1 HelloWorldService编译生成的thrift服务类
拷贝编译生成的HelloWorldService.java到服务端项目
3.3.2 HelloWorldImpl服务实现类
package com.tongyin.thrift.demo;
import org.apache.thrift.TException;
/** * 服务接口实现类 * @authorliulin * */ public class HelloWorldImpl implements HelloWorldService.Iface {
@Override public String sayHello(Stringusername)throws TException { return"Hi, " +username ; }
} |
3.3.3 HelloServerDemo服务入口类
package com.tongyin.thrift.demo;
import org.apache.thrift.TProcessor; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.server.TServer; import org.apache.thrift.server.TThreadPoolServer; import org.apache.thrift.transport.TServerSocket;
/** * thrift服务端 * @authorliulin * */ public class HelloServerDemo { //thrift服务端口号 public static final int SERVER_PORT = 7911;
public void startServer() { try { System.out.println("Thrift TThreadPoolServer start ....");
TProcessor tprocessor =new HelloWorldService.Processor<HelloWorldService.Iface>( new HelloWorldImpl());
TServerSocket serverTransport =new TServerSocket(SERVER_PORT); TThreadPoolServer.Args ttpsArgs = new TThreadPoolServer.Args( serverTransport); ttpsArgs.processor(tprocessor); ttpsArgs.protocolFactory(new TBinaryProtocol.Factory());
// 线程池服务模型,使用标准的阻塞式IO,预先创建一组线程处理请求。 TServer server =new TThreadPoolServer(ttpsArgs); server.serve();
} catch (Exceptione) { System.out.println("Server start error!!!"); e.printStackTrace(); } }
/** * @param args */ public static void main(String[] args) { HelloServerDemo server =new HelloServerDemo(); server.startServer(); }
} |
3.4 客户端代码
3.4.1 HelloWorldService编译生成的thrift服务类
拷贝编译生成的HelloWorldService.java到客户端项目
3.4.2 HelloClientDemo客户入口类
package com.tongyin.thrift.demo;
import org.apache.thrift.TException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException;
/** * thrift客户端 * @authorliulin * */ public class HelloClientDemo {
public static final String SERVER_IP ="localhost"; public static final int SERVER_PORT = 7911; public static final int TIMEOUT = 30000;
/** * * @param userName */ public void startClient(String userName) { TTransport transport =null; try { transport = new TSocket(SERVER_IP,SERVER_PORT,TIMEOUT); // 协议要和服务端一致 TProtocol protocol =new TBinaryProtocol(transport); HelloWorldService.Client client = new HelloWorldService.Client( protocol); transport.open(); String result =client.sayHello(userName); System.out.println("Thrify client result = " +result); } catch (TTransportExceptione) { e.printStackTrace(); } catch (TExceptione) { e.printStackTrace(); } finally { if (null !=transport) { transport.close(); } } }
/** * @param args */ public static void main(String[] args) { HelloClientDemo client =new HelloClientDemo(); client.startClient("Linda"); }
} |
3.5 启动项目
运行HelloServerDemo,启动服务
执行HelloClientDemo,客户端获得服务端返回的数据
4 性能测试
4.1 测试准备
我采用基于httpclient的方式,将thrift与spring进行了集成,具体代码不再列出,只列出与测试相关的信息。
4.1.1 user.thrift配置文件
namespace java com.tongyin.thrift.demo
struct User { 1: required string uid;#注释:用户id 2: optional string name;#注释:用户名 3: optional string mobile;#注释:用户手机号 4: optional string email;#注释:用户邮箱 5: optional i32 partyId;#注释:用户微信分组id,无用 6: optional i32 sysId;#注释:用户关心系统,系统id }
service UserService { list<User> getAllUser(); } |
4.1.2 服务实现类
返回包含10000个实例的list
package com.tongyin.thrift.demo;
import java.util.ArrayList; import java.util.List;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.thrift.TException; import org.springframework.stereotype.Service;
@Service(value="userService") public class UserServiceImpl implements UserService.Iface{
@Override public List<User> getAllUser()throws TException { intuserNum = 10000; List<User> users = new ArrayList<User>(); User tempuser; for(inti=1;i<=userNum;i++){ tempuser = new User(); tempuser.setUid(String.valueOf(i)); tempuser.setName("name"+i); tempuser.setMobile("10086"+i); tempuser.setPartyId(i/10); tempuser.setSysId(1); users.add(tempuser); } returnusers; }
}
|
4.2 thrift测试结果
耗时(毫秒) | 并发量=1 | 并发量=50 | 并发量=200 |
第1次 | 615 | 1671 | 3273 |
第2次 | 68 | 918 | 3025 |
第3次 | 29 | 948 | 2973 |
第4次 | 31 | 1020 | 2875 |
第5次 | 32 | 1062 | 2938 |
第6次 | 61 | 915 | 3031 |
第7次 | 31 | 1001 | 2910 |
第8次 | 29 | 941 | 2958 |
第9次 | 28 | 915 | 2892 |
第10次 | 27 | 962 | 2893 |
平均值 | 95.1 | 1035.3 | 2976.8 |
2-10次平均 | 37.33 | 964.67 | 2943.89 |
5 与CXF性能对比
WebService是常用的远程调用技术,也是一种RPC(Remote Procedure CallProtocol,远程过程调用协议通信),下面以Apache CXF为例,进行性能对比。
5.1 测试准备
5.1.1 VO
采用与thrift相同的字段
5.1.2 CXF服务实现类
与thrift相同,返回包含10000个实例的list
package com.tongyin.weixin.webservice;
import java.util.ArrayList; import java.util.List;
import javax.jws.WebService;
import org.springframework.stereotype.Service;
import com.tongyin.weixin.vo.UserVo;
@WebService(serviceName="helloWs", endpointInterface="com.tongyin.weixin.webservice.ITestService") @Service("testService") public class TestService implements ITestService {
public List<UserVo> getAllUser(){ intuserNum = 10000; List<UserVo> vos = new ArrayList<UserVo>(); UserVo tempvo; for(inti=1;i<=userNum;i++){ tempvo = new UserVo(); tempvo.setUid(String.valueOf(i)); tempvo.setName("name"+i); tempvo.setMobile("10086"+i); tempvo.setPartyId(i/10); tempvo.setSysId(1); vos.add(tempvo); } returnvos; }
}
|
5.2 对比结果
耗时(毫秒) | 并发量=1 | CXF并发量=50 | CXF并发量=200 | |||
Thrift | CXF | Thrift | CXF | Thrift | CXF | |
第1次 | 615 | 885 | 1671 | 2506 | 3273 | 8504 |
第2次 | 68 | 128 | 918 | 2831 | 3025 | 6758 |
第3次 | 29 | 46 | 948 | 2119 | 2973 | 8564 |
第4次 | 31 | 57 | 1020 | 2028 | 2875 | 6701 |
第5次 | 32 | 60 | 1062 | 2289 | 2938 | 6788 |
第6次 | 61 | 47 | 915 | 2234 | 3031 | 7959 |
第7次 | 31 | 53 | 1001 | 2178 | 2910 | 7757 |
第8次 | 29 | 48 | 941 | 2248 | 2958 | 8099 |
第9次 | 28 | 44 | 915 | 2205 | 2892 | 7266 |
第10次 | 27 | 46 | 962 | 1874 | 2893 | 8580 |
平均值 | 95.1 | 141.4 | 1035.3 | 2251.2 | 2976.8 | 7697.6 |
2-10次平均 | 37.33 | 58.78 | 964.67 | 2222.89 | 2943.89 | 7608 |
l 初次调用时,Thrift和CXF都需要进行一些初始化操作,耗时较长。Thrift耗时615毫秒,CXF耗时885毫秒,CXF比Thrift要多耗时270毫秒,是Thrift耗时的1.44倍;
l 在第一次调用完毕后,随后的调用中,性能都明显提升。Thrift在2-10次耗时的平均值为37.33,CXF在2-10次耗时的平均值为58.78,CXF平均耗时要比Thrift多21.5毫秒,是Thrift的1.57倍;
l 当有50个并发请求时,Thrift耗时1035.3毫秒,CXF耗时2251.2毫秒,CXF比Thrift多耗时1215.9,是Thrift耗时的2.17倍。
l 当有200个并发请求时,Thrift耗时2976.8毫秒,CXF耗时7697.6毫秒,CXF比Thrift多耗时4720.8,是Thrift耗时的2.58倍。
5.3 总结
l 从以上对比结果来看,Thrift的性能比Apache CXF要好很多;
l Thrift支持的语言很多,可以很方便的进行跨语言远程调用,甚至可以直接与JavaScript进行交互;
l Thrift想要与Spring进行整合还需要编写很多代码,Apache CXF能够很容易的与spring进行整合;
l Thrift的相关资料比较少,开发难度大,Apache CXF有丰富的资料。
参考资料:
1. http://blog.sina.com.cn/s/blog_72995dcc0101gn82.html
2. http://blog.csdn.net/taizhenba/article/details/49149589
3. http://baike.baidu.com/link?url=Kn_cU65EdYIupMDdBiYn_zuopJXx_r0-zPAtvvh6jL0ts6UsSyAKXo_YYFpwNwoGvhCIB7CBxtwqrofyYjWzCK