大家好,我是老李,为了稳妥先做个声明(以后每篇技术文儿都会引用这个文章前缀):
- 本文没有什么亮点不高端不涉及高性能高并发而且网上一搜一大把发誓文章没有蹭swoole热点最后的末尾有会挂一个微信的广告
- 本文可能会存在错误欢迎公号留言指出或者公正讨论
然后再说下今天的事情是这样,你们先感受下。
一开始的时候这篇文章的标题叫做:
连续研发【附近的人】---swoole love thrift 3000 ci(十)
一开始的时候这篇文章的封面是这样的:
然后我就顺手把这封面图发到群里去了,果不其然,这图发出去后就有了两个被这图误导的大佬提出异议了:
我突然意识到这个封面图和标题会带来比较大的歧义,所以这篇文章的标题和封面图就变成了现在你们所看到的这个样子。
嗯,这样看起来舒服多了。今天主要是说如何将swoole和thrift结合起来使用。其实我写到现在发现了一个问题,那就是好多人都不知道我在写什么,确切说就是为什么莫名其妙地跟thrift死磕起来了。
我决定先聊一聊thrift在一个由众多微服务组成的业务体系中所处的位置以及所起到的作用。由于前面我已经用了三个篇幅来依次讲解了:
巨无霸服务 --> 服务拆分为小服务 --> API在前一坨小服务在后
因为有了这个基础,只要你们仔细看过(最好拉下来代码实践过)就应该很快get到thrift在这种【API在前,众多小服务在后】体系中所起到作用。我想我还是贴一下这张图吧,帮助回忆和加深理解一下:
从上图中可以看到API和后面三个服务均是靠http协议 JSON在飞数据。现在我们打算把momo-user-service这个服务拎出来,通过thrift来和API之间飞数据。
在进行下一步之前,现在得把上一节漏掉的thrift基础得补一下了,不然你会在后面的部分中惨烈阵亡掉并不瞑目。上一篇结尾我提了一句【thrift是一套完整的跨语言RPC解决方案】,这个咋理解呢(我现在还不想提RPC,放到后面再提,会更加合适)?先对thrift整体有个笼统的概念吧,thrift包括了三大块儿内容:
- 数据传输格式:下面有详情介绍,除此之外thrift php库的protocol文件夹里有全部的格式说明
- 数据传输方式:下面有详情介绍,除此之外thrift php库的transport文件夹里有全部的格式说明
- 服务器模型:下面有简要介绍,除此之外thrift php库的server文件夹里有全部php server模型
数据传输格式
- TBinaryProtocol,二进制格式,这是我们一般最常用的格式
- TCompactProtocol,压缩格式,压缩了一把,毫无疑问对CPU应该是有些许伤害的
- TJSONProtocol,JSON格式,不太清楚有没有人用这个的,都已经用thrift还要用文本方式来飞数据
- and so on... ...
数据传输方式
- TBufferedTransport,利用缓存传输当缓存满了才会发送数据
- TSocket,使用socket传输
- TFramedTransport,底层依赖TSocket传输,数据会被分块。我看了一下,thrift php库的数据分块是按照固定包头 包体协议对数据进行的分块,大概意思就是数据包的头部有一个数据信息告诉你本数据多大。补充一下,另外一种数据分块是根据数据末尾特殊分隔符进行数据分块,比如一旦出现rn就算一块儿数据(如果基础好的同学看这里应该一眼就能看不明白,看不明白再说吧,我在往后的篇章迟早要提到的)
- TCurlClient,这个没试过,不过我猜测既然有curl了,八成是依靠http协议咯
- THttpClient,这个没试过,估计一定是按照http协议走了
简单贴一张图你们先理解一下,理解不深刻不重要,后面篇章会详细说,或者你们可以自己搜索一下:
服务器模型
这个整块就是服务器进程(线程)模型,基础好的同学应该一眼能看穿是啥意思,和数据包拆分那里一样,我们也到后面再说,这里的内容太需要专门来说明了,一两行根本说不清的。
- TForkingServer,就是每当收到一个请求,服务器就fork一个子进程去处理这个会话
- TSimpleServer,每当收到一个请求后,服务器就在当前进程里处理这个会话,处理期间不接受其他新的会话请求,还不如上面的TForkingServer呢...
好了,到了此处应该对thrift的整体有个笼统概念了,同时也应该明白为什么我说thrift FOR php的服务器代码没法用了吧?你瞅瞅这两个服务器进程模型,没一个能在生产用的,只能用来做demo。
说到此处,有些老铁可能有点儿反应过来了:“怎么感觉thrift就相当于grpc protobuf啊?“,我一听立马回复:“是啊是啊!幸亏你接过话茬了,不然我都不知道这文章怎么编下去了...”
所以【swoole love thrift 3000 ci】的本质就是,保留并继续使用thrift的数据传输协议以及数据传输方式,但是利用swoole接替thrift的服务器部分。
好了,下面到了比较恶心但是又必须要动手实践的地方:动手把momo-user-server修改为支持thrift,摇身变成swoole-thrift-rpc-server。
定义IDL文件
这里需要简单说下thrift IDL的数据类型,一般常见的有bool、i16、i32、string、double等等,这些基本上一看就知道啥意思;然后得说两个符合格式,一个是struct另一个是map,比如下面这个struct:
代码语言:javascript复制struct User{ 1: string name, 2: i32 age = 0; 3: bool gender }
那么struct对应PHP里的什么数据结构呢?你以为是array然而可能并不是。struct对应PHP的对象,map则对应PHP的array,这些你们都可以亲手试试的。那么我们根据之前momo-user-service的接口来定义一下IDL:
代码语言:javascript复制namespace php Application.User/* 登录接口的入参类型*/struct loginArgu {1:string username;2:string password;}/* 用户信息结构体*/struct userStruct {1:string id; 2:string username;3:string password;4:string createTime;}/*登录和注册接口返回的数据结构*/struct loginResponse {1:string token;2:userStruct user;}/* 定义服务的三个方法login、reg和getuserbyuid 同时定义入参参数 同时定义出参参数*/service UserService {loginResponse login( 1:loginArgu argu ) loginResponse reg( 1:loginArgu argu )userStruct getuserbyuid( 1:i64 uid ) }
生成文件
命令是php --gen php:server user.thrift,将生成的文件和thrift php库放整合到原来的momo-user-service中去,文件结构如下:
重点改动区
最大的改动是什么?就是之前的服务用的http json,说到底是http协议;现在用了thrift的frametransport,这种传输方式利用的tcp socket传输,直接走了tcp协议,还好Ti-RPC一旦启动同时支持http和tcp两种协议。所以改造完毕后,只能通过Ti-RPC的tcp服务写业务逻辑了。
第一处是位于System/Core.php的417到434行,看下:
代码语言:javascript复制public function receive( swoole_server $server, $fd, $fromId, $data ){ $requestId = time().mt_rand( 1, 100000 ); $arg['requestId'] = $requestId; $arg['type'] = 'SW'; $arg['data'] = $data; $arg['fd'] = $fd;/* SW : 单个请求,等待结果 SN : 单个请求,不等待结果 MW : 多个请求,等待结果 MN : 多个请求,不等待结果 */switch( $arg['type'] ){// 单个请求,等待结果case 'SW': $taskId = $server->task( $arg );self::$taskData[$arg['requestId']]['taskKey'][$taskId] = 'single';break;
第二处更重要的改动是index.php文件中第87到100行:
代码语言:javascript复制public function process( $aServer, $aParam ){ // handler就是业务逻辑书写的地方 // 粗暴点儿说,就是控制器~~~~ // 这个文件是你能够改的文件,也是唯一能改的文件 $oHandler = new ApplicationUserUserServiceHandler(); // 这里是服务processer,你可以看一下,但不要改 $oProcessor = new ApplicationUserUserServiceProcessor( $oHandler ); // 这两个,分别是生产tranport和protocol的工厂类 $oTransportFactory = new ThriftFactoryTTransportFactory(); $oProtocolFactory = new ThriftFactoryTBinaryProtocolFactory( true, true );// frame传输方式 // thrift的frametransport就是分块传输,利用的是【包头定长】协议 // 实现的分块。利用的php函数就是pack函数 $oFrameTransport = new ThriftTransportTiFramedTransport(); $oFrameTransport->buffer = $aParam['data']; $oFrameTransport->server = $aServer; $oFrameTransport->fd = $aParam['fd']; $oProtocol = new ThriftProtocolTBinaryProtocol( $oFrameTransport, false, false ); $oProcessor->process( $oProtocol, $oProtocol );return []; }
详细解说下上面代码的第14行,表示利用了数据分块传输方式,同时看第18行,数据传输序列化是二进制。你们可以看下这个类,其利用的是【包头包体】实现的分块,利用pack函数实现的数据二进制序列化。这里有一堆著名的PHP函数pack/unpack,这两个函数恐怕不少人都不知道啥意思,就是那种即便是看完文档也是一脸懵逼的那种,其实你粗暴理解就是二进制序列化函数(这个地方还需要接触的概念为Little-Endian & Big-Endian),这里如果实在觉得恶心可以先跳过,后面篇章我还是会写的。
我再提一次,这些都是基础知识,也就是平时我们口中所吐槽的面试造火箭的知识点,现在说如果你想更加深入理解thrift,如果不知道这些造火箭的点,感觉到吃力了吗?
接着说不能断要连续,thrift的分块传输利用的是pack打包数据,N表示使用Big-Endian;所以,服务端也要遵守同一种拆数据的规则。我可以给你们看下thrift的frametransport里打包数据的代码是这样的:
代码语言:javascript复制$out = pack('N', TStringFuncFactory::create()->strlen($this->wBuf_));
而在swoole里,直接在配置项里配置即可:
客户端代码
客户端代码就比较简单了,我贴关键部分代码你们凑合感受下:
代码语言:javascript复制 $transport = new TFramedTransport( new TSocket( '127.0.0.1', 6667 ) ); $protocol = new TBinaryProtocol( $transport ); $argu = new loginArgu( array( 'username' => 'test','password' => '123456', ) ); $client = new UserServiceClient( $protocol ); $transport->open();// 同步方式进行交互//$recv = $client->login( $argu );//print_r( $recv ); $recv = $client->getuserbyuid( 352 ); print_r( $recv ); //echo "收到服务器返回:".$recv.PHP_EOL; $transport->close();
测试一下
你们准备好了吗?我要启动momo-thrift-user-service了!输入php index.php start回车走起!
客户端来一把,完美返回数据,有没有?!完美!
然而到这里我还要告诉你,除了比利用http json飞数据性能更加好之外,我们有个意外收获:就是PHP返回数据类型和字段终于不会随着我们手残而无意中发生改变或丢失了。众所周知,PHP是弱语言类型,我们给APP端返回数据的时候一般都是json_encode()一个PHP数组,PHP数组是一种非常随意的数据结构非常灵活,灵活就导致不知道什么时候一个手残就导致返回给APP端少了某个字段或者更改了某个字段的数据类型导致APP闪退等,现在有了thrift,只要你动手实践一下,你就感受到这个意外收获。
老规矩哈,代码下载地址在这里(不过先说好了,如果你没有看之前的篇章这个服务你应该不会流利地跑起来的,文章是一篇接一篇的):
http://t.ti-node.com/static/momo/momo-user-thrift-service.tar.gz
文章最后了,和一起坚持过来认真看并仔细实践自己默默搜索查资料的同学再画一张整改过后的架构图: