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