由浅入深了解Thrift之服务模型和序列化机制
一、Thrift介绍
Thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎。其允许你定义一个简单的定义文件中的数据类型和服务接口。以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。
二、Thrift基础架构
Thrift是一个客户端和服务端的架构体系,数据通过socket传输;
具有自己内部定义的传输协议规范(TProtocol)和传输数据标准(TTransports);
通过IDL脚本对传输数据的数据结构(struct) 和传输数据的业务逻辑(service)根据不同的运行环境快速的构建相应的代码;
通过自己内部的序列化机制对传输的数据进行简化和压缩提高高并发、 大型系统中数据交互的性能。
- Thrift 支持的数据类型
基本类型:
bool: 布尔值
byte: 8位有符号整数
i16: 16位有符号整数
i32: 32位有符号整数
i64: 64位有符号整数
double: 64位浮点数
string: UTF-8编码的字符串
binary: 二进制串
结构体类型:
struct: 定义的结构体对象
容器类型:
list: 有序元素列表
set: 无序无重复元素集合
map: 有序的key/value集合
异常类型:
exception: 异常类型
服务类型:
service: 具体对应服务的类
- 协议
Thrift可以让你选择客户端与服务端之间传输通信协议的类别,在传输协议上总体上划分为文本(text)和二进制(binary)传输协议, 为节约带宽,提供传输效率,一般情况下使用二进制类型的传输协议为多数,但有时会还是会使用基于文本类型的协议,这需要根据项目/产品中的实际需求:
1、TBinaryProtocol – 二进制编码格式进行数据传输。
2、TCompactProtocol – 这种协议非常有效的,使用Variable-Length Quantity (VLQ) 编码对数据进行压缩。
3、TJSONProtocol – 使用JSON的数据编码协议进行数据传输。
4、TSimpleJSONProtocol – 这种节约只提供JSON只写的协议,适用于通过脚本语言解析
5、TDebugProtocol – 在开发的过程中帮助开发人员调试用的,以文本的形式展现方便阅读。
- 传输层
1、TSocket- 使用堵塞式I/O进行传输,也是最常见的模式。
2、TFramedTransport- 使用非阻塞方式,按块的大小,进行传输,类似于Java中的NIO。
3、TFileTransport- 顾名思义按照文件的方式进程传输,虽然这种方式不提供Java的实现,但是实现起来非常简单。
4、TMemoryTransport- 使用内存I/O,就好比Java中的ByteArrayOutputStream实现。
5、TZlibTransport- 使用执行zlib压缩,不提供Java的实现。
三、Thrift网络服务模型
Thrif 提供网络模型:单线程、多线程、事件驱动。从另一个角度划分为:阻塞服务模型、非阻塞服务模型。
- 阻塞服务
TSimpleServer
TThreadPoolServer
- 非阻塞服务模型
TNonblockingServer
THsHaServer
TThreadedSelectorServer
1、TSimpleServer
TSimpleServer实现是非常的简单,循环监听新请求的到来并完成对请求的处理,是个单线程阻塞模型。由于是一次只能接收和处理一个socket连接,效率比较低,在实际开发过程中很少用到它。
2、TThreadPoolServer
ThreadPoolServer为解决了TSimpleServer不支持并发和多连接的问题, 引入了线程池。但仍然是多线程阻塞模式即实现的模型是One Thread Per Connection。
线程池采用能线程数可伸缩的模式,线程池中的队列采用同步队列(SynchronousQueue)。
ThreadPoolServer拆分了监听线程(accept)和处理客户端连接的工作线程(worker), 监听线程每接到一个客户端, 就投给线程池去处理。
import org.apache.thrift.TException;
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; /**
* 注册服务端
* 线程池服务模型,使用标准的阻塞式IO,预先创建一组线程处理请求
*/
public class ThriftTThreadPoolServer {
// 注册端口
public static final int SERVER_PORT = 8080; public static void main(String[] args) throws TException {
TProcessor tprocessor = new HelloWorld.Processor<HelloWorld.Iface>(new HelloWorldImpl());
// 阻塞IO
TServerSocket serverTransport = new TServerSocket(SERVER_PORT);
// 多线程服务模型
TThreadPoolServer.Args tArgs = new TThreadPoolServer.Args(serverTransport);
tArgs.processor(tprocessor);
// 客户端协议要一致
tArgs.protocolFactory(new TBinaryProtocol.Factory());
// 线程池服务模型,使用标准的阻塞式IO,预先创建一组线程处理请求。
TServer server = new TThreadPoolServer(tArgs);
System.out.println("Hello TThreadPoolServer....");
server.serve(); // 启动服务
}
}
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 BlockClient {
public static final String SERVER_IP = "127.0.0.1";
public static final int SERVER_PORT = 8080;
public static final int TIMEOUT = 30000; public static void main(String[] args) throws TException {
// 设置传输通道
TTransport transport = new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT);
// 协议要和服务端一致
// 使用二进制协议
TProtocol protocol = new TBinaryProtocol(transport);
// 创建Client
HelloWorld.Client client = new HelloWorld.Client(protocol);
transport.open();
String result = client.sayHello("thrift");
System.out.println("result : " + result);
// 关闭资源
transport.close();
}
}
TThreadPoolServer模式优点:
线程池模式中,数据读取和业务处理都交由线程池完成,主线程只负责监听新连接,因此在并发量较大时新连接也能够被及时接受。线程池模式比较适合服务器端能预知最多有多少个客户端并发的情况,这时每个请求都能被业务线程池及时处理,性能也非常高。
TThreadPoolServer模式缺点:
线程池模式的处理能力受限于线程池的工作能力,当并发请求数大于线程池中的线程数时,新请求也只能排队等待。
3、TNonblockingServer
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里有阻塞的操作时, 会导致整个服务被阻塞住。
import org.apache.thrift.TException;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket; /**
* 注册服务端
* 使用非阻塞式IO,服务端和客户端需要指定 TFramedTransport 数据传输的方式
*/
public class ThriftTNonblockingServer {
// 注册端口
public static final int SERVER_PORT = 8080; public static void main(String[] args) throws TException {
// 处理器
TProcessor tprocessor = new HelloWorld.Processor<HelloWorld.Iface>(new HelloWorldImpl());
// 传输通道 - 非阻塞方式
TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(SERVER_PORT);
// 异步IO,需要使用TFramedTransport,它将分块缓存读取。
TNonblockingServer.Args tArgs = new TNonblockingServer.Args(serverTransport);
tArgs.processor(tprocessor);
tArgs.transportFactory(new TFramedTransport.Factory());
// 使用高密度二进制协议
tArgs.protocolFactory(new TCompactProtocol.Factory());
// 使用非阻塞式IO,服务端和客户端需要指定TFramedTransport数据传输的方式
TServer server = new TNonblockingServer(tArgs);
System.out.println("Hello TNonblockingServer....");
server.serve(); // 启动服务
}
}
import org.apache.thrift.TException;
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 NonblockingClient {
public static final String SERVER_IP = "127.0.0.1";
public static final int SERVER_PORT = 8080;
public static final int TIMEOUT = 30000; public static void main(String[] args) throws TException {
// 设置传输通道,对于非阻塞服务,需要使用TFramedTransport,它将数据分块发送
TTransport transport = new TFramedTransport(new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT));
// 协议要和服务端一致
//HelloTNonblockingServer
// 使用高密度二进制协议
TProtocol protocol = new TCompactProtocol(transport); // 使用二进制协议
//TProtocol protocol = new TBinaryProtocol(transport);
HelloWorld.Client client = new HelloWorld.Client(protocol);
transport.open();
String result = client.sayHello("jack");
System.out.println("result : " + result);
// 关闭资源
transport.close();
}
}
TNonblockingServer模式优点:
相比于TSimpleServer效率提升主要体现在IO多路复用上,TNonblockingServer采用非阻塞IO,同时监控多个socket的状态变化;
TNonblockingServer模式缺点:
TNonblockingServer模式在业务处理上还是采用单线程顺序来完成,在业务处理比较复杂、耗时的时候,例如某些接口函数需要读取数据库执行时间较长,此时该模式效率也不高,因为多个调用请求任务依然是顺序一个接一个执行。
4、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上新连接请求不能被及时接受。
5、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较多的情况。
import org.apache.thrift.TException;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadedSelectorServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket; /**
* TFramedTransport 数据传输的方式
*/
public class ThriftTThreadedSelectorServer {
// 注册端口
public static final int SERVER_PORT = 8080; public static void main(String[] args) throws TException {
TProcessor tprocessor = new HelloWorld.Processor<HelloWorld.Iface>(new HelloWorldImpl()); TThreadedSelectorServer.Args tArgs = new TThreadedSelectorServer.Args(serverTransport);
tArgs.processor(tprocessor);
tArgs.transportFactory(new TFramedTransport.Factory());
// 二进制协议
tArgs.protocolFactory(new TBinaryProtocol.Factory()); TServer server = new TThreadedSelectorServer(tArgs);
System.out.println("Hello TThreadedSelectorServer....");
server.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/json有了很大的差别,那么我们来比较是常见的数据传输格式
数据传输格式 | 类型 | 优点 | 缺点 |
Xml | 文本 |
1、良好的可读性 2、序列化的数据包含完整的结构 3、调整不同属性的顺序对序列化/反序列化不影响 |
1、数据传输量大 2、不支持二进制数据类型 |
Json | 文本 |
1、良好的可读性 2、调整不同属性的顺序对序列化/反序列化不影响 |
1、丢弃了类型信息, 比如"price":100, 对price类型是int/double解析有二义性 2、不支持二进制数据类型 |
Thrift | 二进制 | 高效 |
1、不宜读 2、向后兼容有一定的约定限制,采用id递增的方式标识并以optional修饰来添加 |
Google Protobuf | 二进制 | 高效 |
1、不宜读 2、向后兼容有一定的约定限制 |
由于本人经验有限,文章中难免会有错误,请浏览文章的您指正或有不同的观点共同探讨!
由浅入深了解Thrift之服务模型和序列化机制的更多相关文章
- 由浅入深了解Thrift之客户端连接池化
一.问题描述 在上一篇<由浅入深了解Thrift之服务模型和序列化机制>文章中,我们已经了解了thrift的基本架构和网络服务模型的优缺点.如今的互联网圈中,RPC服务化的思想如火如荼.我 ...
- Thrift 个人实战--Thrift 的序列化机制
前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还 ...
- Thrift 个人实战--Thrift 网络服务模型
前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还 ...
- 由浅入深了解Thrift之客户端连接池化续
前文<由浅入深了解Thrift之客户端连接池化>中我们已经实现了服务调用端 连接的池化,实现的过于简陋,离实际的项目运用还很遥远.本文将在进一步改造,主要是两方面:1.服务端如何注册多个服 ...
- hrift 的序列化机制
Thrift 个人实战--Thrift 的序列化机制 前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码 ...
- Thrift 个人实战--Thrift 网络服务模型(转)
前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还 ...
- Hadoop序列化机制及实例
序列化 1.什么是序列化?将结构化对象转换成字节流以便于进行网络传输或写入持久存储的过程.2.什么是反序列化?将字节流转换为一系列结构化对象的过程.序列化用途: 1.作为一种持久化格式. 2.作为一种 ...
- 深入挖掘.NET序列化机制——实现更易用的序列化方案
.NET框架为程序员提供了“序列化和反序列化”这一有力的工具,使用它,我们能很容易的将内存中的对象图转化为字节流,并在需要的时候再将其恢复.这一技术的典型应用场景包括[1] : 应用程序运行状态的持久 ...
- Hadoop阅读笔记(六)——洞悉Hadoop序列化机制Writable
酒,是个好东西,前提要适量.今天参加了公司的年会,主题就是吃.喝.吹,除了那些天生话唠外,大部分人需要加点酒来作催化剂,让一个平时沉默寡言的码农也能成为一个喷子!在大家推杯换盏之际,难免一些画面浮现脑 ...
随机推荐
- Python学习教程(learning Python)--3.3.2 Python的关系运算
如果if的condition不用布尔表达式来做条件判断而采用关系表达式,实际上关系表达式运算的结果要么是True要么是False.下面我们先了解一些有关关系运算符的基础知识,如下表所示. 做个小程序测 ...
- [转载]--用Python 自动安装软件
脚本使用了 Python 2.3 + Com 对象,所以你的系统必须安装Python2.3或更高版本同时必须安装 Mark Hammond's Win32all 模块 (特别感谢Mark Hamm ...
- ED/EP系列1《简介》
电子存折(ED:ElectronicDeposit)一种为持卡人进行消费.取现等交易而设计的支持个人识别码(PIN)保护的金融IC卡应用.它支持圈存.圈提.消费和取现等交易. 电子钱包(EP:Elec ...
- Oracle Database Links解析
什么是Database Links呢? 首先我们阐述下它的作用:使用户可以通过一个数据库访问到另外一个远程数据库. 那么Database Link是存储着远程数据库的连接信息. 如下图所示: 用户Sc ...
- job不自动运行解决方法
一.plsql.新建命令窗口 用查询语句: show parameter job_queue_processes 看看job_queue_processes的值 如果你的job很多那么将这个值设大,5 ...
- Stream,Reader/Writer,Buffered的区别(1)
Stream: 是字节流形式,exe文件,图片,视频等.支持8位的字符,用于 ASCII 字符和二进制数据. Reader/Writer: 是字符流,文本文件,XML,txt等,用于16位字符,也就是 ...
- poj 1679 The Unique MST
题目连接 http://poj.org/problem?id=1679 The Unique MST Description Given a connected undirected graph, t ...
- mac下163企业邮箱客户端的配置
一 添加账户 添加账户->添加其他邮件账户->输入电子邮件地址和密码.(全名随意起). 二 收件服务器和发件服务器的设置 收件服务器:pop.qiye.163. ...
- 高德地图JavaScript API开发研究
高德地图JavaScript API是一套用JavaScript 语言编写的应用程序接口,可以通过各种API接口向地图添加内容,创建功能丰富.交互性强的地图应用.高德地图JavaScript API ...
- 关于如何将Excel数据导入到SQL Server中
面对大量的Excel数据我们可能会非常苦恼,如果一条一条的插入到数据库:不仅会耗大量的时间,而且还可能会发生错误,现在我来说一下如何导入数据! 1.准备工作 首先要在Excel中建立数据表对应的数据字 ...