Service Mesh 在百度網盤數萬後端的落地實踐

1 背景

起初,在網盤快速發展期,爲了快速上線,採用了服務單體化 + 主幹開發模式進行研發,隨着用戶規模爆發式的增長以及產品形態的豐富,單體化的不足就體現出來了,於是架構上採用了微服務架構,開始對業務邏輯進行拆分部署。

服務拆分之後,也引入了新的問題,具體如下:

  • 請求路由:服務部署從物理機向虛擬化方式遷移中,有大量的切流量操作,需要相關的上游都進行升級上線修改,效率低下;

  • 故障管理:單實例異常、服務級別異常、機房故障異常、網絡異常等,嚴重缺失或者不完善,同時配套的故障定位也沒有,服務穩定性不足;

  • 流量轉發:不同的服務採用了不同的框架,甚至裸框架,策略不完善,導致負載不均衡;

  • 研發效率:相同的功能點,需要在不同的語言框架上實現一次,浪費人力,同時升級週期比較長,收斂效率低

2 解決方案 - UFC

2.1 UFC 發展史

爲了解決這個問題,從2015年底開始思考解決方案,確定瞭解決問題的核心在於管控請求流量,在2016年開始自研網絡流量轉發中間件 - UFC(Unified Flow Control),業務通過同機部署的agent進行服務通信,相關的發展史如下:

2.2 UFC 和 Service Mesh的關係

後來在調研業界相關技術的時候,發現了istio(業界Service Mesh的典型代表),從而發現了Service Mesh的存在,而它的定義是在2016.9 由Buoyant 公司的CEO William Morgan 提出的:

Service Mesh是一個專門用來處理服務和服務之間通信的基礎設施。它複雜確保在一個由複雜服務構成的拓撲的現代雲應用中,請求能夠被穩定的傳遞。在實踐中,Service Mesh通常通過一系列輕量級的代理來進行實現。這些代理和應用同機部署,而應用不需要感知到代理的存在。

A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It’s responsible for the reliable delivery of requests through the complex topology of services that comprise a modern, cloud native application. In practice, the service mesh is typically implemented as an array of lightweight network proxies that are deployed alongside application code, without the application needing to be aware.

從定義上,我們不難發現UFC和Service Mesh的設計理念是一致的,都是基於sidecar模式,有着控制面板和數據面板,UFC是Service Mesh的一種實現。感慨的說,歷史的發展潮流是一致的,不管你在哪裏。

目前UFC應用於網盤過千個服務上,涉及虛擬化實例數量超過20W,千億PV,機器規模10W+(網盤+其它產品線機器),10個IDC,從已知的實例規模上看,是國內最大的Service Mesh的實踐落地。

2.3 基於Service Mesh 之上的服務治理

百度網盤的實踐落地並不只侷限於Service Mesh,首先是構建了從點延伸到線的Service Mesh進行服務通信管控,然後是在UFC這個Service Mesh的基礎之上,站在全局視角對服務進行治理,保障服務的穩定性,相關能力等發展如下:

  • 點:關注與下游進行通信,不關注上游的情況;

  • 線:加入上游的標識,基於上游做異構的通信策略;

  • 面:多條線組成面,站着全局視角進行服務治理;

2.4 本文概要

本文將會先介紹UFC(如何實現一個Service Mesh),然後是基於UFC做服務治理(基於Service Mesh的實踐應用)

3 UFC 外部視角簡介

3.1 用戶使用視角

只需要做兩個事情:

  1. 服務註冊:需要先確保自己的服務(上游)和要訪問的服務(下游)已經註冊過了,沒註冊過,則需要服務的owner進行服務註冊;

  2. 服務通信:用戶通過單機agent進行服務通信訪問下游,下圖顯示了用戶從直接訪問下游變成了通過同機agent訪問下游

3.1.1 服務註冊

UFC爲每個註冊的服務分配一個service_name,service_name 是這個服務的唯一標識。同時需要對這個service_name配置它的相關配置:比如destination來源、負載均衡、熔斷、超時重試等策略

3.1.2 服務通信

訪問下游的時候,只需要訪問本機固定端口,帶上下游服務的service_name、自身標記、trace等信息,例如下面就是一個發請求的demo例子:

curl “http://127.0.0.1:8888/URI”
-H “`x-ufc-service-name`=to_service_name”
–H “`x-ufc-self-service-name`=from_service_name”
-H “`x-ufc-trace-xxxx` = xxxxx”

3.2 UFC能力視角

介紹UFC基於點和線視角的相關能力,從服務聲明、請求路由、流量轉發、故障處理、故障定位、安全等維度和istio做了一個比較。而istio是大部分是基於下游這個點進行相關通信能力設計,線視角能力很少(比如權限認證等)

總結來說,istio是能力全面,但是具體實現上策略比較簡單,而UFC是更貼近實際的業務場景(具體的可以看後面的介紹內容)

PS: 有些能力(比如流量複製/權限管理)UFC沒有,並代表百度網盤沒有這方面的能力,只是因爲歷史原因,通過其它的方案解決了。但是故障注入這塊,的確是UFC需要考慮的,有了這塊的能力,混沌工程也更容易的落地。

4 UFC 內部視角簡介

主要是介紹架構和相關具體能力的實現設計初衷

4.1 架構設計

整個架構和Service Mesh一樣,都是採用了同機Sidecar進行了流量的轉發 + 中心化控制。

4.1.1 核心流程圖

UFC組件

  • Service-Mgr: 服務(實例)管理,提供服務的增刪改查(存儲到db + cache)。定期從naming服務拉取destination列表,寫入cache(多個idc cache)。此外還會和paas進行協作(比如通知paas遷移異常的實例);

  • Agent:每臺機器部署一個,四個功能:1)通信代理:爲服務提供通信代理 2)配置同步:從同機房的meta模塊同步配置 3) 上報:異常和統計數據 4)系統異常監控:接收中心的監控檢測,包括agent存活和配置同步時效性;

  • Meta:提供服務配置元信息的查詢,多個IDC地域部署;

  • Monitor:worker模式,定期發起監控系統和業務是否存在異常,系統層面監控單機agent存活率/配置同步等,業務層面監控異常實例/服務等;

  • Metrics:聚會後的數據,數據來源來agent和monitor;

4.1.2 相關設計說明

4.1.2.1 高可用

服務單點

  • Agent: 單機agent肯定會掛斷,解決思路是基於如何避免請求受損出發點,1) 基於backup集羣臨時訪問:業務使用sdk進行訪問的時候,當訪問本機agent失敗之後,會通過訪問遠端的backup agent集羣進行訪問下游 2)基於探活屏蔽+修復: Monitor會檢測單機agent 的存活性,當單機agent異常的時候,Monitor會通知meta 服務屏蔽單機上的實例,避免上游訪問該機器,同時進行agent 修復;

  • Meta: 每個IDC 獨立部署,另外IDC內部通過多實例 + 無狀態設計解決單點問題;

  • Monitor: 每個IDC 獨立部署,另外IDC內部通過多實例 + 分佈式鎖互斥設計解決單點問題;

  • Metrics: 多實例,前面有一層proxy,將service 進行一致性hash 進行服務拆分,單機統計聚合數據;

  • Service-Mgr:多實例 + 無狀態設計解決單點問題;

  • IDC cache:當某個IDC的cache異常之後,通過Meta切換讀取backup IDC的cache解決

機房鏈路故障:

會導致配置更新異常,解決方案是通過接入層將IDC流量切空,不在UFC這層考慮解決問題

業務實例異常:

除了通過UFC 自身的故障屏蔽解決之外,還會聯動paas將長期處於異常的實例通過遷移恢復,實現異常處理的閉環

4.1.2.2 擴展性

paas擴展性:很容易融入不同的paas平臺,只需要兼容不同的naming系統即可。

業務協議:支持任意業務協議,通過bypass旁路式解決(後面的能力會進行相關介紹)。

4.1.2.3 監控

系統:各種系統的異常點,比如單機agent的存活和配置同步情況,中心配置定時器同步。

業務:服務單點異常、服務實例存活率、服務sla等業務指標儀表盤監控。

4.1.2.4 配置同步

單機上的配置是全量的,通過版本號進行增量同步(全局的版本號+服務級別的版本號),同時會通過monitor監控配置是否同步成功

4.1.2.5 具體實現

數據面板:基於OpenResty開發。從理論角度:nginx轉發性能本身就好,同時在2016年的時候Go的gc問題還比較嚴重,從實際壓測:OpenResty 要優於Go。

控制面板:基於Go開發,比較好實現併發編程。

4.1.2.6 實時debug

對於一個實時運行的系統,如何實時獲取相關的stat數據,用於問題定位呢?比如想獲取當前封禁後端列表之類的。

爲了滿足需求,增加了一個獲取數據的接口,獲取內存裏指定字段的數據,以^爲分隔符。比如以下demo就獲取了一個服務的動態數據,包括了請求次數,更新時間,失敗次數等信息。

request:
curl -v -H "token:token" 'http://10.10.11.11:8888/bypass?command=query&ar=request_info^pcs-upload'
response:
{
"request_info": {
"5xx_cnt": 2,
"mtime": 1573456024,
"request_cnt": 6,
"success_cnt": 4
}
}

4.2 功能設計

主要介紹UFC基於點和線的相關能力細節以及設計出發點

4.2.1 請求路由

提供相關的匹配能力,根據匹配條件進行後續相關的操作,比如設置後端的權重等進行多版本灰度測試、流量遷移等功能。

4.2.1.1 基於請求匹配

基於請求特徵做路由匹配,例如http request header: headers[“x-http-flow-control”],可以按隨機比例 或者 等價 進行匹配

類似於istio的路由規則

4.2.1.2 基於上游匹配

基於上游匹配是從線視角出發,UFC的線視角如下:

爲啥要做基於上游匹配,出發點如下:

流量等級:

不同的上游服務重要性也不一樣,產生請求的重要性也不一樣,於是基於上游的標誌設置異構的策略就很有必要。比如網盤的用戶文件列表展示服務和多端列表同步服務一樣會訪問數據庫,顯然前者比後者重要,用戶直接感知,需要區別對待。而如果是重試請求,這裏面的重要性又不一樣了,爲了簡化策略配置,目前在將請求權重化,根據若干個請求因素的計算出請求權重,然後根據請求權重執行不同的策略。

具體的實踐落地:請求權重可以用於細粒度的降級策略。

機房策略:

上游訪問下游,默認是不關注上游請求的來源機房,也不關注下游endpoint的機房歸屬,流量在機房之間是混連的,那麼當時候,要切空機房流量異常麻煩。基於上游請求的機房的標識設置機房路由規則就很有必要了。

具體的實踐落地:可以定製機房轉發策略,比如流量在同一個物理 或者 邏輯機房內轉發,詳見下文流量轉發裏的基於上游轉發。

4.2.2 流量轉發

從協議、負載均衡、基於上游轉發、傳輸策略四個方面介紹

4.2.2.1 協議

Istio 只支持http/tcp/grpc協議,而業務使用的協議肯定不止這些協議,比如redis/mysql等,如果都支持這些協議以及後續的其它協議,那麼兼容的研發成本將非常的高,在用戶需求和研發成本之間,UFC找到了折衷的解決方案實現了支持任意協議,具體上是以穿透式和旁路式進行落地的。

穿透式請求: 適用場景爲http協議控制流,和istio的使用方式一樣

旁路式請求: 加強版的DNS,適用場景爲http 協議數據流 or 非 http 協議,業務先從UFC拿到一個後端地址,然後業務自己通過這個後端地址訪問後端,最後需要回調UFC告知UFC這次通信的結果,方便UFC更新相關數據(比如該後端的訪問成功率等,決策是否需要封禁之類的)。通過旁路式解決了任意協議的場景,同時對後端流量進行了管控。

4.2.2.2 負載均衡

採用了比較常見的負載均衡策略,比如輪詢、隨機、一致性hash(根據業務定義的字段進行hash)

4.2.2.3 基於上游轉發

主要的應用在於上下游IDC流量路由上,比如設置完下面的路由表之後,就實現了同(邏輯/物理)機房的流量轉發:

4.2.2.4 傳輸策略

主要是從連接策略、超時策略,重試策略介紹的

連接策略:

上游:短連接、長連接

下游:短連接、長連接

超時策略:

基本功能:設置與下游的連接/讀寫超時,可以被上游請求裏的指定參數覆蓋超時時間。

自動切換:兩套超時策略配置,結合故障處理自動切換。常規下T1超時策略(客戶端可覆蓋),雪崩下T2超時策略。當雪崩的時候自動切換到T2超時策略。這樣既可以照顧到常規情況下的請求長尾,又避免了雪崩等場景下下游服務拖跨上游訪問。

重試策略:

什麼場景下重試:需要分清楚重試的邊界。從功能角度看,Service Mesh的功能是保障服務通信成功,所以UFC只在與後端通信失敗場景(連接/寫/讀失敗)下才重試,對於業務語義層面的錯誤,由業務發起重試,UFC不介入重試邏輯。另外,從性能的角度出發,也不該把業務層面的重試下沉到Service Mesh這個基礎組件上來,因爲涉及到對返回的body做反序列化,這個會影響其它的請求,增加請求耗時。

基本功能:設置與下游交互失敗的重試次數。

自動切換:兩套重試策略,結合故障處理自動切換。常規下重試,雪崩下不重試,當雪崩的時候自動切換到不重試。這樣既可以照顧到常規情況下少量的異常,又避免了雪崩等場景下加劇下游的雪崩,減少無意義的重試請求。

4.2.3 故障處理

從故障的範圍影響上分成:

單點故障:少量後端異常,比如進程因爲oom 掛掉 或者 因爲機器異常導致進程異常。

服務級別故障:當多個單點故障之後,就引發了服務級別的故障,比如服務雪崩。另外業務自身異常(比如全部返回5xx)不在處理範疇內。

從故障處理流程上分成:

發現故障: 怎麼定位故障的發生,包括單點/服務級別故障。

處理故障:怎麼處理使得單點/服務故障能夠恢復。

4.2.3.1 單點故障

故障處理具體發現故障和解決故障兩方面的內容,難點在於發現單點故障的準確性,是否存在漏判。

發現故障

基於請求:

單次異常:連接異常(連接超時/拒絕/Reset等)、讀寫異常(讀寫超時等)

判斷標準:N次請求裏M次請求異常

基於業務:

業務維度:http status code、耗時等

判斷標準:單個後端和平均值(算平均值的時候需要排除掉比較對象的數據)做比較,看哪個後端嚴重偏離平均值,比如狀態碼區間比例之類的。這裏沒有采用單次異常判斷是因爲有些場景下,通過單次請求是無法判斷爲異常的,比如一個後端返回404的http status code,這個是符合預期的?沒人知道,因爲常規情況下業務也會返回404,而在LNMP架構裏,如果單個實例磁盤問題導致PHP文件丟失,Nginx找不到PHP文件,也會返回404,這是一個混沌的狀態。UFC的解決方案,就是和平均值進行比較,看是否偏離平均值。以 下圖後端狀態碼區間比例統計爲例子,後端2 4xx的http code達到了100%,而其它的後端這個比例也只有不到5%,那麼大概率後端2 就存在問題。

解決故障

處理:初始封禁T時間

封禁收斂:後端連續封禁時間翻倍,解決異常後端被反覆解封、封禁的場景

封禁閉環:長時間封禁的後端,通知paas進行異常實例的遷移
封禁backup機制:達到最大封禁比例,通知人工介入處理

恢復:當封禁時間到了之後,檢測端口是否可用,可用則解封

4.2.3.2 服務故障

主要是針對雪崩過載的場景下如何儘量保障服務可用,也是從發現故障和解決故障進行介紹的,難點在於如何減少故障帶來的業務流量損失。

發現故障

基於通信成功率:下游服務N次請求裏成功率低於X%

基於業務語義成功率: UFC沒采用的原因是不能越俎代庖做自己能力之外的事情,Service Mesh 核心在於流量控制,它的能力是流量控制來避免下游過載,防止雪崩,例如業務代碼bug導致的成功率下降,Service Mesh 也沒有辦法。

解決故障

PS: 動態熔斷的思想是借鑑了網絡,當雪崩過載的時候,相當於發生了請求的擁塞,和網絡擁塞是一樣的特徵行爲,網絡鏈路都帶寬相當於服務的容量

4.2.4 儀表盤

儀表盤基於數據源進行分析得到以下內容:

  • 分佈式服務的日誌追蹤;

  • 業務度量數據的收集/分析/展示。

4.2.4.1 數據源

有兩方面的數據源:

服務通信access日誌:通過日誌傳輸進行聚合分析。

何時由上游A對下游B進行訪問,請求通過了後端X1/X2進行訪問,重試了N次,耗時爲T1/T2等,狀態碼爲S1/S2等,

異常行爲:業務層面比如服務熔斷等,系統層面比如封禁計數器異常等,由agent實時上報到中心做聚合分析

4.2.4.2 distributed traces

首先基於服務通信access日誌做鏈路分析,然後將鏈路信息存入Elasticsearch方便檢索,比如下圖就是一個鏈路檢索

4.2.4.3 metrics

從點、線、面視角上進行數據的聚合分析。

點視角:後端維度

服務後端異常監控:

單實例異常觸發封禁:觸發封禁的後端,長時間封禁走paas遷移流程。

服務觸發熔斷:觸發服務動態熔斷降級。

線視角:上下游維度

服務下游監控:服務訪問所有下游的概貌,支持按http 狀態碼和idc做過濾,同時支持環比(昨天/一週前)。下面以視頻轉碼爲例子,展示對若干個下游的訪問概貌。

請求數: 可以根據曲線分析是否存在異常。

請求失敗數:根據失敗數量可以算出上游對下游的請求sla

請求耗時: 可以比較昨天/上週的耗時數據,看是否存在異常

服務上游監控:服務被所有上游訪問的概貌,可以按http 狀態碼和idc粒度進行過濾,同時支持環比(昨天/一週前)。下面以一個異步化服務爲例子,被一堆的上游訪問,統計這些上游的訪問概貌。

請求數: 在定位服務的請求數突增,可以很容易識別出是哪個上游導致的。

請求失敗數:如果一些上游訪問失敗數比較高,可以聯繫業務進行分析定位。

請求耗時: 可以比較昨天/上週的耗時數據,看是否存在異常

面視角:全局視角

核心功能鏈路SLA監控:當鏈路sla降低的時候,可以很快定位到是哪個鏈路分支出現的異常。

耗時維度:全部服務裏,耗時增加top 10 業務,快速知曉業務概貌。

業務失敗率維度: 全部服務裏,失敗數最多的top 10業務,快速知曉業務概貌。

機器維度:全部機器裏,請求5xx失敗最高的機器 top 10,快速知曉機器異常。

5 服務治理

5.1 服務治理的定義

如前文所述,服務治理是建立在Service Mesh基礎能力之上的,站在全局視角統籌規劃,以保障服務穩定性爲出發點。

5.2 具體實踐

服務治理的意義:如下圖所示,百度網盤全局拓撲異常複雜,靠傳統的人工套路去保障服務穩定性,效率和覆蓋面都有很大的缺陷,基於流量控制的service mesh進行服務治理纔是王道,具體從故障預防、故障定位和故障處理出發。

5.2.1 故障預防

故障預防從以下三個維度進行:

流量隔離:如何規避低優先級的請求流量影響高優先級的請求,避免喧賓奪主;

容量管理:如何保障服務容量滿足實際需求,避免容量不足導致雪崩;

無效請求: 如何減少無效的請求,避免服務陷入雪崩的危機之中

5.2.1.1 流量隔離

具體落地中又分成機房流量隔離以及在離線流量隔離:

機房流量隔離:統一網盤所有服務邏輯IDC映射關係,UFC 自動識別上游所在機房,將請求轉發到下游服務相應機房,從入口到請求終端,以邏輯IDC機房維度進行了流量隔離。當發生機房故障的時候,入口處切流量即可解決機房故障。

在離線流量隔離:定義網盤所有服務的等級,包括在離線標記,UFC根據全局流量拓撲,可以發現是否存在在離線混連情況,避免低優先級的請求流量影響高優先級的請求,導致喧賓奪主。比如下圖中,Online-a 和 Offline-a都訪問Online-b服務,這樣Offline-a 有可能引發Online-b服務異常,而從影響Online-a與Online-b的請求,間接影響用戶請求。發現這種在離線混連的情況,需要對服務進行拆分,Online-b/Onine-c各自拆分成Onine和Offline兩套服務,進行在離線流量的隔離。

5.2.1.2 容量管理

具體落地中又分成容量評估以及容量壓測,前置是根據鏈路拓撲做評估,後者是通過壓測實踐驗證評估的準確性:

容量評估:通過鏈路上的qps,分析出每個服務需要增加的qps,進一步推算出需要擴容多少實例(PS:需要解決環路的問題)

容量壓測: 通過以線上流量以機房切流量逐漸加壓的方式來壓測,期間監控服務sla,低於某個閾值之後,自動停止壓測

5.2.1.3 無效請求

無效請求產生的背景:當client斷開連接之後,server還在繼續訪問其它的後端,進行無效的請求。比如下圖中,client以300ms的超時時間訪問server,server在訪問A和B之後,已經用掉了300ms,這個時候client已經斷開了和server的連接,但是server卻繼續訪問C和D,進行無效的請求。當這種無效請求在整個鏈路蔓延開,client又在大量的重試的時候,就是雪崩降臨的時刻。

解決方案 - 基本思路:基於鏈路的智能超時,設置整個鏈路的超時時間,當發現超時時間已經到了,就不再訪問其它的下游服務,避免無效請求。上游需要傳遞給下游執行超時時間(採用相對時間,避免機器之間的時鐘不同步),用於下游判斷執行時間是否已經到了。

解決方案 - 基於業務:不需要使用service mesh,業務自己維護當前的超時時間,業務改造的成本比較大。

解決方案 - 基於service mesh:UFC以唯一ID映射到一個請求到所有後端交互的鏈路上,UFC自動維護剩餘的請求耗時,實現對業務近視0侵入(PS:還未用於生產環境)。

5.2.2 故障定位

定位的思路爲:發現異常 –> 收集系統異常點/異常時間點相關變動 –> 定位原因。

除了基於service mesh採集系統的異常點,還需要聯動其它的系統監控項,比如服務容量。

5.2.3 故障處理

根據故障的影響面可以分成局部/全局雪崩故障。

5.2.3.1 局部故障

前置條件:

部署:服務部署在多個邏輯機房;

上線:服務分級發佈,單點 -> 單機房 -> 全機房。

場景:通過機房切流量快速解決機房硬件故障、小流量上線引發故障、後端異常封禁失敗等局部異常

5.2.3.2 全局雪崩故障

前置條件:

等級:需要定義服務/流量的等級;

統一降級標記:統一所有服務的降級標記。

場景:通過全局動態熔斷 + 異構降級 + 降級閉環策略解決服務引起的雪崩問題,儘量保障服務的可用度,具備自愈能力

6 總結與展望

在業務拆分面多衆多問題的背景之下,百度網盤從2015年底開始思考解決方案,確定瞭解決問題的核心在於管控請求流量,在2016年開始自研網絡流量轉發中間件 - UFC(Unified Flow Control),業務通過同機部署的agent進行服務通信。後來通過調研借鑑業界技術的時候發現UFC和Service Mesh的設計理念是一致的,都是基於sidecar模式,有着控制面板和數據面板,UFC是Service Mesh的一種實現。經過多年的線上實踐驗證,UFC這個Service Mesh實現了動態熔斷 + 異構降級 + 降級閉環等故障處理、結合故障/上游進行流量轉發等創造性設計,滿足業務的實際場景需求。但是百度網盤的實踐落地並不只侷限於Service Mesh,首先是構建了從點延伸到線的Service Mesh進行服務通信管控,然後是在UFC這個Service Mesh的基礎之上,站在全局視角對服務進行治理,保障服務的穩定性。

未來,UFC將會加入故障注入等能力,同時基於該能力落地混沌工程,而這只是服務治理中預防的一部分。服務治理的目標是自愈,爲了完成這個目標,還需要更加努力。

作者介紹:

本文作者李鴻斌,百度資深研發工程師。2011 年畢業後加入百度的基礎架構部門,參與了百度分佈式雲存儲的設計與開發,支撐了百度所有業務線的對象存儲。2012 年至今,作爲核心研發人員參與了網盤從 0 到 1 到 N 的基礎架構演進,先後參與或負責對象存儲 / 個人文件存儲 / runtime 基礎環境 / 資源管理 / 微服務架構,設計並實現了計算混佈於萬級別在線存儲機器的資源模型以及服務資源調度,節省下了數萬臺服務器的計算資源,產品線資源利用率公司第一,同時從 2016 年至今主導了自研的 Service Mesh- UFC(Unified Flow Control),實現了從點到線到面的服務治理保障了網盤服務的高可用,近期專注於邊緣計算方向,此外業餘時間反哺社區,php-src/hhvm/beego 等源碼貢獻者。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章