腾讯与阅文技术合做 微服务框架Tars再添PHP

梁晨(Ted),任职阅文集团技术中心,负责起点中文网的WEB后台开发工做。曾负责腾讯上海企业产品部营销QQWeb后台开发、QQ公众号Web后台开发,对大型网站技术架构,有本身的经验和看法。腾讯开源项目TSF2.0框架开发者,腾讯开源组件Tars-PHP开发者,也曾是腾讯公司多个PHP扩展组件的开发者与维护者。php

引言

TARS做为由腾讯公司开源的优秀RPC框架与服务部署运维解决方案,被阅文集团引入了实际实践中,同时阅文集团对TARS在PHP语言层面进行了能力的补全,令TARS如虎添翼。TARS-PHP的解决方案兼具简单高效、接口维护方便容易扩展、代码自动生成,以及集成寻址、服务发现、监控、上报等功能。经历了阅文集团线上业务的考验与洗礼,充分证实了该解决方案的优点。前端

项目地址:github.com/Tencent/Tar… git

"PHP是世界上最好的语言"

众所周知,在PHP诞生之初,就是WEB站点的开发而生。可是一直以来,都没法摆脱弱类型、脚本语言的性能之殇的帽子。随着互联网行业的不断发展,以及用户需求和基础架构的不断变化,PHP语言自己也一直在发展。不管是SWOOLE的出现,仍是PHP7对性能的提高,都丰富和助力了PHP自己的应用。github

相信你们在开发中也会发现,做为常常处在WEB中间层的PHP,其实有不少的痛点。既要接收前端的HTTP请求,又要调用各式各样的后台服务与存储服务,经常成为一个站点的性能瓶颈。其中HTTP协议的过度冗余以及上层封装带来的损耗,就是一个比较突出的问题。sql

开发者不但要应对使用同步的HTTP的调用库所带来的吞吐量的降低,还要忍受HTTP协议自己,以及JSON、XML协议在信息传输上的低效率。为了解决这一问题,一套在TCP协议层的,使用简单的二进制协议。才能保证业务用更少的传输带宽,承载更多的传输内容,从而提升吞吐量和WEB服务伺服能力。后端

同时,在实际开发的层面上,PHP逻辑层与后台服务之间通讯协议的维护成本较高。同时,后台服务侧新增或修改接口字段,每每调用侧也要配合修改,不少时候没法保证接口的彻底兼容而引起线上的运营问题。所以,这种二进制协议又要作到接口方便维护,同时又容易扩展。数组

除此以外,从开发效率上而言,本来的开发中老是包含大量的重复的,但又不得不去作的工做内容。由于每一次新协议的开发,代码很难复用,JSON和XML也并不容许你共用部分数据。同时一个很现实的问题是,不一样HTTP接口的提供方,每每会视本身的心情和习惯来定义接口。bash

一个常见的例子就是对返回码的定义,有些人叫ret,有些人叫code,还有些人就叫r,简直是无所不包。所以这类重复无趣的开发工做,给调用方的开发同窗带来了极大的生理和心理负担。基于这种需求,一种服务端和客户端都可以根据协议和接口自动生成调用代码,保证联调通畅的解决方案必不可少。服务器

再者,调用方对后端服务的发现和调用的上报与监控,也是一个老生常谈的问题。后端服务如何被发现,后端的接口如何被发现,这都是调用方真真切切想知道的。同时,调用方很是有必要对后端服务的调用状况进行上报到中央服务器,中央服务器再根据收集上来的信息,对后端服务的负载进行动态的调整,保证服务的高可用。要实现这样的需求,必须引入一种集成了监控、主控寻址、上报通道、负载均衡功能的解决方案。网络

Tars做为腾讯公司的优秀RPC框架与服务部署运维解决方案,能够知足上述的全部需求。经过引入Tars-PHP的全套解决方案,开发者既可使用二进制的Tars协议,大大压缩了服务请求的流量。同时也可以借助Tars协议解析的PHP扩展,提升了打包解包的性能进而提高了单进程的任务处理能力。再次,自动生成代码的工具也可以提高开发者的效率。

Tars-PHP解决方案

Tars-PHP的开源方案,首先从二进制的协议提及:

二进制协议

HTTP协议多是在应用层上使用最为普遍的协议了。现有HTTP的版本主要是1.0和1.1版本。它在TCP协议的基础上作了十分简洁的应用层协议封装,纯文本的内容,以及Header和Body的区分。都使得这种协议的使用和理解十分的方便。可是不可避免的,使用和阅读的简单意味着信息的冗余,为了传输少许的内容,每每须要耗费大量的流量。

另外两个比较熟知的协议,就是JSON和XML了,这两位在API交互经常使用的协议中不分上下,可读性强、容易理解、语言客户端支持丰富、协议表述能力突出,都是二者的优点所在。先看看一样一段信息,二者须要的数据量。

假定有一所学校,一个学生,若是用JSON标识的话,以下所示:

{
    "school":
    {
        "student":{
            "name":"ted",
            "age":18,
            "degree":"master"
        }
    }
}复制代码

很简单的结构,共须要65个字符来表述。而若是换成XML:

<school>
        <student>
            <name>ted</name>
            <age>18</age>
            <degree>master</degree>
        </student>
</school>复制代码

则一共须要92个字符。从信息学的角度而言,信息熵明显就是过低了。因此为了实现通讯的更高性能和更少带宽的使用,二进制协议的引入势在必行。

Tars协议做为一个二进制的协议,相比于上述两个协议的优点不言自明。从上文中的JSON和XML中发现其灵活性,也就是没有指定字段的类型。可是不可避免的,这种灵活带来了性能的大损失。所以Tars定义了八种基本的数据类型,经过对不一样的数据类型进行编码优化:

bool、byte、short、int、long、float、double 、string复制代码

而同时为了知足业务需求,扩展出了struct(包含任意字段)、vector(数组)、map(key-value结构)这三种能够嵌套数据,丰富协议表现力的复杂类型。

按照上文的表现结构,几个struct就能够完成。
首先是student结构体:

struct student {
    0 required string name; // tag为0,type为string,实际数据为ted,共5个字节
    1 required byte age; // tag为1,type为short,实际数据为18, 共2个字节
    2 required string degree; // tag为2,type为string,实际数据为master,共7个字节
}复制代码

从注释中能够看到,三个字段须要的字节数为14,再加上结构体的开始和结构体结束的标识共2个字节,一共只须要16个字节而已。相比之下,这仅仅是JSON的1/4,是XML协议标识一样信息的1/5,高下立判. 巧妙地用协议强约定换传输可读性,这就是高信息熵的二进制协议的诀窍。

为了使得PHP可以充分与Tars结合,必须使其具有做为客户端和做为服务端两个方面的能力。

Tars-PHP的客户端

做为客户端而言,要可以知足快速开发的需求,也要可以与PHP现有的常见使用方式相结合,同时还要给出远程调用的实例。基于这些需求,客户端方案中实现了以下的特性:

  • 实现了用TUP协议进行打包解包、编码解码的PHP扩展及相应的测试用例;
  • 实现了从Tars协议文件生成对应PHP类文件的tars2php工具;
  • 实现了包含网络库的二次封装,以及远程调用的代码示例;

做为客户端实现的最核心一步,就是对TUP协议的支持。TUP协议是在Tars协议的上层,经过固定的数据结构封装一些收发包必须的信息,如返回值、输入输出参数、包自己的状态、包计数等,来给非Tars原生客户端与Tars服务端进行通讯的协议。Tars-PHP在支持TUP协议的方案中,选择了使用PHP扩展做为实现方式。

PHP语言自己被诟病最多的,就是针对CPU密集型的运算的低效率。因为并不十分高效的ZEND虚拟机、松散的数据结构和弱类型的存在,使得打包、解包这类CPU密集型的效率低下。所以,PHP扩展应运而生。经过引入高性能的C/C++类库和一些原生的C/C++实现,使得PHP在性能处理方面迎头遇上。这也就是以扩展的方式实现打包解包主逻辑的初衷。

首先来看看PHP5x语言的结构:

最底层的Server API用来PHP与Webserver通讯,这个主要是以前与APACHE配合须要使用的。在其左上的PHPCORE层,是为了提供最基本的文件和网络操做的能力。而右上的ZEND,则是用来把PHP的脚本语言编译成机器码的工具。最上面就是扩展层了,这层会充分利用ZEND的API和PHPCORE的能力,直接写出ZEND可以高效执行和理解的代码,省去了PHP脚本编译为机器码的过程,从而大大的提升执行的效率。

若是要设计这个扩展,必需要将上文中Tars的数据结构经过C语言的方式加以表达,同时设计出基于这套数据结构的编码器与解码器。另外一个须要考虑的方面是,必需要使得在PHP层面尽量的简单、易用,这就对扩展的设计提出了比较高的挑战。一方面要兼顾性能,另外一方面,要将Tars协议中的Struct,进行了PHP中的Class的表达:

从图中能够清晰的看到,结构体SimpleStruct被分解成了三个部分:

  • TAG部分
  • 成员变量部分
  • 变量描述的fields

TAG部分相当重要,这部分用来表明Struct中每一个元素的TAG值。这也是实际进行TUP编码和解码的时候,二进制包里面最终包含的内容。为何要有TAG?这是由于相比于JSON里面对字段的文本性质的描述,TAG自己更节省空间。

第二部分则是类的成员变量,这部分红员变量和Tars协议的Struct中的变量一一对应。这是为了承载对应变量的实际值而存在的。借此才能对真正的数据进行打包和解包。

为了在TAG和变量之间搭起一座桥梁,就有了第三部分:Fields部分。这部分是TAG与其对应的变量属性的一个映射。包含了变量的名称、变量是否必填以及变量的类型。经过这些信息,一方面实现了对Tars协议的二进制编码,也实现了解码时候的映射。可谓一箭双雕。

那么通过复杂的扩展设计与实现,有必要将扩展实现的打包解包性能和原生PHP实现的打包解包性能进行比对。从下面的表格中能够很是明显的看出扩展实现拥有性能上面的绝对优点:

方式/100次 tars复杂度 打包时间(ms) 打包耗时倍数 解包时间(ms) 解包耗时倍数
扩展 简单 0.69 1 1.18 1
php原生 简单 11.25 16 16.28 13
扩展 复杂 1.17 1 1.55 1
php原生 复杂 14.5 12 15.1 10

从这个表格中能够很是清晰的看到,不管是简单的Tars协议,仍是复杂的Tars协议,使用扩展进行打包解包都比原生PHP的性能提升十倍以上。当遇到复杂的业务逻辑,须要调用大量的使用Tars协议的后台服务的时候,这种效率的提高会让服务的吞吐量上一个数量级。

开发者在完成扩展的编译工做以后,就能够很是方便的使用TUP协议进行打包,解包与编码解码的工做了。

// 针对基本类型的打包和解包的方法,输出二进制buf
$buf = \TASAPI::put*($name, $value);
$value = \TUPAPI::get*($name, $buf);

// 针对Struct,传输对象,返回结果的时候,以数组的方式返回,其元素与类的成员变量一一对应
$buf = \TUPAPI::putStruct($name, $clazz);
$result = \TUPAPI::getStruct($name, $clazz, $buf);

// 针对Vector,传入完成pushBack的Vector
$buf = \TUPAPI::putVector($name, TARS_Vector $clazz);
$value = \TUPAPI::getVector($name, TARS_Vector $clazz, $buf);

// 针对Map,传入完成pushBack的Map
$buf = \TUPAPI::putMap($name, TARS_Map $clazz);
$value = \TUPAPI::getMap($name, TARS_Map $clazz, $buf);

// 须要将上述打好包的数据放在一块儿用来编码
$inbuf_arr[$name] = $buf;
// 进行tup协议的编码,返回结果能够用来传输、持久化
$reqBuffer = \TUPAPI::encode(
                         $iVersion=3,
                         $iRequestId,
                         $servantName,
                         $funcName,
                         $cPacketType=0,
                         $iMessageType=0,
                         $iTimeout,
                         $context=[],
                         $statuses=[],
                         $inbuf_arr);
// 进行tup协议的解码
$ret = \TUPAPI::decode($respBuffer);
$code = $ret['code'];
$msg = $ret['msg'];
$buf = $ret['sBuffer'];复制代码

为了方便开发者扩展使用中常常遇到的没法找到具体函数和参数的问题,同时提供了tars-ide-helper:

以PHPSTORM为例,只须要导入到相应的INCLUDE路径中,就能够实现自动提示了:

除了打包解包的能力,Tars-PHP同时也提供了网络收发的能力,网络收发主要实现了如下几个点:

  • TarsAssistant.php文件:经过COMPOSER加载,底层内置SOCKET原生网络层收发包实现;
  • 根据Interface自动生成PHP的Class,与TarsAssistant无缝结合
  • 提供Exception等容错处理;

一旦完成了代码的自动生成以后,使用者便可经过以下代码,方便的进行远程Tars服务调用:

require_once "./vendor/autoload.php";

    $ip = "";// taf服务ip
    $port = 0;// taf服务端口
    $servant = new App\Server\Servant\servant($ip,$port);

    $in1 = "test";
    $ss1 = new SimpleStruct();
    $ss1->id = 1;
    $ss1->count = 2;
    $ss1->page = 3;

    try {
        $intVal = $servant->singleParam($in1,$ss1,$out1);
    }
    catch(phptars\TarsException $e) {
        // 错误处理
    }复制代码

Tars-PHP的服务端

除了建设Tars-PHP做为客户端的能力以外,服务端的能力一样是必不可少的。为了可以知足不一样业务场景下的需求,Tars-PHP在服务端主要会关注两类服务。

第一类是HTTP的服务,会以SWOOLE2.0为网络收发的基础,实现一套高性能、简洁好用的面向WEB服务的框架。这套框架会支持基本的 路由、中间件、MVC架构等常见的WEB框架特性。同时也会集成Redis、Mysql、Http、Multicall、Tars等常见的客户端,方便WEB服务再去调用后台服务。更重要的是,接入到Tars平台中,使得服务可监控,可重启,享受Tars运维平台带来的一站式便利。如今框架的第一个版本已经实现,并在阅文集团内部上线使用,测试成熟后,会及时进行开源。

第二类则是TCP的服务,一样底层依赖于SWOOLE2.0,可是协议从HTTP换成了对TUP和Tars的支持。框架实现上而言,会与JAVA、C++的服务端保持一致,底层集成网络能力,使用者只需关心服务名称以及接口参数和本身的业务处理逻辑而已。固然,这个服务确定也是要与Tars运维平台相结合的。如今框架对TUP协议支持的第一个版本已经完成,后续也会在完成Tars协议的底层支持以后,在业务上进行使用和验证。

业务实践

阅文集团在进行后台服务治理与改造的过程当中,使用了Tars-PHP的解决方案。一方面,全部WEB后台与后台服务的接口,所有从原有的HTTP接口,切换为了基于Tars协议的TCP网络传输。依赖于Tars-PHP的自动代码生成,开发效率提高巨大,保证了项目的顺利按时上线。同时,这套基于PHP扩展的方案,也保证了代码执行效率的高效,单个请求的处理时间,相比于原有的HTTP接口调用,获得了显著的缩短。

另外一方面,因为使用的WEB后台服务是常驻内存的,基于SWOOLE的实现。因此在发布、启动、监控等方面与原有PHP中固有的Apache和PHP-FPM的方式都不相同。所以,正如上文中所说,服务接入Tars平台,享受其监控、保活、日志等一系列的功能,会大大提升服务自己的运维和扩容的便利性。现在在其线上服务中,超过十个服务已经切入并稳定运行了接入到Tars平台的HTTP服务。这些服务的发布、扩容和运维彻底依赖Tars平台,十分便利。

除去对Tars平台运维的使用,阅文WEB后台侧一样在服务发现上,有一套方案。

对于远程服务的地址管理,最差的方案就是将其写入本地文件中。这种方案没法应对快速缩扩容以及服务器下线的需求,会给后续的运维带来很大的工做量。

稍微好一些的方案是本地存储虚拟IP,那么每次只须要调整虚拟IP,就能够实现服务地址的自动映射和变化。可是这意味着对要调用的每个后台服务,都须要存储其对应的虚拟IP、HOST信息、接口信息等一系列的信息,一样维护成本很高。

而更加通用的方案,则是提供服务的统一配置中心,每次须要调用后台服务的时候,就从配置中心根据惟一的标识拉取出服务最新的地址。这样一方面可以作到缩扩容对业务的无感知,另外一方面配置中心也可以经过服务的寻址状况,给每一个客户端分配最适合它的服务机器地址,好比机房或者SET就近分配等等。本地的服务只须要提供两个能力,第一个是可以调用按期的寻址服务,并存入本机的存储中,保证寻址的速度。第二个则是可以接收配置中心下发的命令,更新特定服务的地址。能作到这两点,就可以实现高效的寻址和可靠的寻址了。

在实际使用中,结合实际业务状况,一方面每分钟向主控请求一次服务的地址,经过轮询的方式获取一个可用的服务地址,再放入本地的高速共享内存,方便在这一分钟以内重复的读取。另外一方面在每次服务调用的时候,都自动在底层集成对服务调用状况的耗时、成功率的上报。在左右开弓的做用之下,对远程服务的调用再也不像过去那样难以维护、难以开发、难以监控,而是清晰可见高效的被管理。

结语

从开发效率上而言,使用Tars-PHP摆脱了过度冗余的业务代码,以自动生成的方式提升代码开发自动化程度。

从性能方面而言,Tars-PHP方案经过引入扩展,作到了性能的大幅度提高,让性能再也不成为PHP“之殇”。

从易用性而言,经过提供TarsAssistant的网络收发组件,使得收发包无需单独实现。后面也会引入更高性能的Swoole做为socket收发的利器,进一步提升网络性能。

后续,Tars-PHP的SERVER侧方案也会尽快开源,从而可以提供一套包含客户端与服务端的完整解决方案。这一整套的WEB后台的Tars-PHP开发体系,可以真正作到了高性能、高效率与高可用。而阅文集团也会继续与腾讯在Tars-PHP技术方案上深度合做与实践。欢迎开发者试用!

相关文章
相关标签/搜索