Linux高性能網(wǎng)絡編程十談
《Linux高性能網(wǎng)絡編程十談》十篇技術博客已經(jīng)寫完幾個月了,想著還是寫點總結來回顧一下這幾年的工作,說來在鵝廠兩次經(jīng)歷加起來也快8年,雖然很多時候在做螺絲釘?shù)氖虑椋贿^細想自己的高性能架構演進的經(jīng)歷,從參與,優(yōu)化到最后設計架構,從中還是學到了很多東西。
1、提前設計還是業(yè)務演進?
大家應該都經(jīng)歷過項目從0到1的過程,我想提一個問題:很多時候的架構是隨著業(yè)務演進還是提前設計呢?
可能有人看過相關的架構書籍,書上大多都支持架構是隨著業(yè)務演進的,但是也有很多架構師認為架構就應該被提前設計,這里我先不給出結論,先從我所經(jīng)歷的架構演進來尋找答案。
2、從PHP到C++
2.1 簡單的PHP架構
PHP作為一門簡單便捷的語言,在大廠各個部門應該都有身影,當時我工作用的兩種語言:C++和PHP,使用PHP開發(fā)功能很快,而且有很多成熟的庫,因此組成了經(jīng)典的nginx + php-fpm + memcache架構。
php架構
在當前架構下單臺8c8g機器支持1000qps問題不大,所以對于業(yè)務當前1wqps都不到,顯然多堆幾臺機器就可以支持了。對于緩存層的設計,在redis還不是發(fā)展很好的情況下,memcache是當時緩存組件的主流,而且對于業(yè)務和對接PHP簡單。但是隨著業(yè)務的發(fā)展,按照當時計算曲線可能一年以內會到5wqps,用nginx + php-fpm + memcache架構是不是合理?經(jīng)過討論后的目標是服務端高性能,于是開始了高性能的探索之旅。
2.2 多進程的框架
當時在PHP實現(xiàn)高性能服務端框架上也有一些方案,通過PHP插件功能將Server的功能嫁接到腳本語言上,從而實現(xiàn)高性能。比如現(xiàn)在PHP的swoole就是從當時發(fā)展起來。
php-server
不過這里會面臨一些需要解決的問題:
- 熟悉PHP擴展的使用場景,防止踩坑
- PHP本身使用上的內存泄漏問題
- 出現(xiàn)問題時的排查成本,比如一旦出現(xiàn)問題,我們有時候需要去了解PHP源碼,但是面對幾十萬行代碼,這個成本是相當高
- PHP使用上簡單,這個實際相對的,隨著Docker的崛起,單機時代必然會過去,PHP的生態(tài)是不是能支持
- ...
基于以上思考和對業(yè)務發(fā)展的分析,其實我們自己實現(xiàn)一個或者使用現(xiàn)有的C++框架實現(xiàn)一套業(yè)務層的Server應該更合理,于是經(jīng)過考慮采用了公司內的SPP框架,其架構如下:
SPP框架架構
可以看出SPP是多進程架構,其架構類似Nginx,分為Proxy進程和Worker進程,其中:
- proxy進程使用handle_init執(zhí)行初始化,handle_route轉發(fā)到指定執(zhí)行的worker處理進程,handle_input處理請求的入包
- worker進程使用handle_init執(zhí)行初始化,handle_process處理包和業(yè)務邏輯并返回
使用C++的架構后,單機性能直接提升到6kqps,基本已經(jīng)滿足性能上的要求,可以在相同的機器下支持更多的業(yè)務,看似已經(jīng)可以將架構穩(wěn)定下來了。
2.3 引入?yún)f(xié)程
使用C++在性能上已經(jīng)滿足需求,但是在開發(fā)效率上卻存在眾多問題,比如訪問redis,為了保持服務的高性能,代碼邏輯上都采用異步回調,類似如下:
...
int ret = redis->GetString(k, getValueCallback)
...
其中getValueCallback就是回調函數(shù),如果出現(xiàn)很多io操作,這里回調就會非常麻煩,即使封裝為類似同步方式,在處理上也非常麻煩,當時還沒有引入std::future和std::async。
另一方面基于后續(xù)的qps可能到10~20w量級,協(xié)程在多io的服務處理的性能上也會更有優(yōu)勢,于是開始了協(xié)程方式改造,將io的地方全部替換為協(xié)程調用,對于業(yè)務開發(fā)來說,代碼上就變成了這樣:
...
int ret = redis->GetString(k, value)
...
其中value就是可以直接用的返回值,一旦代碼中有io的地方,底層就會將io替換為協(xié)程的API,這樣阻塞的io操作就全部變成同步化原語,代碼結構和開發(fā)效率都提升不少(具體的協(xié)程實現(xiàn)可以參考系列文章的《Linux高性能網(wǎng)絡編程十談|協(xié)程》)。
協(xié)程
從架構上還是沒有太多變化,多進程+協(xié)程的方式,支持著業(yè)務發(fā)展幾年時間,雖然性能上沒有指數(shù)增長,但是對于高性能探索和沉淀上已經(jīng)有了更多經(jīng)驗。
3、云原生
業(yè)務繼續(xù)發(fā)展,而工程師總是在追求最前沿的理念,云原生作為最近這幾年熱門的技術點自然不會放過,但是在進入云原生之前,如果你的團隊沒有DevOps開發(fā)理念,這將是一個痛苦的過程,需要對架構設計和框架選擇償還技術債。
3.1 實施DevOps理念
以前做架構考慮高性能,隨著對于架構的理解,發(fā)現(xiàn)高性能只是架構設計的一個小領域,要想做好一個架構,需要更多的敏捷流程和服務治理理念,具體考慮的點總結如下:
- 持續(xù)集成:開發(fā)人員一天多次將代碼集成到共享存儲庫中,并且對代碼的每個孤立更改都將立即進行測試,以檢測并防止集成問題
- 連續(xù)交付:連續(xù)交付(CD)確保可以隨時發(fā)布在CI存儲庫中測試的每個版本的代碼
- 連續(xù)部署:這里包括灰度部署,藍綠發(fā)布等,目的是快速迭代,經(jīng)過相對完整的集成測試,就可以灰度驗證
- 服務發(fā)現(xiàn):將服務作為微服務化,簡化服務之間的調用
- RPC的框架:追求高性能的Server框架,也需要考慮限流,熔斷等基礎組件的支持
- 監(jiān)控系統(tǒng):集成Promethues,OpenTracing等功能,能在敏捷開發(fā)流程中快速發(fā)現(xiàn)線上的問題
- 容器化:為了環(huán)境統(tǒng)一,同時提前考慮云原生場景,容器化是開發(fā)過程中必不可少的
- ...
DevOps
到這里會發(fā)現(xiàn),簡單的高性能Server已經(jīng)作為架構追求的目標了,于是需要重新調研并設計架構,以順利實施DevOps的理念。
3.2 多線程
基于DevOps,結合上面的C++的Server框架,發(fā)現(xiàn)多進程已經(jīng)不能滿足架構的需求,原因如下:
- 多進程與Docker容器的單進程理念不相符
- 工作進程負載不均,如何更利用多核
- 與監(jiān)控系統(tǒng)有效的對接
- 業(yè)務配置重復加載,需要重新適配配置中心
- 用多進程做有狀態(tài)的服務不是很合理
- ...
業(yè)務也發(fā)展到百萬QPS,為了更好的服務治理和服務調用成本,不得不考慮另外的架構:
(1)調研gRPC
gRPC
gRPC是多線程RPC Server,有成熟的生態(tài),各種中間件,支持多語言等,對于從0到1開發(fā)的業(yè)務是一個不錯的選擇,但是對于業(yè)務遷移卻面臨挑戰(zhàn),比如開發(fā)自己的中間件適配服務發(fā)現(xiàn),配置中心等,改造協(xié)議按照自定義編解碼,如何結合協(xié)程等,因此對于部分業(yè)務滿足,但是還需要更好的結合公司內組件的RPC Server。
(2)使用tRPC
剛好公司內正在開發(fā)tRPC,經(jīng)過調研發(fā)現(xiàn)基本滿足需求,于是在tRPC的C++版本剛剛發(fā)展初期就嘗試適配我們的系統(tǒng),經(jīng)過一系列的改造,高性能的RPC框架在業(yè)務系統(tǒng)中遷移和使用了,其中tRPC的架構:
https://trpc.group/zh/docs/what-is-trpc/archtecture_design/
基于上述的考慮和業(yè)務的發(fā)展,于是開始嘗試以高性能為基礎,將RPC Server框架統(tǒng)一,以適配后續(xù)RPC多樣化場景,于是實現(xiàn)一套適配我們的業(yè)務系統(tǒng)的RPC Server的基本框架:
新架構
3.3、走向k8s
經(jīng)歷了上述選型和改造后,我們的服務在遷移k8s過程中,按部就班對接就可以了,服務不需要經(jīng)過太多的改造可以在其平臺上運行,對接的各個平臺也是可以完整的支持。
看似去追求更新的技術等著下一個風口就可以了?實際這個時候反而挑戰(zhàn)更多了,由于在云上的便捷和遷移服務架構的無序擴張,導致業(yè)務服務和邏輯層次越來越多,同時一個服務依賴的下游鏈路越來越長,雖然我們的框架支持鏈路跟蹤,但是鏈路越長,對服務的可控性和穩(wěn)定性就越來越差,反而浪費更多的人力支持日常ops。
怎么辦?...
是不是要合并業(yè)務邏輯,將架構簡化?這里面臨的問題是業(yè)務邏輯復雜情況下往往周期很長,而且從成本角度考慮比較高,收益并不會很大
是不是重新開發(fā)的新的架構,將腐朽的保持原樣或者拋棄,使用新的架構來適配下一步的發(fā)展。
以上的方案其實需要在業(yè)務層去權衡,如果本身業(yè)務簡單,業(yè)務邏輯合并周期短,建議采用第一種,如果業(yè)務復雜,風險很高,如果開始考慮的架構不合理,就應該采用新的架構。
如果你也有類似的經(jīng)歷,你會發(fā)現(xiàn)在這個過程中我們又回到了原點,以前做高性能是為了服務能承載更多性能,簡化調用鏈路,提升開發(fā)效率,走到云原生時代,似乎又需要重新走一遍類似的路徑,始終沒法擺脫服務端的束縛。
4、嘗試Serverless
4.1 什么是Serverless
Serverless解釋是無服務器計算,開發(fā)者實現(xiàn)的服務端邏輯運行在無狀態(tài)的計算容器中,無需要關系資源,使開發(fā)者更聚焦在業(yè)務邏輯,而減少對基礎架構的關注,業(yè)界公認的Serverless計算的準確定義應該為"FaaS+BaaS",即Function-as-a-Service同Backend-as-a-service的組合。
既然云原生時代我們無法擺脫服務端的束縛去做架構,那在理想情況的Serverless是不是能做到:
Serverless
- 高性能是需要考慮的點,但是服務不再以完全追求高性能為目標
- 降低開發(fā)成本和運營成本
- 支持服務的橫向擴展和縱向擴展
- 對于開發(fā)者最好是省去CI/CD流程,但是平臺將這些流程作為發(fā)布的前置條件
- 云上的資源拿來就可以直接用,只需要評估容量即可
- 縮放靈活,可以減少資源的使用
- 考慮服務的安全性
- ...
4.2 基于微內核的云函數(shù)
從最開始的PHP到C++的框架迭代,一直在圍繞高性能,服務治理等在優(yōu)化,但是要結合Serverless,簡化架構層級,于是萌生實現(xiàn)一套基于微內核的云函數(shù)架構。
其實參考AWS Lambda的技術路徑也是如此,他們正在嘗試輕量化容器和microVM去解決慢啟動問題,而我們使用微內核解決業(yè)務邊界和安全問題,因為場景的是可以枚舉的,不需要做到足夠非常通用化,于是形成如下架構:
新架構
這里微內核主要做的事情有兩個:
一種是實現(xiàn)業(yè)務層代碼解析,比如對于JavaScript,我們可以通過自定義的解析引擎將代碼加載到微內核中,調用基礎庫和各種抽象API層,相當于實現(xiàn)了一個簡單版本的NodeJS,但是整個框架的安全和功能都是在可控的范圍內;
一種是自定義其他語言,比如定義支持golang,那么MicroVM負責將golang層的代碼和框架代碼打包在一起,然后編譯構建為鏡像,不過我們正在考慮支持更多的語言,這里嘗試使用Webassembly,這樣能簡化鏡像構建成本,直接MicroVM動態(tài)加載wasm文件即可;
使用基于微內核的云函數(shù)的優(yōu)點是,這里對于開發(fā)者簡單,真正只需要做業(yè)務邏輯,不需要考慮底層的高性能(現(xiàn)在已經(jīng)支撐百萬級QPS),更不需要關注DevOps某些流程,提升了開發(fā)效率,當然也有缺點,就是由于MicroVM偏向業(yè)務層抽象基礎功能,所以通用性不強,但是對于現(xiàn)有或者后續(xù)的業(yè)務形態(tài)的發(fā)展已經(jīng)足夠了。
5、總結
寫到這里,再來聊聊這個問題:提前設計還是業(yè)務演進?大家應該都有自己的答案了。上述也是我從開發(fā)者小白追求如何寫好一個高性能Server,到追求新的架構技術,到最后思考高性能,新技術到底是為什么服務的一段總結。本文屬于《Linux高性能網(wǎng)絡編程十談》附加篇,沒有具體談高性能的技術細節(jié),但是我覺得這句話比技術細節(jié)可能更重要:"架構需要大簡至道"。