Thrift多路复用的设计与实现



# 介绍

Thrift作为一个跨语言的rpc框架,为后端服务间的多语言混合开发提供了高可靠,可扩展,以及高效的实现。但是自从2007facebook开源后的6年时间内一直缺少一个多路复用的功能(Multiplexing Services),也就是在一个Server上面实现多个Service调用。比如要实现一个后端服务,很多时候不仅仅只是实现业务服务接口,往往还会预留一些调试,监控服务接口,以往要实现多个Service在一个Server上面调用,要么将多个服务糅合成一个庞大的服务(图1),要么将多个Service分摊到不同的Server上(图2)。最近的版本0.9.1终于实现内置的多路复用,本文将就该功能简要分析一下该功能的设计与实现,并提供一个简单的示例。

thrift-monolithic-interface

图1

thrift-multi-server

图2

 

## 设计

Thrift的架构如下:

thrift-arch

Thrift采取了很优雅的分层设计,下面简述各层的主要功能:

Ø Transport

负责传输数据的接口,有文件,内存,套接字等实现。

Ø Protocol

负责数据的协议交互接口,有二进制,Json等实现。

Ø Processor

负责输入输出数据处理的接口,其实就是对Protocol的处理。

Ø Server

包括了上面各层的创建和管理,并提供网络服务的功能,网络服务这块目前有四个实现,分别是最简单的单线程阻塞,多线程阻塞,线程池阻塞和基于libevent的非阻塞模式。

实现多路复用有以下几个需要注意的地方:

Ø 不修改现有的Thrift代码,主要是指向后兼容,旧代码可以在新版本中编译

Ø 不修改白皮书上说的协议

Ø 不依赖任何Server层代码,也不需要新的Server实现

0.9.1之前版本不能多个Service在同一个Server上面调用,主要是因为在协议层只把Service的函数名打包,没有将ServiceName打包进去,所以在Processor层默认只能处理一个Service

新版本中通过新增以下设计完成了Service的多路复用:

thrift-multiplex-arch

thrift-multiplex

深色部分为新增部分,需要多个Service复用Server的客户端需要使用新的Protocol实现,支持Service复用的服务端需要通过新的Processor实现注册不同的Service

## 实现

release-notes看到目前已经有JavaDelphiC#C++几个语言实现了该功能,具体到C++实现按照THRIFT-1902的介绍,是直接移植的java版本。

### 新增代码

protocol/TMultiplexedProtocol.h

protocol/TMultiplexedProtocol.cpp

protocol/TProtocolDecorator.h

processor/TMultiplexedProcessor.h

### 主要逻辑

1) TMultiplexedProtocol类重写writeMessageBegin_virt方法,在Service的函数名前新增了ServiceName,并附带了一个分隔符;该类采取了decorator模式将绝大部分操作转发到实际的protocol对象。

2) TMultiplexedProcessor类增加了一个存放servicemap

typedef std::map< std::string, shared_ptr<TProcessor> > services_t;

key为ServiceName,这样在process方法中先解出TMultiplexedProtocol传递过来的ServiceName,通过该key查找到对应的Processor对象,再调用该Processor的process方法,这样就完美的实现了多个serivice的共存。

## 使用示例

IDL:
namespace cpp thrift.multiplex.demo

service FirstService
{
void blahBlah()
}

service SecondService
{
void blahBlah()
}

Server:
int port = 9090;
shared_ptr<TProcessor> processor1(new FirstServiceProcessor
(shared_ptr<FirstServiceHandler>(new FirstServiceHandler())));
shared_ptr<TProcessor> processor2(new SecondServiceProcessor
(shared_ptr<SecondServiceHandler>(new SecondServiceHandler())));

//使用MultiplexedProcessor
shared_ptr<TMultiplexedProcessor> processor(new TMultiplexedProcessor());

//注册各自的Service
processor->registerProcessor(“FirstService”, processor1);
processor->registerProcessor(“SecondService”, processor2);

shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
server.serve();

Client:
shared_ptr<TSocket> transport(new TSocket(“localhost”, 9090));
transport->open();

shared_ptr<TBinaryProtocol> protocol(new TBinaryProtocol(transport));
shared_ptr<TMultiplexedProtocol> mp1(new TMultiplexedProtocol(protocol, “FirstService”));
shared_ptr<FirstServiceClient> service1(new FirstServiceClient(mp1));
shared_ptr<TMultiplexedProtocol> mp2(new TMultiplexedProtocol(protocol, “SecondService”));
shared_ptr<SecondServiceClient> service2(new SecondServiceClient(mp2));

service1->blahBlah();
service2->blahBlah();


## 总结

Thrift通过在客户端采取的decorator模式巧妙的在协议层将ServiceName传递到服务端,服务端通过常见的手段将ServiceName和具体的Processor进行映射,完美的解决了Service的多路复用问题。

## 参考资料

1. http://thrift.apache.org/docs/concepts/

2. https://issues.apache.org/jira/browse/THRIFT-1902

3. https://issues.apache.org/jira/browse/THRIFT-563

4. http://bigdata.impetus.com/whitepaper