http://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/
http://www.cnblogs.com/exceptioneye/p/4945073.html
thrift数据类型
基本类型
- bool true 或 false,对应 Java 的 boolean
- byte 8 位有符号整数,对应 Java 的 byte
- i16 16 位有符号整数,对应 Java 的 short
- i32 32 位有符号整数,对应 Java 的 int
- i64 64 位有符号整数,对应 Java 的 long
- double 64 位浮点数,对应 Java 的 double
- string 对应 Java 的 String
- binary Blob (byte array)
结构体类型
- struct:定义公共的对象,类似Java 中是一个 JavaBean
容器类型
- map
异常类型
- exception 对应 Java 的 Exception
服务类型
- service:对应服务的类
tutorial.thrift文件说明
.thrift文件在tutorial目录下
//这里是包含另外的.thrift文件,
include "shared.thrift"
//namespace和java的package和c++的namespace差不多,后面跟的是语言的类型,最后tutorial是一个标识名。
namespace java tutorial
//定义常量
const i32 INT32CONSTANT = 9853
//thrift提供了容器map,map在.thrift中可以初始化,初始化的形式和python中一样。
const map<string,string> MAPCONSTANT ={'hello':'world', 'goodnight':'moon'}
//thrift中提供了枚举类型,和c++,java中差不多。
enum Operation {
ADD = 1,
SUBTRACT = 2,
MULTIPLY = 3,
DIVIDE = 4
}
//也提供了结构体,在结构体中定义依次需要定义的参数序号,参数类型以及参数名
struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
exception InvalidOperation {
1: i32 what,
2: string why
}
//service就是个接口的定义,需要在生成代码后在server中实现这些接口。
//同时,server还提供了继承,可以从另外一个service中继承接口,继承的关键字是用的java中的extend。
//另外thrift对于service有一些要求,如果继承*另外接口,那么不能有重名的接口名(也就是oop中的重载),
//还有就是,thrift不提供多态的概念。
service Calculator extends shared.SharedService {
void ping(),
i32 add(1:i32 num1, 2:i32 num2),
i32 calculate(1:i32 logid, 2:Work w) throws(1:InvalidOperation ouch),
oneway void zip()
}
Thrift java命令
thrift -gen java Hello.thrift
thrift -gen java:beans Hello.thrift
beans: Members will be private, and setter methods will return void.
private-members: Members will be private, but setter methods will return 'this'
nocamel: Do not use CamelCase field accessors with beans.
fullcamel: Convert underscored_accessor_or_service_names to camelCase.
android: Generated structures are Parcelable.
android_legacy: Do not use java.io.IOException(throwable) (available for Android 2.3 and above).
option_type: Wrap optional fields in an Option type.
java5: Generate Java 1.5 compliant code (includes android_legacy flag).
reuse-objects: Data objects will not be allocated, but existing instances will be used (read and write).
sorted_containers:
Use TreeSet/TreeMap instead of HashSet/HashMap as a implementation of set/map.
generated_annotations=[undated|suppress]:
undated: suppress the date at @Generated annotations
suppress: suppress @Generated annotations entirely
协议
Thrift 可以让用户选择客户端与服务端之间传输通信协议的类别,在传输协议上总体划分为文本 (text) 和二进制 (binary) 传输协议,为节约带宽,提高传输效率,一般情况下使用二进制类型的传输协议为多数,有时还会使用基于文本类型的协议,这需要根据项目 / 产品中的实际需求。常用协议有以下几种:
TBinaryProtocol —— 二进制编码格式进行数据传输
TCompactProtocol —— 高效率的、密集的二进制编码格式进行数据传输
TJSONProtocol —— 使用 JSON 的数据编码协议进行数据传输
TSimpleJSONProtocol —— 只提供 JSON 只写的协议,适用于通过脚本语言解析
TDebugProtocol – 在开发的过程中帮助开发人员调试用的,以文本的形式展现方便阅读。
传输层
TSocket —— 使用阻塞式 I/O 进行传输,是最常见的模式
TFramedTransport —— 使用非阻塞方式,按块的大小进行传输,类似于 Java 中的 NIO
TZlibTransport- 使用执行zlib压缩,不提供Java的实现
Thrift网络服务模型
Thrif 提供网络模型:单线程、多线程、事件驱动。从另一个角度划分为:阻塞服务模型、非阻塞服务模型
阻塞服务
TSimpleServer
TThreadPoolServer
非阻塞服务模型
TNonblockingServer
THsHaServer
TThreadedSelectotServer
TSimpleServer —— 单线程服务器端使用标准的阻塞式 I/O
TSimpleServer循环监听新请求的到来并完成对请求的处理,是个单线程阻塞模型。由于是一次只能接收和处理一个socket连接,效率比较低,在实际开发过程中很少用到它。
TThreadPoolServer —— 多线程服务器端使用标准的阻塞式 I/O
ThreadPoolServer为解决了TSimpleServer不支持并发和多连接的问题, 引入了线程池。但仍然是多线程阻塞模式
线程池采用线程数可伸缩的模式,线程池中的队列采用同步队列(SynchronousQueue)
ThreadPoolServer拆分了监听线程(accept)和处理客户端连接的工作线程(worker), 监听线程每接到一个客户端, 就交给线程池去处理
实例
编写thrift脚本文件
HelloService.thrift
namespace java com.xxx.thrift.test
struct User{
1:i32 id,
2:string username,
3:string password
}
exception AuthException{
1:string username,
2:string password
}
service HelloService{
User find(1:i32 uid),
void sayHello(1:string username),
i32 addUser(1:User user),
list<User> searchAll(),
void login(1:string username,2:string password) throws (1:AuthException authException)
}
编译成java文件
thrift -gen java:beans HelloService.thrift
生成User、AuthException、HelloService的java文件
编写HelloService实现类
HelloServiceImpl
package com.xxx.thrift.test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.thrift.TException;
public class HelloServiceImpl implements HelloService.Iface{
private static final List<User> list = new ArrayList<>();
private static final Map<Integer,User> map = new HashMap<Integer,User>();
static {
User user1 = new User(1, "ljh1", "123456");
User user2 = new User(2, "ljh2", "123456");
User user3 = new User(3, "ljh3", "123456");
list.add(user1);
list.add(user2);
list.add(user3);
map.put(user1.getId(),user1 );
map.put(user2.getId(),user2 );
map.put(user3.getId(),user3 );
}
@Override
public User find(int uid) throws TException {
return map.get(uid);
}
@Override
public void sayHello(String username) throws TException {
System.out.println(username+":sayHello");
}
@Override
public int addUser(User user) throws TException {
list.add(user);
map.put(user.getId(), user);
return user.getId();
}
@Override
public List<User> searchAll() throws TException {
return list;
}
@Override
public void login(String username, String password) throws AuthException, TException {
boolean exist = false;
for(User u:list){
if(u.getUsername()!=null && u.getPassword()!=null){
if(u.getUsername().equalsIgnoreCase(username) && u.getPassword().equals(password)){
exist = true;
break;
}
}
}
if(!exist){
throw new AuthException(username,password);
}
}
}
服务端测试代码
package com.xxx.thrift.test;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import com.xxx.thrift.test.HelloService.Iface;
public class Server {
public static void main(String[] args) throws Exception {
//服务端传输层:TServerSocket transport = new TServerSocket(Constant.SERVER_PORT);
//客户端传输层:TTransport transport = new TSocket(Constant.SERVER_IP, Constant.SERVER_PORT);
//服务端协议:TProtocolFactory factory = new TBinaryProtocol.Factory();
//客户端协议:TProtocol protocol = new TBinaryProtocol(transport);
//服务端绑定处理器:TProcessor processor = new HelloService.Processor<Iface>(new HelloServiceImpl());
//客户端client:HelloService.Client client = new HelloService.Client(protocol);
//服务端参数绑定
//TThreadPoolServer.Args targs = new TThreadPoolServer.Args(transport);
//targs.processor(processor);
//targs.protocolFactory(factory);
// 关联处理器与 Hello 服务的实现
TProcessor processor = new HelloService.Processor<Iface>(new HelloServiceImpl());
// 设置服务端口与超时时间
TServerTransport transport = new TServerSocket(Constant.SERVER_PORT, Constant.TIMEOUT);
// 设置协议工厂
TProtocolFactory factory = new TBinaryProtocol.Factory();
TThreadPoolServer.Args targs = new TThreadPoolServer.Args(transport);
targs.processor(processor);
targs.protocolFactory(factory);
//Thrift 服务器包含用于绑定协议和传输层的基础架构,它提供阻塞、非阻塞、单线程和多线程的模式运行在服务器上,
//可以配合服务器 / 容器一起运行,可以和现有的 J2EE 服务器 /Web 容器无缝的结合
TServer tserver = new TThreadPoolServer(targs);
System.out.println("HelloService TThreadPoolServer....");
tserver.serve();
}
}
客户端代码
package com.xxx.thrift.test;
import java.util.List;
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;
public class Client {
public static void main(String[] args) throws Exception {
// 设置客户端传输通道
TTransport transport = new TSocket(Constant.SERVER_IP, Constant.SERVER_PORT, Constant.TIMEOUT);
//TServerTransport transport = new TServerSocket(Constant.SERVER_PORT, Constant.TIMEOUT);
// 协议要和服务端一致
// 使用二进制协议
TProtocol protocol = new TBinaryProtocol(transport);
// 创建Client
HelloService.Client client = new HelloService.Client(protocol);
transport.open();
try{
//服务端返回为空抛异常
User user100 = client.find(100);
System.out.println(user100);
}catch(TException e){
}
User user1000 = new User(1000,"ljh1000","123456");
client.addUser(user1000);
client.sayHello("ljh");
try{
client.login("1ljh1", "123456");
}catch(AuthException e){
System.err.println(e.getUsername()+"===="+e.getPassword());
}
List<User> list = client.searchAll();
for(User u:list){
System.out.println(u);
}
transport.close();
}
}
TThreadPoolServer模式优点:
线程池模式中,数据读取和业务处理都交由线程池完成,主线程只负责监听新连接,因此在并发量较大时新连接也能够被及时接受。线程池模式比较适合服务器端能预知最多有多少个客户端并发的情况,这时每个请求都能被业务线程池及时处理,性能也非常高。
TThreadPoolServer模式缺点:
线程池模式的处理能力受限于线程池的工作能力,当并发请求数大于线程池中的线程数时,新请求也只能排队等待。
TNonblockingServer —— 多线程服务器端使用非阻塞式 I/O
TNonblockingServer采用单线程非阻塞(NIO)的模式, 借助Channel/Selector机制, 采用IO事件模型来处理。所有的socket都被注册到selector中,在一个线程中通过seletor循环监控所有的socket,每次selector结束时,处理所有的处于就绪状态的socket,对于有数据到来的socket进行数据读取操作,对于有数据发送的socket则进行数据发送,对于监听socket则产生一个新业务socket并将其注册到selector中。
private void select() {
try {
selector.select(); // wait for io events.
// process the io events we received
Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
while (!stopped_ && selectedKeys.hasNext()) {
SelectionKey key = selectedKeys.next();
selectedKeys.remove();
if (key.isAcceptable()) {
handleAccept(); // deal with accept
} else if (key.isReadable()) {
handleRead(key); // deal with reads
} else if (key.isWritable()) {
handleWrite(key); // deal with writes
}
}
} catch (IOException e) {
}
}
select代码里对accept/read/write等IO事件进行监控和处理, 唯一可惜的这个单线程处理. 当遇到handler里有阻塞的操作时, 会导致整个服务被阻塞住
实例
服务端
package com.xxx.thrift.test;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import com.xxx.thrift.test.HelloService.Iface;
public class TTNONServer {
public static void main(String[] args) throws Exception {
// 处理器
TProcessor processor = new HelloService.Processor<Iface>(new HelloServiceImpl());
// 传输通道 - 非阻塞方式
TNonblockingServerSocket transport = new TNonblockingServerSocket(Constant.SERVER_PORT);
// 使用高密度二进制协议
TProtocolFactory factory = new TCompactProtocol.Factory();
// 异步IO,需要使用TFramedTransport,它将分块缓存读取
TNonblockingServer.Args targs = new TNonblockingServer.Args(transport);
targs.processor(processor);
targs.protocolFactory(factory);
//服务端和客户端需要指定TFramedTransport数据传输的方式
targs.transportFactory(new TFramedTransport.Factory());
TServer tserver = new TNonblockingServer(targs);
System.out.println("HelloService TNonblockingServer....");
tserver.serve();
}
}
客户端
package com.xxx.thrift.test;
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;
public class TNONClient {
public static void main(String[] args) throws Exception {
// 设置传输通道,对于非阻塞服务,需要使用TFramedTransport,它将数据分块发送
TTransport tsocket = new TSocket(Constant.SERVER_IP, Constant.SERVER_PORT, Constant.TIMEOUT);
TTransport transport = new TFramedTransport(tsocket);
// 协议要和服务端一致
//HelloTNonblockingServer
// 使用高密度二进制协议
TProtocol protocol = new TCompactProtocol(transport);
HelloService.Client client = new HelloService.Client(protocol);
transport.open();
client.sayHello("ljh");
transport.close();
}
}
TNonblockingServer模式优点:
相比于TSimpleServer效率提升主要体现在IO多路复用上,TNonblockingServer采用非阻塞IO,同时监控多个socket的状态变化;
TNonblockingServer模式缺点:
TNonblockingServer模式在业务处理上还是采用单线程顺序来完成,在业务处理比较复杂、耗时的时候,例如某些接口函数需要读取数据库执行时间较长,此时该模式效率也不高,因为多个调用请求任务依然是顺序一个接一个执行。
THsHaServer
THsHaServer类是TNonblockingServer类的子类,为解决TNonblockingServer的缺点, THsHa引入了线程池去处理, 其模型把读写任务放到线程池去处理即多线程非阻塞模式。HsHa是: Half-sync/Half-async的处理模式, Half-aysnc是在处理IO事件上(accept/read/write io), Half-sync用于handler对rpc的同步处理上。因此可以认为THsHaServer半同步半异步。
THsHaServer的优点:
与TNonblockingServer模式相比,THsHaServer在完成数据读取之后,将业务处理过程交由一个线程池来完成,主线程直接返回进行下一次循环操作,效率大大提升;
THsHaServer的缺点:
主线程需要完成对所有socket的监听以及数据读写的工作,当并发请求数较大时,且发送数据量较多时,监听socket上新连接请求不能被及时接受
TThreadedSelectorServer
TThreadedSelectorServer是大家广泛采用的服务模型,其多线程服务器端使用非堵塞式I/O模型,是对TNonblockingServer的扩充, 其分离了Accept和Read/Write的Selector线程, 同时引入Worker工作线程池。
(1)一个AcceptThread线程对象,专门用于处理监听socket上的新连接;
(2)若干个SelectorThread对象专门用于处理业务socket的网络I/O操作,所有网络数据的读写均是有这些线程来完成;
(3)一个负载均衡器SelectorThreadLoadBalancer对象,主要用于AcceptThread线程接收到一个新socket连接请求时,决定将这个新连接请求分配给哪个SelectorThread线程。
(4)一个ExecutorService类型的工作线程池,在SelectorThread线程中,监听到有业务socket中有调用请求过来,则将请求读取之后,交个ExecutorService线程池中的线程完成此次调用的具体执行
MainReactor就是Accept线程, 用于监听客户端连接, SubReactor采用IO事件线程(多个), 主要负责对所有客户端的IO读写事件进行处理. 而Worker工作线程主要用于处理每个rpc请求的handler回调处理(这部分是同步的)。因此其也是Half-Sync/Half-Async(半异步-半同步)的 。
TThreadedSelectorServer模式对于大部分应用场景性能都不会差,因为其有一个专门的线程AcceptThread用于处理新连接请求,因此能够及时响应大量并发连接请求;另外它将网络I/O操作分散到多个SelectorThread线程中来完成,因此能够快速对网络I/O进行读写操作,能够很好地应对网络I/O较多的情况
package com.xxx.thrift.test;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TNonblockingServer;
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.TTransportException;
import com.xxx.thrift.test.HelloService.Iface;
public class SelectorServer {
public static void main(String[] args) throws Exception {
// 处理器
TProcessor processor = new HelloService.Processor<Iface>(new HelloServiceImpl());
// 传输通道 - 非阻塞方式
TNonblockingServerSocket transport = new TNonblockingServerSocket(Constant.SERVER_PORT);
// 使用高密度二进制协议
TProtocolFactory factory = new TCompactProtocol.Factory();
// 异步IO,需要使用TFramedTransport,它将分块缓存读取
TThreadedSelectorServer.Args targs = new TThreadedSelectorServer.Args(transport);
targs.processor(processor);
targs.protocolFactory(factory);
//服务端和客户端需要指定TFramedTransport数据传输的方式
targs.transportFactory(new TFramedTransport.Factory());
TServer tserver = new TThreadedSelectorServer(targs);
System.out.println("HelloService TNonblockingServer....");
tserver.serve();
}
}
Thrift序列化机制
Thrift提供了可扩展序列化机制, 不但兼容性好而且压缩率高。
thrift 数据格式描述
thrift的向后兼容性(Version)借助属性标识(数字编号id + 属性类型type)来实现, 可以理解为在序列化后(属性数据存储由 field_name:field_value => id+type:field_value)
我们定义IDL文件形如
namespace java stu.thrift;
struct User {
1: required string name
2: required string address
}
数据传输格式 | 类型 | 优点 | 缺点 |
---|---|---|---|
Xml | 文本 | 1、良好的可读性 | 1、数据传输量大 |
2、序列化的数据包含完整的结构 | 2、不支持二进制数据类型 | ||
3、调整不同属性的顺序对序列化/反序列化不影响 | |||
Json | 文本 | 1、良好的可读性 | 1、丢弃了类型信息, 比如”price”:100, 对price类型是int/double解析有二义性 |
2、调整不同属性的顺序对序列化/反序列化不影响 | 2、不支持二进制数据类型 | ||
Thrift | 二进制 | 高效 | 1、不宜读 |
2、向后兼容有一定的约定限制,采用id递增的方式标识并以optional修饰来添加 | |||
Google Protobuf | 二进制 | 高效 | 1、不宜读 |
2、向后兼容有一定的约定限制 |