騰訊與閱文技術合做 微服務框架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技術方案上深度合做與實踐。歡迎開發者試用!

相關文章
相關標籤/搜索