LiangChen(Ted),就职于阅文集团技术中心,负责起点中文网WEB后台开发。曾负责腾讯上海企业产品部营销QQWeb后台开发和QQ公众号Web后台开发。对大型网站的技术架构有自己的经验和见解。腾讯开源项目TSF2.0框架的开发者,腾讯开源组件Tars-PHP的开发者,腾讯多个PHP扩展组件的开发者和维护者。TARS作为腾讯开源的一款优秀的RPC框架和服务部署运维解决方案,已经被阅文集团引入到实践中。TARS-PHP的解决方案简单高效,易于维护和扩展接口,代码自动生成,并集成了寻址、服务发现、监控、报告等功能。经历了阅文在线业务的考验和洗礼,充分证明了该方案的优势。项目地址:https://github.com/Tencent/Ta...《PHP是世界上最好的语言》众所周知,PHP诞生之初,是开发??WEB站点的。但长期以来,它始终无法摆脱弱类型和脚本语言性能的帽子。随着互联网行业的不断发展,以及用户需求和基础设施的不断变化,PHP语言本身也在不断发展。无论是SWOOLE的出现,还是PHP7的性能提升,都丰富和帮助了PHP本身的应用。相信大家也会发现,在开发过程中,往往处于WEB中间层的PHP其实也有很多痛点。需要接收前端的HTTP请求,调用各种后台服务和存储服务,往往成为站点的性能瓶颈。其中,HTTP协议的冗余过多和上层封装造成的丢失是一个比较突出的问题。开发者不仅要应对使用同步HTTP调用库带来的吞吐量下降,还要忍受HTTP协议本身,以及JSON和XML协议在信息传输上的低效。为了解决这个问题,在TCP协议层使用了一个简单的二进制协议。以保证业务使用更少的传输带宽承载更多的传输内容,从而提高吞吐量和WEB服务伺服能力。同时,在实际开发层面,PHP逻辑层与后台服务之间的通信协议维护成本较高。同时,在后台服务端增加或修改接口字段时,往往需要调用方配合修改。很多时候无法保证接口的完全兼容,导致线上运行出现问题。因此,这个二进制协议必须同时易于维护和扩展。另外,从开发效率上来说,原来的开发总是包含着很多重复性的工作要做。因为每开发一个新的协议,代码很难复用,而JSON和XML不允许你共享部分数据。同时,一个很现实的问题是,不同HTTP接口的提供者往往会根据自己的心情和习惯来定义接口。一个常见的例子就是返回码的定义,有的人叫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的话:ted18master,一共92字符是必需的。从信息学的角度来看,信息熵显然太低了。因此,为了达到更高的通信性能和更少的带宽占用,二进制协议的引入势在必行。Tars协议作为一个二进制协议,相对于以上两种协议的优势不言而喻。从上面的JSON和XML中找到它的灵活性,即没有指定字段的类型。但不可避免的是,这种灵活性带来了性能上的较大损失。因此,Tars定义了八种基本数据类型,并对不同数据类型的编码进行了优化:bool、byte、short、int、long、float、double、string,同时为了满足业务需要,struct(包括任意字段))、vector(数组)、map(key-value结构)三种复杂类型,可以嵌套数据,丰富协议的表现力。根据上面的表现结构,可以完成几个结构。第一个是学生结构:structstudent{0requiredstringname;//tag为0,type为string,实际数据为ted,共5个字节1个需要的byteage;//tag为1,type为short,实际数据为18,共2个字节2个所需的字符串度;//tag为2,type为string,实际数据为master,共7个字节}从注释中可以看出,三个字段需要的字节数14,加上结构体开头的2个字节和结构的结尾,总共只需要16个字节。相比之下,这只是JSON的1/4和XML协议识别的相同信息的1/5。它是一种高熵二进制协议,巧妙地用协议的强协议代替了传输的可读性。诀窍。为了使PHP能够与Tars完全集成,它必须具有作为客户端和服务器的能力。Tars-PHP的客户端,作为客户端,既要能够满足快速开发的需要,又要能够结合PHP现有的常用使用方法,同时给出远程调用的例子。基于这些需求,客户端解决方案实现了以下功能:实现了使用TUP协议进行打包解包、编解码的PHP扩展,以及相应的测试用例;实现tars2php工具,从Tars协议文件生成相应的PHP类文件;实现了包括网络库的二次封装,以及远程调用的代码示例;作为客户端实现的核心步骤,就是对TUP协议的支持。TUP协议位于Tars协议的上层。它通过固定的数据结构封装了发送和接收数据包的一些必要信息,如返回值、输入输出参数、数据包本身的状态、数据包计数等,提供给非Tars原生客户端和Tars服务端。用于通信的协议。Tars-PHP在支持TUP协议的方案中选择使用PHP扩展作为实现方式。PHP语言本身最受诟病的就是CPU密集型运算效率低下。由于存在效率不高的ZEND虚拟机、松散的数据结构和弱类型,导致打包和解包等CPU密集型过程效率低下。因此,PHP扩展应运而生。通过引入高性能的C/C++类库和一些原生的C/C++实现,PHP在性能处理方面迎头赶上。这就是扩展实现打包和拆包的主要逻辑的初衷。首先我们看一下PHP5x语言的结构:最底层的ServerAPI用于PHP与Webserver的通信,主要与APACHE配合使用。左上角的PHPCORE层是提供最基本的文件和网络操作能力。右上角的ZEND是一个用来将PHP脚本语言编译成机器码的工具。顶层是扩展层。这一层将充分利用ZEND的API和PHPCORE的能力,直接编写ZEND可以高效执行和理解的代码,省去了将PHP脚本编译成机器码,从而大大提高执行效率。效率。如果要设计这个扩展,就必须用C语言表达上面Tars的数据结构,并基于这个数据结构设计编码器和解码器。另外一个需要考虑的方面是在PHP层面要尽可能做到简单易用,这对扩展设计提出了比较高的挑战。一方面,必须考虑性能。另一方面,Tars协议中的Struct在PHP中必须表示为一个Class:从图中可以清楚的看出结构体SimpleStruct被分解为三部分:TAG部分成员变量部分变量的字段TAG部分description很重要,这部分用来表示Struct中各个元素的TAG值。这也是实际进行TUP编解码时二进制包最终包含的内容。为什么要有TAG?这是因为TAG本身比描述JSON中字段的文本性质节省了更多的空间。第二部分是类的成员变量,与Tars协议的Struct中的变量一一对应。这存在是为了携带相应变量的实际值。这样就可以对真正的数据进行打包和解包。为了在TAG和变量之间架起一座桥梁,还有第三部分:Fields部分。这部分是TAG与其对应的变量属性之间的映射。包含变量的名称、是否需要变量以及变量的类型。通过这些信息,一方面实现了Tars协议的二进制编码,同时也实现了解码时的映射。可谓一箭双雕。然后,在复杂的扩展设计和实现之后,需要将扩展??实现的打包和解包性能与原生PHP实现的打包和解包性能进行比较。从下表可以明显看出扩展实现在性能上有绝对优势:method/100tars复杂度打包时间(ms)打包耗时倍数解包时间(ms)解包耗时倍数展开简单0.6911。181phpNativeSimple11.251616.2813ExtendedComplex1.1711.551phpNativeComplex14.51215.110从这个表可以很明显的看出无论是简单的Tars协议还是复杂的Tars协议,使用extensions打包解包都比native好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,传入PushBackVector$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协议进行编码,返回结果可用于传输,Persist$reqBuffer=\TUPAPI::encode($iVersion=3,$iRequestId,$servantName,$funcName,$cPacketType=0,$iMessageType=0,$iTimeout,$context=[],$状态=[],$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加载,底层内置SOCKETnative实现网络层收发包;根据Interface自动生成PHPClass,与TarsAssistant无缝结合,提供Exception等容错处理;代码自动生成后,用户可以通过以下代码方便地调用远程Tars服务:require_once"./vendor/autoload.php";$ip="";//taf服务ip$port=0;//taf服务端口$servant=newApp\Server\Servant\servant($ip,$port);$in1="测试";$ss1=newSimpleStruct();$ss1->id=1;$ss1->count=2;$ss1->page=3;尝试{$intVal=$servant->singleParam($in1,$ss1,$out1);}catch(phptars\TarsException$e){//错误处理}除了构建Tars-PHP作为客户端的能力之外,Tars-PHP服务端的能力也是必不可少的,以满足不同业务场景的需求,Tars-PHP主要专注于服务器端的两类服务。第一类是HTTP服务,它将以SWOOLE2.0为基础进行网络收发,实现一套高性能、简洁易用的WEB面向服务的框架。该框架将支持常见的WEB框架特性,例如基本路由、中间件和MVC架构。同时还会集成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就近分配等。本地服务只需要提供两种能力。首先是能够调用定时的寻址服务,存储在本地存储中,保证寻址的速度。二是能够接收配置中心下发的更新特定服务地址的命令。如果能做到这两点,就可以实现高效寻址和可靠寻址。在实际使用中,结合实际业务情况,一方面,每分钟向主控请求一次服务的地址,通过轮询的方式获取一个可用的服务地址,然后放入本地高速共享内存中,以便Repeated在其中读取。另一方面,每次调用服务时,底层会自动集成服务调用耗时和成功率的报表。在双管齐下的作用下,对远程服务的调用不再像过去那样难以维护、开发和监控,而是管理清晰、高效。结论在开发效率上,使用Tars-PHP去掉了过多冗余的业务代码,通过自动生成的方式提高了代码开发的自动化程度。在性能方面,Tars-PHP方案通过引入扩展,大大提升了性能,让性能不再是PHP的“悲哀”。在易用性方面,通过提供TarsAssistant的网络发送和接收组件,不需要单独实现数据包的发送和接收。后续会推出更高性能的Swoole作为socket收发器的强大工具,进一步提升网络性能。未来,Tars-PHP的服务端解决方案也将尽快开源,提供包括客户端和服务端的完整解决方案。这套WEB后台的Tars-PHP开发系统,真正做到了高性能、高效率、高可用。阅文集团将继续与腾讯就Tars-PHP技术方案进行深度合作与实践。欢迎开发者试用!