字節(jié)跳動(dòng)自研高性能微服務(wù)框架 Kitex 的演進(jìn)之旅(字節(jié)跳動(dòng)服務(wù)器開發(fā))
字節(jié)微服務(wù)框架的挑戰(zhàn)和演進(jìn)
2014 年以來,字節(jié)跳動(dòng)內(nèi)部業(yè)務(wù)的快速發(fā)展,推動(dòng)了長連接推送服務(wù),它們面臨著高并發(fā)的業(yè)務(wù)需求問題,對(duì)性能和開發(fā)效率都有很高要求。當(dāng)時(shí)的業(yè)務(wù),大部分都是由 Python 開發(fā),難以應(yīng)對(duì)新出現(xiàn)的問題。項(xiàng)目負(fù)責(zé)人在一眾現(xiàn)存的技術(shù)棧中選擇了 Golang 這一門新興的編程語言,快速解決了性能和開發(fā)效率的問題。隨后,字節(jié)跳動(dòng)內(nèi)部開始逐漸推廣使用 Golang 進(jìn)行服務(wù)開發(fā)。
2016 年, 第一代 Golang RPC 框架 Kite 正式發(fā)布。Kite 是一個(gè)基于 Apache Thrift 進(jìn)行包裝的 RPC 框架,它在 Facebook 開源的 thrift 之上提供了結(jié)合字節(jié)跳動(dòng)內(nèi)部基礎(chǔ)設(shè)施的治理功能,同時(shí)還提供了一套簡單易用的生成工具。隨著 Kite 的發(fā)展,業(yè)務(wù)開始大規(guī)模使用 Golang。然而,在業(yè)務(wù)發(fā)展的過程中,由于研發(fā)專注于實(shí)現(xiàn)業(yè)務(wù)需求,對(duì)于框架的可維護(hù)性考量不足,Kite 逐漸背上了一些技術(shù)包袱,越來越難以滿足業(yè)務(wù)在高性能和新特性方面的需求。因此我們決定對(duì) Kite 進(jìn)行重新設(shè)計(jì),于是出現(xiàn)了 Kitex。
2020 年,Kitex 在內(nèi)部發(fā)布了 V1.0.0,并且直接接入了 1,000 服務(wù)。由于 Kitex 的優(yōu)秀性能和易用性,Kitex 在內(nèi)部得到了大規(guī)模發(fā)展。直到 2021 年年中,字節(jié)跳動(dòng)內(nèi)部已有 2w 服務(wù)使用了 Kitex。因此,我們決定全面優(yōu)化 Kitex,將其實(shí)踐成果進(jìn)行開源,反饋給開源社區(qū)。
字節(jié)跳動(dòng) Golang RPC 框架的演進(jìn)
Kite 的缺陷
Kite 作為字節(jié)跳動(dòng)第一代 Golang RPC 框架,主要存在以下缺陷:
- Kite 為了快速支持業(yè)務(wù)發(fā)展需求,不可避免地耦合了部分中臺(tái)業(yè)務(wù)的功能;
- Kite 對(duì) Go modules 支持不友好(Go modules 在 2019 年才進(jìn)入語言核心);
- Kite 自身的代碼拆分成多倉庫,版本更新時(shí)推動(dòng)業(yè)務(wù)升級(jí)困難;
- Kite 強(qiáng)耦合了早期版本的 Apache Thrift,協(xié)議和功能拓展困難;
- Kite 的生成代碼邏輯與框架接口強(qiáng)耦合,成為了性能優(yōu)化的天花板。
因此,業(yè)務(wù)的快速發(fā)展和需求場景的多樣化,催生了新一代 Golang RPC 框架 Kitex。
Kitex
Kitex 的架構(gòu)主要包括四個(gè)部分:Kitex Tool、Kitex Core、Kitex Byted、Second Party Pkg。
- Kitex Core 是一個(gè)攜帶了一套微服務(wù)治理功能的 RPC 框架,它是 Kitex 的核心部分。
- Kitex Byted 是一套結(jié)合了字節(jié)跳動(dòng)內(nèi)部基礎(chǔ)設(shè)施的拓展集合。通過這一套拓展集合,Kitex 能夠在內(nèi)部支持業(yè)務(wù)的發(fā)展。
- Kitex Tool 是一個(gè)命令行工具,能夠在命令行生成我們的代碼以及服務(wù)的腳手架,可以提供非常便捷的開發(fā)體驗(yàn)。
- Second Party Pkg,例如 netpoll, netpoll-http2,是 Kitex 底層的網(wǎng)絡(luò)庫,這兩個(gè)庫也開源在 CloudWeGo 組織中。
Kitex 的架構(gòu)設(shè)計(jì)
總的來說, Kitex 主要有五個(gè)特點(diǎn):面向開源、功能豐富、靈活可拓展、支持多協(xié)議、高性能。
面向開源
由于之前已經(jīng)體驗(yàn)過了 Kite 維護(hù)的各種問題,我們?cè)诹㈨?xiàng)之初就考慮到了未來可能會(huì)開源 Kitex。因此,我們?cè)O(shè)計(jì)的第一個(gè)宗旨就是不將 Kitex 和公司內(nèi)部的基礎(chǔ)設(shè)施進(jìn)行強(qiáng)耦合或者硬編碼綁定。Kitex Core 是一個(gè)非常簡潔的框架,公司內(nèi)部的所有基礎(chǔ)設(shè)施都以拓展的方式注入到 Kitex Core 里。即使我們現(xiàn)在已經(jīng)開源了,它也以這種形式存在。公司內(nèi)部基礎(chǔ)設(shè)施的更新?lián)Q代,和 Kitex 自身的迭代是相互獨(dú)立的,這對(duì)于業(yè)務(wù)來說是非常好的體驗(yàn)。同時(shí),在 Kitex 的接口設(shè)計(jì)上,我們使用了 Golang 經(jīng)典的 Option 模式,它是可變參數(shù),通過 Option 能夠提供各種各樣的功能,這為我們的開發(fā)和業(yè)務(wù)的使用都帶來了非常大的靈活性。
Kitex 的功能特性
治理能力
Kitex 內(nèi)置了豐富的服務(wù)治理能力,例如超時(shí)熔斷、重試、負(fù)載均衡、泛化調(diào)用、數(shù)據(jù)透傳等功能。業(yè)務(wù)或者外部的用戶使用 Kitex 都是可以開箱即用的。如果你有非常特殊的需求,你也可以通過我們的注入點(diǎn)去進(jìn)行定制化操作,比如你可以自定義中間件去過濾或者攔截請(qǐng)求,定義跟蹤器去注入日志、去注入服務(wù)發(fā)現(xiàn)等。在 Kitex 中,幾乎一切跟策略相關(guān)的東西都是可以定制的。
以服務(wù)發(fā)現(xiàn)為例,Kitex 的核心庫里定義了一個(gè) Resolver interface 。任何一個(gè)實(shí)現(xiàn)了這四個(gè)方法的類型都可以作為一個(gè)服務(wù)發(fā)現(xiàn)的組件,然后注入到 Kitex 來取代 Kitex 的服務(wù)發(fā)現(xiàn)功能。在使用時(shí),客戶端只需要?jiǎng)?chuàng)建一個(gè) Resolver 的對(duì)象,然后通過 client.WithResolver 注入客戶端,就可以使用自己開發(fā)的服務(wù)發(fā)現(xiàn)組件。
Kitex 的一個(gè)創(chuàng)新之處是使用 Suite 來打包自定義的功能,提供一鍵配置基礎(chǔ)依賴的體驗(yàn)。
它能在什么地方起作用呢?例如,一個(gè)外部企業(yè)想要啟用或者接入 Kitex, 它不可能擁有字節(jié)跳動(dòng)內(nèi)部的所有基礎(chǔ)設(shè)施。那么企業(yè)在使用的時(shí)候肯定需要定制化,他可能需要定義自己的注冊(cè)中心、負(fù)載均衡、連接池等等。如果業(yè)務(wù)方要使用這些功能的話,就需要加入非常非常多的參數(shù)。而 Suite 可以通過一個(gè)簡單的類一次性包裝這些功能,由此,業(yè)務(wù)方使用時(shí),仍然是以單一的參數(shù)的方式添加,十分方便。又例如,我現(xiàn)在開發(fā)一個(gè)叫 mysuite 的東西,我可能提供一個(gè)特殊的服務(wù)發(fā)現(xiàn)功能,提供了一個(gè)攔截的中間件,還有負(fù)載均衡功能等。業(yè)務(wù)方使用時(shí),不需要感知很多東西去配置,只需要添加一個(gè) suite 就足夠了,這點(diǎn)非常方便一些中臺(tái)方或者第三方去做定制。
示例
多協(xié)議
Kitex 網(wǎng)絡(luò)層基于高性能網(wǎng)絡(luò)庫 Netpoll 實(shí)現(xiàn)。在 Netpoll 上,我們構(gòu)建了 Thrift 和 netpoll-http2;在 Thrift 上,我們還做了一些特殊的定制,例如,支持 Thrift 的泛化調(diào)用,還有基于 Thrift 的連接多路復(fù)用。
多協(xié)議
代碼生成工具
和 Kitex 一同出現(xiàn)的,還有我們開發(fā)的一個(gè)簡單易用的命令行工具。如果我們寫了一個(gè) IDL, 只需要提供一個(gè) module 參數(shù)和一個(gè)服務(wù)名稱,Kitex 就會(huì)為你生成服務(wù)代碼腳手架。
目前 Kitex 支持了 Protobuf 和 Thrift 這兩種 IDL 的定義。命令行工具內(nèi)置豐富的選項(xiàng),可以進(jìn)行項(xiàng)目代碼定制;同時(shí),它底層依賴 Protobuf 官方的編譯器,和我們自研的 Thriftgo 的編譯器,兩者都支持自定義的生成代碼插件。
Kitex 的性能表現(xiàn)
字節(jié)跳動(dòng)內(nèi)部 RPC 框架使用的協(xié)議主要都是基于 Thrift,所以我們?cè)?Thrift 上深耕已久。結(jié)合自研的 netpoll 能力,它可以直接暴露底層連接的 buffer。在此基礎(chǔ)上,我們?cè)O(shè)計(jì)出了 FastRead/FastWrite 編解碼實(shí)現(xiàn),測(cè)試發(fā)現(xiàn)它具有遠(yuǎn)超過 apache thrift 生成代碼的性能。整體而言,Kitex 的性能相當(dāng)不錯(cuò),今年 1 月份的數(shù)據(jù)如下圖所示,可以看到,Kitex 在使用 Thrift 作為 Payload 的情況下,性能優(yōu)于官方 gRPC,吞吐接近 gRPC 的兩倍;此外,在 Kitex 使用定制的 Protobuf 協(xié)議時(shí),性能也優(yōu)于 gRPC。
Kitex/gRPC 性能對(duì)比(2022 年 1 月數(shù)據(jù))
Kitex:一個(gè) demo
下面簡單演示一下 Kitex 是如何開發(fā)一個(gè)服務(wù)的。
首先,定義 IDL。這里使用 Thrift 作為 IDL 的定義,編寫一個(gè)名為 Demo 的 service。方法 Test 的參數(shù)是 String,它的返回也是 String。編寫完這個(gè) demo.thrift 文件之后,就可以使用 Kitex 在命令行生成指定的生成代碼。如圖所示,只需要傳入 module name,service name 和目標(biāo) IDL 就行了。
定義 IDL
隨后,我們需要填充業(yè)務(wù)邏輯。文件中除了第 12 行,全部代碼都是 Kitex 命令行工具生成的。通常一個(gè) RPC 方法需要返回一個(gè) Response,例如這里需要返回一個(gè)字符串,那么我們給 Response 賦值即可。接下來需要通過 go mod tidy 把依賴?yán)聛恚缓笥?build.sh 構(gòu)建,就可以啟動(dòng)服務(wù)了。Kitex 默認(rèn)的接聽端口是 8888。
定義 Handler 方法
編譯、運(yùn)行
對(duì)于剛剛啟動(dòng)的服務(wù)端,我們可以寫一個(gè)簡單的客戶端去調(diào)用它。服務(wù)端寫完之后,寫客戶端也是非常方便的。這里同樣是 import 剛剛生成的生成代碼,創(chuàng)建 Client、指定服務(wù)名字、構(gòu)成相應(yīng)的參數(shù),填上“Hello,word!” ,然后就可以調(diào)用了。
編寫 Client
Kitex 在字節(jié)內(nèi)部的落地
與內(nèi)部基礎(chǔ)設(shè)施的集成
談到落地,第一步就是 Kitex 和字節(jié)跳動(dòng)內(nèi)部的基礎(chǔ)設(shè)施進(jìn)行結(jié)合。字節(jié)跳動(dòng)內(nèi)部的所有基礎(chǔ)設(shè)施都是以依賴的方式注入到 Kitex 的。我們將日志、監(jiān)控、tracing 都定義為 tracer,然后通過 WithTracer 這個(gè) Option 將其注入到 Kitex 里;服務(wù)發(fā)現(xiàn)是 WithResolver;Service Mesh 則是 WtihProxy 等。字節(jié)跳動(dòng)內(nèi)部的基礎(chǔ)設(shè)施都是通過 Option 被注入到 Kitex 的,而且所有的 Option 都是通過前面說的 Suite 打包,簡單地添加到業(yè)務(wù)的代碼里完成。
與內(nèi)部基礎(chǔ)設(shè)施的集成
內(nèi)部落地的經(jīng)典案例:合并部署
這里介紹一個(gè)內(nèi)部落地的經(jīng)典案例:合并部署。其背景是,在開發(fā)微服務(wù)時(shí),由于業(yè)務(wù)拆分和業(yè)務(wù)場景的多樣化,微服務(wù)容易出現(xiàn)過微的情況。當(dāng)服務(wù)數(shù)量越來越多,網(wǎng)絡(luò)傳輸和序列化開銷就會(huì)越來越大,變得不可忽視。因此,Kitex 框架需要考慮如何減小網(wǎng)絡(luò)傳輸和序列化的開銷。
字節(jié)跳動(dòng)基礎(chǔ)架構(gòu)經(jīng)過一系列的探索和實(shí)踐,最終推出了合并部署的機(jī)制。它的思路是:將有強(qiáng)依賴關(guān)系的服務(wù)進(jìn)行同機(jī)部署,減少它們之間的調(diào)用開銷。理論上說起來比較簡單,實(shí)際過程中需要非常多的組件進(jìn)行配合。
Kitex 的做法是:首先,它會(huì)依賴一套中心化的部署調(diào)度和流量控制;其次,我們開發(fā)了一套基于共享內(nèi)存的通信協(xié)議,它可以使得我們兩個(gè)不同的服務(wù)在同一臺(tái)機(jī)器部署時(shí),不需要通過網(wǎng)絡(luò)進(jìn)行數(shù)據(jù)傳輸,直接通過共享內(nèi)存,減少額外的數(shù)據(jù)拷貝。
在服務(wù)合并部署的模式下,我們需要特殊的服務(wù)發(fā)現(xiàn)和連接池的實(shí)現(xiàn)、定制化的服務(wù)啟動(dòng)和監(jiān)聽邏輯。這些在 Kitex 框架里都是通過依賴注入的方式給添加進(jìn)來的。Kitex 服務(wù)在啟動(dòng)過程中會(huì)感知到我們 PaaS 平臺(tái)提供的指定的環(huán)境變量。當(dāng)它察覺到自己需要按合并部署的方式啟動(dòng)之后,就會(huì)啟動(dòng)一個(gè)預(yù)先注入的特定 Suite,隨后將相應(yīng)的功能全都添加進(jìn)來再啟動(dòng),就可以執(zhí)行我們的合并部署。
那么,它的效果如何呢?在 2021 年的實(shí)踐過程中,我們對(duì)抖音的某個(gè)服務(wù)約 30% 的流量進(jìn)行了合并,服務(wù)端的 CPU 的消耗減少了 19%, TP99 延遲下降到 29%,效果相當(dāng)顯著。
內(nèi)部落地的經(jīng)典案例:合并部署
微服務(wù)框架推進(jìn)的痛點(diǎn)
- 升級(jí)慢
大家可能好奇 Kitex 在字節(jié)跳動(dòng)內(nèi)部推廣是不是很順暢?其實(shí)并不是。作為一個(gè)相對(duì)而言比較新的框架, Kitex 和其它新生項(xiàng)目一樣,在推廣的過程中都會(huì)遇到同樣的問題。特別是, Kitex 作為一個(gè) RPC 框架,我們提供給用戶的其實(shí)是一個(gè)代碼的 SDK, 我們的更新是需要業(yè)務(wù)方的用戶去感知、升級(jí)、部署上線,才能最終體現(xiàn)在他們的服務(wù)邏輯里,因此具有升級(jí)慢的問題。
- 召回慢
同時(shí),因?yàn)榇a都是由研發(fā)人員編寫,如果代碼出現(xiàn)了 bug,我們就需要及時(shí)地去感知定位問題,通知負(fù)責(zé)人去更新版本。因此,會(huì)有召回慢的問題。
- 問題排查困難
業(yè)務(wù)方的用戶在寫代碼時(shí),他們其實(shí)往往關(guān)注的是自己的業(yè)務(wù)邏輯,他們不會(huì)深入理解一個(gè)框架內(nèi)部的實(shí)現(xiàn)。所以如果出現(xiàn)問題,他們往往會(huì)不知所措,需要依賴我們的業(yè)務(wù)同學(xué)才能進(jìn)行相應(yīng)的問題排查。所以會(huì)有問題排查困難的問題。
針對(duì)升級(jí)慢,我們有兩個(gè)操作。一是,代碼生成工具支持自動(dòng)更新:當(dāng)用戶在使用時(shí),我們會(huì)檢查最新版本,然后直接將我們的版本更新到最新版本,這樣可以及時(shí)把我們的框架新 feature、bug fix 直接推送到業(yè)務(wù)方;二是,用戶群發(fā)版周知:我們有一個(gè)幾千人的用戶群,當(dāng)有了新版本,我們會(huì)在用群里周知,可以最大范圍的覆蓋到我們的目標(biāo)用戶。
針對(duì)召回慢,我們有三個(gè)操作。一是,我們?cè)诰€上建立完整的版本分布統(tǒng)計(jì),監(jiān)控所有服務(wù)上線部署的框架的版本;二是,我們會(huì)跟 PaaS 平臺(tái)合作,在服務(wù)上線時(shí)進(jìn)行卡點(diǎn)操作,檢查它們使用的框架版本是不是有 bug,是否需要攔截;三是,針對(duì)有問題的版本,我們會(huì)及時(shí)封禁,及時(shí)推動(dòng)用戶更新。
針對(duì)問題排查困難,我們有兩個(gè)操作。一是,我們積累了非常豐富的 Wiki 和問題排查手冊(cè),例如超時(shí)問題、 協(xié)議解析問題等。二是,如果遇到難以解決的問題,我們?cè)诰€上服務(wù)默認(rèn)開啟了 Debug 端口,保證框架開發(fā)同學(xué)可以第一時(shí)間趕到現(xiàn)場去排查。
Kitex 在字節(jié)內(nèi)部的發(fā)展
數(shù)據(jù)顯示,在 2020 年,v1.0 版本發(fā)布的初始階段,用戶的接受度比較低。直到 2020 年 6 月,線上接受 Kitex 的數(shù)量還不到 1000。隨后進(jìn)入快速發(fā)展的階段,到 2021 年年初,累積接近 1w 的服務(wù)開始使用 Kitex。2021 年底,4w 服務(wù)使用 Kitex。
Kitex 的開源實(shí)踐
開源工作主要包括代碼、文檔和社區(qū)運(yùn)營三個(gè)層面。
代碼層面
- 代碼拆分、脫敏;
- 內(nèi)部倉庫引用開源倉庫,避免內(nèi)外多副本同時(shí)維護(hù);
- 在開源過程中確保內(nèi)部用戶平滑切換、體驗(yàn)無損;
文檔層面
- 重新梳理用戶文檔,覆蓋方方面面;
- 建立詳盡的用例倉庫(CloudWeGo/Kitex-examples)。
社區(qū)運(yùn)營
- 官網(wǎng)建設(shè);
- 組建用戶群,進(jìn)行答疑解惑;
- 飛書機(jī)器人對(duì)接 Github 的 Issue 管理、PR 管理之類的業(yè)務(wù),可以快速響應(yīng);
- 對(duì)優(yōu)秀貢獻(xiàn)者進(jìn)行獎(jiǎng)勵(lì)。
在以上努力下,CloudWeGo/Kitex 倉庫目前收獲了 4.1k stars;Kitex-Contrib 獲得多個(gè)外部用戶貢獻(xiàn)的倉庫;CloudWeGo 飛書用戶群近 950 個(gè)用戶……
未來展望
首先,我們?nèi)匀粫?huì)持續(xù)向開源社區(qū)反饋?zhàn)钚碌募夹g(shù)進(jìn)展。例如在 Thrift 協(xié)議上,雖然對(duì) Thrift 的編解碼已經(jīng)做到非常極致的優(yōu)化了,我們還在探索利用 JIT 手段來提供更多的性能提升;在 Protobuf 上,我們會(huì)補(bǔ)足短板,將在 Thrift 方面的優(yōu)化經(jīng)驗(yàn)遷移到 Protobuf 上,對(duì) Protobuf 的生成代碼和編解碼進(jìn)行優(yōu)化;Kitex 后續(xù)也會(huì)進(jìn)一步融入云原生社區(qū),所以也在考慮支持 xDS 協(xié)議。其次,我們會(huì)去拓展更多的開源組件,去對(duì)接現(xiàn)存的云原生社區(qū)的各種常用的或者熱門組件。最后,我們也會(huì)嘗試去對(duì)接更多的公有云基礎(chǔ)設(shè)施,使得用戶在公有云上使用 Kitex 時(shí)能夠擁有愉悅的體驗(yàn)。