网络库Tbnet及其应用分析
2017-01-19李艳,张玲,胡术,李璞,潘倩
李 艳,张 玲,胡 术,李 璞,潘 倩
(1. 四川大学 计算机学院,四川 成都 610064;2. 四川大学 国家空管自动化系统技术重点实验室,四川 成都610064;3. 四川大学 计算机基础教学实验中心,四川 成都 610064)
网络库Tbnet及其应用分析
李 艳1,2,张 玲3,胡 术1,2,李 璞1,2,潘 倩1,2
(1. 四川大学 计算机学院,四川 成都 610064;2. 四川大学 国家空管自动化系统技术重点实验室,四川 成都610064;3. 四川大学 计算机基础教学实验中心,四川 成都 610064)
Tbnet采用生产者-消费者队列模型,具有附带回应的报文发送机制,对外提供类库型的接口,应用具有多样性。研究了淘宝开源网络库Tbnet的核心设计实现、多样化使用,内容包括Tbnet主要类及其类间关系,客户端与服务端间的连接通信过程,以OceanBase早期版本为代表的淘宝分布式产品对Tbnet使用的分析,以及该库向Windows平台的移植工作。
输入/输出线程;工作线程;生产者-消费者
0 引言
Tbnet广泛应用于TFS、Tair等开源分布式系统,不同于仅提供数据传输的传统网络库,该库为客户端/服务端提供交互式通信,即客户端发出请求,服务端接收、处理并予以回应,适用于分布式系统开发。Tbnet采用对象语义进行类的设计并大量使用类的继承,实际使用时需继承IPacketHandler、IServerAdaptor等接口类,重写类中虚函数。Tbnet内部由一个处理事件的输入/输出线程、一个超时检查线程及工作线程池组成,I/O线程与工作线程间采用单生产者-多消费者模式[1]共享加锁报文列表。本文分析了Tbnet主要类及其消息通信,对比分析了分布式产品使用Tbnet的差异性。
1 内部实现解析
Tbnet对用户提供库类型接口,其使用方式呈多样化。通过不同方式调用Transport类对象,可运行为客户端或服务端。为与多个服务端连接通信,客户端主线程调用ConnectionManager类指针与由本机IP地址及其端口号转换的无符号64位整型ipport提供的serverId标识的服务端建立连接。客户端与服务端分别继承IPacketHandler、采用对象适配器模式[2]的IServerAdaptor类,分别重写handlePacket()实现报文处理流程。因继承的类接口不同,服务端在I/O线程或工作线程处理报文,客户端则在I/O线程处理报文。
1.1 主要类
1.1.1 线程相关类
Tbnet采用I/O线程+工作线程池的模型[3],其线程的实现主要通过Runnable、DefaultRunnable以及Channel类完成,其中DefaultRunnable继承自Runnable,内部组合CThread类。Transport类继承自Runnable,创建一个I/O线程和一个超时检查线程,线程内部包含至多一个eventLoop处理事件;而继承自DefaultRunnable的PacketQueueThread类则创建工作线程池。
1.1.2 IOComponent相关类
Socket类封装套接字及其操作函数,SocketEvent类封装epoll机制[4],而IOComponent类包含Socket类指针,设置SocketEvent类指针、引用计数及回调函数等。TCPAcceptor类继承自IOComponent,完成类似Acceptor接收器功能,服务端根据TCPAcceptor类指针触发读事件执行accept()生成已连接套接字的Socket来创建TCPComponent类指针。TCPComponent继承自IOComponent类,TCPComponent根据init函数传入的参数来判断选择客户端或服务端,作为客户端发起非阻塞连接,服务端则代表连接成功。
1.1.3 Channel相关类
为跟踪各报文交互式通信,Tbnet使用Channel进行抽象,通过对客户端Packet设置信道ID,服务端回应Packet也使用该ID,可确保请求与回应一一对应。客户端处理回应报文的IPacketHandler类指针定义在Channel,实现了客户端发送和接受处理报文的异步化,Channel机制也为用户带来更多可能性。因定义在主机间正在使用的Channel数量有限,为避免不断申请Channel造成内存碎片,客户端发送请求报文需从ChannelPool中获取一个空闲Channel,客户端收到回应报文后将该Channel取出并放回ChannelPool。
1.1.4 Transport相关类
Transport类封装监听与连接,创建Socket、IOComponent类指针,设置Socket超时检查。主线程回调Socket设置IP地址、端口等信息;回调IOComponent创建套接字、设置套接字选项以及监听或连接。因TCPComponent内部创建了用于报文收发的TCPConnection类指针,Transport类创建的I/O线程在EPollSocketEvent类对象的驱动下,回调IOComponent进行事件处理。超时线程遍历检查存储在vector中的内部正在使用的超时IOComponent类指针,再检查内部发生错误已移入删除列表的超时或引用计数≤ -10的IOComponent类指针。
1.1.5 ConnectionManager相关类
ConnectionManager类包含Transport类指针,客户端首次与指定服务端建立TCP连接时采用长连接复用的ConnectionManager类指针,调用Transport类指针建立非阻塞连接,将key为对应服务端序号(serverId)、value为主机间连接的Connection类指针插入map;待双方再次连接,客户端主线程不再调用Transport建立TCP连接,而是在map中根据serverId查找Connection类指针,通过该Connection将Packet压入发送队列,等待I/O线程触发写事件。
1.2 对象生命周期管理设计
Tbnet中类继承、类间相互操作,导致较多类对象的创建与回收不在自身类中。主线程创建Socket类指针,并根据Socket创建IOComponent类指针。待I/O线程不再调用Socket及IOComponent时,主线程IOComponent类的析构函数回收Socket,Transport类的析构函数回收在用链表及删除链表中的IOComponent指针对象。服务端主线程创建的TCPAcceptor类指针,客户端主线程以及服务端I/O线程分别创建的TCPComponent类指针,主机I/O线程通过原子计数修改这些类指针的引用计数,待引用计数≤-10时,回收该指针。TCPComponent内部创建TCPConnection类指针,其析构函数回收该类指针。
1.3 消息通信
客户端与服务端使用单I/O线程+多线程模型即半同步半异步模式[5],主机间通信数据流程如图1所示。
图1 Tbnet工作线程池处理与回应消息通信数据流程图
客户端主线程使用工厂类IPacketFactory,根据数据包类型创建Packet类指针,设置Packet超时时间,将分配的Channel信道ID设置到Packet头部,再将Packet依次压入加锁的发送队列,见步骤1~2;I/O线程将Packet从发送队列移至临时队列,依次取出Packet并调用IPacketStreamer类指针将Packet二进制转换,Packet组装后放入输出缓存,直至临时队列为空或输出缓存可写空间尚未超过阈值,使用send函数发送请求报文直至输出缓存为空或发送次数超过10次,见步骤4。服务端I/O线程使用recv函数接收请求报文至空间足够的输入缓存,调用IPacketStreamer类指针解析报文,获取packet并放入加锁的报文队列,见步骤6;工作线程从报文队列取出并处理Packet,接着回调Connection将设置了超时时间、信道ID的回应Packet压入发送队列,见步骤7~8;服务端I/O线程触发写事件,从发送队列中取出Packet,组装并发送该回应报文,见步骤9;客户端I/O线程触发读事件,接收并处理报文,见步骤10。
2 应用分析
相较于传统的提供收发功能的网络库,面向对象的Tbnet不仅在使用上存在较大差异且不能直接使用,开发人员需继承多个接口类,重写成员函数,还需将其他类如Transport实例化并通过编码有机结合,方可实现通信功能。基于Tbnet的实现呈多样化,本节将介绍两个典型的应用:Tbnet自带示例代码和OceanBaseV0.3中的应用。
2.1 示例代码
Tbnet提供一个简单的报文通信反射示例[6],包含客户端EChoClient与服务端EChoServer两个进程。客户端/服务端主线程使用Transport对象建立TCP连接、开启I/O线程及超时检查线程、等待线程结束[7]。示例服务端无工作线程池,主机间消息通信流程如图2所示。
图2 Tbnet I/O线程处理与回应消息通信数据流程图
客户端主线程分配内存,创建字符串类型的Packet类指针,将附带信道ID的请求Packet压入加锁的发送队列,见步骤1~2;I/O线程从发送队列中移出并组装Packet,发送请求报文,见步骤4;服务端I/O线程接收并解析请求报文获取请求Packet,将请求Packet内容直接复制拷贝到回应Packet并设置对应信道ID,再将其压入发送队列,组装Packet并发送回应报文,见步骤6~7;客户端I/O线程接收回应报文,确认收发数据包数目是否相同,接收完毕关闭I/O线程,销毁I/O组件,删除回应数据包,见步骤8。
2.2 OceanBase中的使用
OceanBase[8]作为分布式关系数据库,其0.4之前版本均使用Tbnet,除常规使用,通过添加WaitObject等待对象机制,客户端可在主线程阻塞等待回应报文。OceanBase采用I/O线程+工作线程池模型[9],主机间网络通信数据流程如图3所示。
图3 WaitObject消息通信数据流程图
客户端主线程创建含有回应报文序号(seq_id)的WaitObject类指针,见步骤1;数据包设置附有seq_id的Channel信道ID并将其压入加锁发送队列,WaitObject在主线程阻塞等待回应报文,见步骤2~3;客户端I/O线程向服务端发送从发送队列移出并组装的请求报文,见步骤5;服务端I/O线程接收并解析报文获取请求Packet,将其放入加锁的报文列表,见步骤6;服务端工作线程获取报文并处理,处理完成后工作线程将带有seq_id的回应Packet压入发送队列,见步骤7~8;服务端I/O线程组装并发送回应报文,见步骤9;客户端I/O线程接收回应报文并处理,若报文带有seq_id,则从map中查找对应WaitObject并将回应报文放入其中,利用条件变量唤醒阻塞等待的主线程;客户端主线程从WaitObject中获取回应报文,见步骤10~11。
3 向Windows平台的移植实现
Tbnet基于Linux平台实现,为便于使用,需向Windows平台移植。移植主要思路:(1)Tbnet依赖淘宝另一开源实现Tbsys,即对系统级函数的封装,跨平台移植需先替换Tbsys中的平台相关部分;(2)替换Tbnet中I/O复用模型调用的epoll机制。
3.1 Tbsys的移植
开源分布式协调系统Zookeeper的C语言客户端的“winport.cpp”文件[10],其包含基于Windows函数实现的常见POSIX语义的线程函数,包括线程创建、回收、互斥锁、自旋锁、读写锁、条件变量等[11],也加载了socket动态链接库。该实现相对较轻量,满足Tbnet移植要求,极大地减少了移植工作量。应用于IOComponent类指针的引用计数通过Tbsys原子计数实现,Tbsys使用AT&T嵌入式汇编实现32位整数的原子操作。在Windows平台,需使用Interlocked系列函数来替换Tbsys中atomic类的相关函数,以此实现自动增加、减少及比较替换功能。
3.2 事件处理机制
Tbnet中EPollSocketEvent类采用epoll机制实现I/O复用模型,移植采用select机制,通过继承SocketEvent实现。epoll机制返回事件触发的套接字,epoll_ctl()关注事件,epoll_wait()获取激活事件;select机制获取被触发的套接字个数后,需遍历整个套接字数组查找事件触发的套接字。移植中,为操作方便,封装的SelectSocketEvent类内部包含key为Socket类指针、value为Socket关联的IOComponent类指针的map,考虑到跨线程操作map,使用互斥锁机制。select处理流程调整为:(1)遍历map将套接字描述符关注事件加入读描述符集、写描述符集;(2)有限时间内执行select函数,再次遍历map,将map中迭代器指向的已触发事件的value设置到根据标识判断触发了读或写事件的IOEvent类指针的成员变量IOComponent类指针中,返回触发事件数目。
4 结论
作为支持交互式通信的网络库,Tbnet使用时需注意:一个Transport仅有一个I/O线程,对于连接多、吞吐量大的应用存在瓶颈[12];因服务端使用工作线程池处理请求报文,导致报文的处理顺序、回应报文的发送顺序及其接收顺序不一致,对同一客户端发出的报文也存在相同情况,该乱序会导致应用层面问题,如先进先出;服务端对请求报文的处理既可在I/O线程也可在工作线程中进行,客户端则在I/O线程中处理回应报文,这种将应用层的处理放入线程的做法,通常存在线程不安全问题,需附加队列等处理机制。
[1] BEVERIDGE J. Win32 多线程程序设计[M]. 侯捷,译.武汉:华中科技大学出版社,2002.
[2] (美)沙洛韦,(美)特罗特.设计模式精解[M]. 熊节,译.北京:清华大学出版社,2004.
[3] 陈硕. Linux多线程服务端编程:使用muduo C++网络库[M]. 北京:电子工业出版社,2013.
[4] STEVENS W R, FENNER B,RUDOFF A M. Unix网络编程卷1:套接字联网API(第3版)[M]. 北京:人民邮电出版社,2015.
[5] SCHMIDT D C,STAL M, ROHNERT H, et al.面向模式的软件体系结构 卷2:用于并发和网络化对象的模式[M]. 张志祥,任雄伟,肖斌,等,译.北京:机械工业出版社,2003.
[6] Mao Qi.Tbnet开源[EB/OL]. (2013-11-xx)[2015-12-25].http://code.taobao.org/svn/tb-common-utils/.
[7] STEVENS W R. UNIX网络编程卷2:进程间通信(第2版)[M]. 杨继张,译.北京:人民邮电出版社,2010.
[8] OceanBase开源[EB/OL]. (2014-04-xx)[2016-04-02].http://code.taobao.org/svn/OceanBase/.
[9]黄贵,庄明强. OceanBase分布式存储引擎[J]. 华东师范大学学报(自然科学版),2014(5):164-173.
[10] The Apache Software Foundation. Zookeeper开源[EB/OL].(2016-01-xx)[2016-05-02].http://www-us.apache.org/dist/zookeeper/.
[11] BUTENHOF D R. POSIX多线程程序设计 [M]. 丁磊,曾刚,译.北京:中国电力出版社,2003.
[12] 杨传辉. 大规模分布式存储系统:原理解析和架构实战[M]. 北京:机械工业出版社,2013.
Network library Tbnet and its application
Li Yan1,2,Zhang Ling3,Hu Shu1,2,Li Pu1,2,Pan Qian1,2
(1.College of Computer Science, Sichuan University, Chengdu 610064, China; 2.State Key Laboratory of ATC Automation System Technology,Sichuan University, Chengdu 610064, China; 3. Computer Teaching Experiment Center, Sichuan University, Chengdu 610064, China)
Tbnet uses the queue model of producer-consumer, it can wait for the response when send packet, provide the interface of library type for user, and its application is diversity. This paper studies the core design and implementation of the Taobao open source network library Tbnet. Including inter main classes and their relations in Tbnet, the communication process between the client and the server, an analysis of the use of earlier versions of OceanBase represented by Taobao distributed products on the Tbnet, and the transplantation of the library to Windows platform.
input/output thread; worker thread;producer-consumer
TP393.1
A
10.19358/j.issn.1674- 7720.2017.01.020
李艳,张玲,胡术,等. 网络库Tbnet及其应用分析[J].微型机与应用,2017,36(1):66-68,72.
2016-08-14)
李艳(1992-),女,硕士研究生,主要研究方向:计算机网络,分布式系统理论。
张玲(1964-),女,学士,实验师,主要研究方向:计算机应用,仿真与网络。
胡术(1972-),通信作者,男,博士,副教授,主要研究方向:计算机网络,操作系统及中间件。E-mail:hushu20050327@163.com。