RPC远程协议之Thrift入门
在上一篇文章《RPC远程协议之原理分析》中,我介绍了RPC的工作原理及欲实现RPC框架功能应该做哪些事情,因为要做的事情太多,完全由开发人员研发实现,不是很现实,所以市面上出现了诸多RPC快捷框架,目前主流的有Facebook的Thrift、谷歌的gRPC,以及Dubbo,但就性能角度考虑,Thrift相对好些,并且是跨语言的,所以这里先以Thrift的介绍开始。对于Facebook,我们现阶段只需要知道它是一个高性能的、支持跨语言平台的远程服务调用框架,并且作为很多企业实现分布式系统架构的服务调用实现的基础,以及该怎样去使用它来快速搭建服务调用功能。
l 数据类型
l 准备条件
l 例子验证
一、数据类型
我们知道,Thrift是跨语言的RPC开源框架,那么它应该有自己的消息数据类型,而不是其它任何一门语言的数据类型,否则就不能支持其它语言,那么就看下它支持的几种定制类型,具体如下:
1、基础类型
类型
位数(有符号)
描述
bool
-
布尔类型,值为true或false
byte
8
字节类型,8位有符号整型数据
i16
16
整型类型,16位有符号整型数据
i32
32
整型类型,32位有符号整型数据
i64
64
整型类型,64位有符号整型数据
double
64
浮点类型,64位有符号浮点数字
string
-
文本类型,UTF-8格式字符串类型
为什么Thrift的数据类型都是有符号?因为很多语言都不支持无符号的数据类型,所以Thrift为了满足大部分语言特征,所以没必要加入无符号的数据类型。
2、特殊类型
binary,未经编码的字节流类型,主要针对字符串类型的字节流化,提供与java语言更好的互操作性。
3、结构类型
struct,定义普通的OOP类型,但不支持继承特性。
4、容器类型
list,一种有序的列表集合类型,如:对应java的List
set,一种无序的唯一值集合类型,如:对应java的Set
map,一种离散的键值对集合类型,如:对应java的Map
5、服务类型
service,定义对外提供的服务,如:供客户端使用的服务接口。
6、异常类型
exception,一种Thrift本身定制异常,与其它语言无缝结合的异常类型。
在下面的例子会演示基础类型、容器类型、结构类型,以及服务类型的使用,供读者参考。
二、准备条件
1、C/S双端
这里的C/S双端指的是客户端和服务端,实际使用时,客户端与服务端进程往往不在同一个节点中,比如:分布式环境,所以客户端和服务端一般是分离的,客户端需要引用由服务端所生成的服务代码,来完成远程调用。但在这里,我们将客户端与服务端代码均放在同一个项目中,所以客户端不需要额外引入即可使用调用,当然两端也分别在不同的进程中运行通信。
2、编译环境
下载地址:
http://thrift.apache.org/download
如果是mac osx则可以使用brew install thrift自动安装;
如果是linux系统,则可使用apt-get install thrift-compiler安装均可。
安装后,可以使用thrift –version查看版本,如果正常显示,则安装完成。
3、依赖添加
我这里采用maven来加载和管理thrift依赖包,并且使用最新版本0.11.0,具体配置如下:
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.11.0</version>
</dependency>
三、例子验证
在这里,我就不以典型的helloworld为例,而是以根据用户ID获取用户基本信息和该用户的订单为例,详细介绍下Thrift的几种数据类的使用方法。
1、编写IDL文件(user.thrift)
# defaine the namespace
namespace java com.cwteam
# define the struct
struct User {
1:i32 uid,
2:string name,
3:i16 sex,
4:list<Order> orderList
}
struct Order {
1:string oid,
2:string oname,
3:double price,
4:i32 number,
5:string createAt
}
# define the service
service UserService {
# Get the user's order list by uid
User getUserOrders(1:i32 uid)
# Other operation follow here
# ...
}
2、生成语言文件(UserService.java)
使用Thrift提供的编辑工具生成,切换到user.thrift文件所在目录,我的结构如下:
也就是user.thrift存放在main下,切换到main下,使用thrift命令编译生成语言文件,如下所示:
#thrift--gen java user.thrift
生成后的stub文件如下样子:
3、业务接口实现(UserHandler.java)
/** * 用户服务接口,由服务端负责实现 */ public class UserHandler implements UserService.Iface { public User getUserOrders(int uid) throws TException { User user = new User(); // 模拟实现用户及订单查询 user.setUid(uid); user.setName("David Lang"); user.setSex((short)1); List<Order> orders = new ArrayList<Order>(); Order order = new Order(); order.setOid("NO1112321"); order.setOname("《Thrift进阶与提高》"); order.setPrice(99.99); order.setNumber(1); order.setCreateAt("2017-04-04"); orders.add(order); Order order2 = new Order(); order2.setOid("NO1112322"); order2.setOname("《RPC进阶与提高》"); order2.setPrice(88.88); order2.setNumber(2); order2.setCreateAt("2017-04-05"); orders.add(order2); user.setOrderList(orders); return user; } }
4、服务端实例(UserServer.java)
/** * 将业务处理逻辑UserHandler作为具体的业务 * 处理器,传递给Thrift服务器,执行处理逻辑. */ public class UserServer { private static final int port = 9081; private static UserHandler handler; private static UserService.Processor processor; /** * 启动服务端 * processor为控制调用逻辑 */ public static void start(UserService.Processor processor) { try { // 阻塞方式,基于ServerSocket TServerTransport serverTransport = new TServerSocket(port); TServer server = new TSimpleServer(new TServer.Args(serverTransport).processor(processor)); System.out.println("Starting simple server ..."); // 启动服务 server.serve(); } catch(Exception e) { e.printStackTrace(); } } public static void main(String[] args) { handler = new UserHandler(); processor = new UserService.Processor(handler); start(processor); } }
5、客户端实例(UserClient.java)
/** * 远程调用服务接口,获取用户信息及订单列表 */ public class UserClient { private static final int port = 9081; private static final String addr = "localhost"; private static UserService.Client client; private static TTransport transport; /** * 创建TTransport */ private static TTransport createTTransport() { TTransport transport = new TSocket(addr,port); return transport; } /** * 开启TTransport */ private static void openTTransport(TTransport transport) throws TTransportException { if(null == transport) { return; } transport.open(); } /** * 关闭TTransport */ private static void closeTTransport(TTransport transport) { if(null == transport) { return; } transport.close(); } /** * 创建客户端实体 */ private static UserService.Client createClient(TTransport transport) { if(null == transport) { return null; } // 编码协议指定(这里是二进制方式传递) TProtocol protocol = new TBinaryProtocol(transport); if(null == protocol) { return null; } // 设定编码协议 UserService.Client client = new UserService.Client(protocol); return client; } public static void main(String[] args) { try { transport = createTTransport(); openTTransport(transport); client = createClient(transport); // 调用远程服务 if(null == client) { System.out.println("客户端生成失败,不能调用服务 ..."); return; } User user = client.getUserOrders(10000021); System.out.println(user); } catch(TException e1) { e1.printStackTrace(); } } }
6、例子运行结果
启动服务端:
启动客户端:
好了,Thrift入门就介绍到这里,读者也会发现这里所实现的服务调用方式为阻塞的,并且是非多线程的,所以在后续文章会继续介绍下阻塞多线程和非阻塞多线程的内容,敬请期待!