2020年8月14日-15日,GIAC 2020 全球網際網路架構大會於上周五正式在深圳開幕。
GIAC(GLOBAL INTERNET ARCHITECTURE CONFERENCE)是長期關注網際網路技術與架構的高可用架構技術社區和msup推出的,面向架構師、技術負責人及高端技術從業人員的年度技術架構大會,是中國地區規模最大的技術會議之一。
第六屆GIAC,將從網際網路架構最熱門的前沿技術、技術管理、系統架構、大數據和人工智慧、移動開發和語言、架構相關等領域,分享有典型代表的技術創新及研發實踐的架構案例。
在Go語言專題,騰訊高級工程師利開園發表了題為《基於TarsGo的微服務技術架構實踐》的主題演講。
以下為演講實錄:
我今天分享的主題是基於TarsGo的微服務技術架構實踐,先做一下自我介紹,我的工作經歷很簡單,畢業之後一直在騰訊,最近5年多時間裡做了三個方向的工作。
第一部分是在2015年,當時docker比較火,了解K8S的人也不少,但是當時大家都覺得還像玩具,不適合生產環境使用。我所在團隊就自己做了一個容器雲, 主要是將Tars等服務跑在docker容器上,自己實現了比k8s多很多的自動調度能力,前幾年我們想把這個方案開源,但是發現k8s已經成了業務標準,還好當時微服務還沒有業務標準,所以我們開源了Tars。
第二部分是在2017年左右,那時候Go也開始火起來了我當時實現的Tars的Go語言版本的部分功能。
第三部分是2019年,很多人開始談serverless和雲原生,我也轉到做騰訊云云開發這個產品,我們後臺團隊是用TarsGo來開發。
你們有沒有發現,我做了幾個方向,好像都是追趕業務潮流,但不變的是都跟Tars有關,從基礎構架,到應用平臺,再到平臺的使用者。也因為這些經歷,在講Tars時可能會有不一樣的角度。做平臺或技術框架的時候可能會去追求酷炫的技術,但是變成使用者之後,才不會去關心技術有多牛逼,而是要看某個組件或平臺好不好用,穩不穩定,能不能解決我們業務上的問題。
今天的分享會分三個部分:
1)Tars的核心功能,2)TarsGo的特性及性能優化實踐,3)Tars與K8S的結合方案。
在介紹之前,我們先來分析一下基本每個軟體開發團隊都會遇到的業務規模增長帶來的通用技術挑戰。
如果你只是做一個個人博客網站,每天幾百個uv,這個挑戰不大,隨便找臺伺服器託管就行,但是如果每天流量增長到幾百萬幾個億,也就是這裡要說的第一個挑戰,用戶流量的增長時,原來單臺服務的架構無法支撐,這個時候就需要考慮分布式的架構,需要讀寫分離,需要高性能的緩存。
第二個挑戰是為了滿足更多業務需求,會招聘更多的軟體研發人員。人多也會帶來新的問題,對老闆來說,除了多發工資就是要想辦法最大化的利用員工的價值和剩餘價值,那對我們研發來說,當然也希望好的生產工具,更好地滿足老闆的需求。具體到項目開發中,就需有通用的接口協議來通信,有支持多種開發語言的框架,這樣可以讓不同開發語言的團隊成員也能協作完成同一個項目,然後精通PHP的同學和精通Golang的同學也不花時間去爭論哪個才是世界上最好的語言。
第三個挑戰是伺服器數量的增長,要管理好數量龐大的機器並不簡單,假如你有1w臺機器,每天可能都會有幾臺機器死機了,有幾臺磁碟壞了,還有可能整個機房都掛了,所以這就需要有機器故障或機房故障自動恢復的解決方案。
第四個是業務服務數的增長,對應的是業務邏輯複雜度的增長,要管理好大量的業務服務也是一大難點,開發的時候很爽,搞了很多的微服務,但是後面漫長的運維過程怎麼辦,要擴縮容、要監控,要排查問題,這就需要一個功能完善的微服務治理框架來提高運維效率。
上面列的這些挑戰在騰訊有大量的業務產品都遇到過,經過多年的沉澱也積累了比較多經驗,其中今天介紹Tars是最有代表性的對外開源項目。
那Tars有哪些能力呢?我認為有兩部分,一部分是跟開發有關, Tars提供了IDL和代碼自動生成的開發框架,開發只需要關心業務邏輯就行了,不用關心用是用tcp還是ucp協議,也不用知道依賴服務的IP或埠,Tars目前支持cpp/golang/php/nodejs/java 5種語言,更多語言也在支持當中。
另一部分跟運維相關,支持服務可視化管理,支持無損變更,配置管理中有版本管理和配置引用能力,日誌、監控和調用鏈是Tars默認就支持的能力,Tars的Set模型為運維提供了方便的多集群管理能力,另外還有過載保護和容災容錯能力。之前TARS開源的版本沒有自動擴縮容能力,最近我們開源了tars跟k8s整合的方案,補齊這個能力,這也是待會我會具體介紹的方案。
跟TARS類似的微服務框架也有不少,這裡有列了幾個影響力比較大的做下比較, GPRC是最熱門的RPC框架, 但是本身它不具有服務治理能力, 另一方面但像Spring Cloud或Dobbo有比較完善的服務治理能力, 最近也開源也dubbo的golang語言版本,之前的話感覺還是以java語言為主。service mesh現在很火, 被稱為下一微服務架構, 它將業務邏輯與底層通信分開, 跟語言無關, 也就是支持多語言, 但是在工業級應用上時間還不長, 不算特別成熟。
今天介紹的TARS跟service mash有很多類似的理念, 比如像負載均衡這樣通用的功能, 不應該在業務層實現, 而是由公共的基礎設施層來實現, 相比service mash是最近幾年才提出的概念, TARS從2008年開始在騰訊多個核心業務上使用, 是一個支持多語言且具有完善的服務治理能力的微服務框架。
剛提到應用,TARS在開源之後,也跟很多公司做過交流。因為時間關係,我會先只選幾個tars能力做更多的介紹。
我們先回到一個簡單的場景,比如服務A要調用服務B,服務B有多個節點,A怎麼選擇哪個節點來調用呢?TARS有幾個種負載均衡方式,默認是輪詢,如果想用hash方式調用,只需要配置一下就行,不用額外的開發。
另外,當服務B的某個節點異常了,TARS默認有提供了一個熔斷算法,會自動把異常的節點屏蔽掉,儘量減少節點異常導致的請求失敗數量,然後再嘗試請求,如果節點恢復了,再把之前異常的節點加回來。
還有一種異常場景是請求量過大導致服務過載,tars可以配置最大並發處理數來實現過載保護,避免服務雪崩。
另一個場景服務A要調用B,A要知道服務B的地址。最簡單的方式是在A服務寫死B的IP,但是B服務的IP可能會因為擴縮容而變化,這樣B服務的變更要修改A的代碼,顯然是不可維護的,優化方案是把IP寫到配置文件裡,這個方案B服務變更時仍然需要運維來修改。所以基本上大家都會用第3種名字服務方案,服務地址自動註冊,被調用服務變更時不需要客戶端變更。但是名字服務在多集群場景下仍然有缺陷,多集群是很常見的場景,比如一個toc的產品有海量用戶,可以按區域上海、北京來劃分集群,分別服務不同區域的用戶,這樣可以比較好的隔離故障,比如上海有問題時不會影響到北京的用戶。
傳統的分集群有兩種方式,一種是每個集群都用一套名字服務和公共服務,每新增一個集群都會增加一定運維成本,另一種是多集群共用一套名字服務,不同集群的同一個服務就要用不同的名字,就需要去管理每個集群服務的名字,也帶來非常高的運維複雜度。
針對這個兩難的問題,TARS提供了SET的能力。
我認為SET模型是TARS裡面最精華的其中一部分能力, 它提供了邏輯上的分隔服務流量的功能, 如果某個服務開啟了SET, 它只能調用到相同SET的服務,而如果服務沒有開啟set,而所有set的服務都可以調用到,另外還可以用通配符,讓某個set給特寫的幾個set調用,不需要改代碼或業務配置,運維就可以靈活地配置set來管理服務的調用關係。
機房容災也是很通用的業務需求,從個人在騰訊經驗上看,基本上每年都有那麼一兩次機房故障的場景,前幾年有騰訊在汕尾的機房因為洪水可能要整個斷電,當然最常見的原因有光纖被挖斷。當整個機房不可用的事故出現時,如果一個產品的服務只部署在一個機房裡, 會導致整個產品不可用,這個老闆肯定接受不了。所以 考慮容災同一個服務就要多機房部署, 一個機房不可用時還能正常對外提供服務。
這樣做也帶來了新的問題, 服務要跨機房調用, 會帶來延時上的增加。
要做機房容災,但是又不想增加延時怎麼辦?TARS提供了自動區域感知的能力, 客戶端在多機房可用的情況下只調用本機房的服務端節點,如果沒有本機房可用節點才會有跨機房的調用,這個功能只在控制面開啟, 不用修改業務代碼, 在具有容災能力的同時, 沒有增加延時, 運維也變得更簡單。
對於微服務來說,監控、日誌和調用鏈可以說是標配。因為微服務的特點,如果出問題之後都要登錄到機器上排查,效率低有時候也不太現實。一般都是通過監控來觀察服務的運行狀態,狀態異常時有告警。通過調用鏈來了解複雜系統的調用關係以及服務架構,在出現超時等問題時可以快速定位到出問題的模塊,然後是可以利用日誌來分析異常的原因。然後監控、日誌和調用鏈在使用時有相互重合的地方,比如也可以通過聚合日誌來實現監控metrics的功能,但是在數據量大的時候效率會低一點,所以一個成熟的微服務系統還是要這三個完整的能力。
TARS默認會提供tarslog/tarsstat/tarsproperty這樣的基礎服務,也可以整合比較成熟的開源方案,比如監控有Prometheus,日誌有ELK,調用鏈有zipkin與jaeger。
接下來是我要介紹的第二部分,TARS的GO語言版本的RPC框架,主要會介紹性能優化相關的經驗。
TarsGo有三個優點,分別是tars的服務治理能力、go的優勢以及TARSGo的性能優化。TARSGo是基於tars協議實現的rpc框架,會默認繼承tars的服務治理能力,比如負載均衡,容災容錯能力,以及tars的名字服務。
第二個是go語言的優點,golang有豐富的標準庫,很多時候不用重複造輪子,對於新入門的同學上手也比較快。相比於python/C或其他語言,在開發效率和程序的運行效率上有比較好的平衡。同時支持靜態編譯和跨平臺編譯給研發提供了不少的便利,比如可以在mac上開發和編譯,然後上傳到linux的伺服器運行。我認為go語言最大的優勢是並發和異步編程,go協程以及channel的數據結構大大簡化了並發編程的複雜度。
TarsGo的第三個優點是性能,接入來會詳細介紹。
性能對微服務來說是很重要的,因為一個用戶請求過來可能會經過十幾個甚至更多服務,高性能的RPC框架可以減少微服務帶來的耗時增長問題,這一點TARS是做得不錯的。
之前我們針對常用的PRC框架及不同開發語言做了性能壓測比較,從測試結果上看,TARS C++和TARS GO的整體性能是最好的,小包場景是grpc 的5倍以上。
為了達到這個目標,tarsgo有做了比較多的優化,這些優化經驗對使用go開發的同學應該也有一定參考價值。
第一個是timer的優化,我們知道每次rpc調用都有一個超時時間,需要用計時器來實現限制超時的目標,如果並發量很大,那就需要大量的計時器。利用golang的pporf工具生成火焰圖可以發現這個創建計時器的性能問題。所以我們實現了一個時間輪算法,可以將創建計時器的時間複雜度從log(n)下降到O(1),缺點是
此外,還有的優化點有net包的daedline的設置性能優化,針對bytes.Buffer頻繁申請優勝sync.Pool優化,針對goroutine大量創建問題使用協程池優化。經過以上優化後達到了前面提到的效果。
接下來介紹TARS與K8S整合的方案。
當前TARS提供了名字服務、負載均衡、過載保護、區域感知等較完善的服務治理能力,但是開源的TARS版本並沒有自動調度功能,服務上線、擴容等操作都需要填寫IP和埠。
這個會帶來2個問題:
一是服務的容量管理,對於無法自動擴縮容的服務,如果預留的資源少,在流量突發可能會導致服務過載,而預留更多的資源則會導致浪費。
二是服務混合部署,如果多個服務混合部署,可能會相互影響,不混合部署,微服務太細了,機器資源也會大量浪費。
針對上述問題最自然的想法是希望能利用K8S的調度能力來實現TARS服務的自動擴縮容。但是問題又來了,TARS的不少能力(如名字服務等)與K8S重合,要整合在一起並不容易。
在K8S生態下,Istio是最熱門的微服務解決方案,是否應該要放棄TARS,改成Istio會更好呢?這裡分析了會遇到的問題。
第一個就要要服務代碼改造,這個是很現實的問題但也不一定充分的理由不用iostio。
第二個用istio的問題是,對tcp的長連接只能是連接級別負載均衡,而tars是請求級別,有更好能力。
第三是tars客戶端有熔斷能力,讓服務更健壯。
第四是tars有set/idc分組的名字服務,在多集群多機房部署時運維更簡單。
第五是支持埠自動管理,業務代碼裡不用關心埠,避免在服務多時管理埠的麻煩。
第六是更好的支持日誌、監控和調用鏈,比如istio雖然也支持調用鏈,但是還要在業務代碼裡配合透傳調用鏈的上下文,一個公共邏輯需要在兩個地方來實現,直覺告訴我這裡容易有問題。
第七是相比istio還在快速迭代,在落地上也有較高成本,TARS經過大量業務服務的驗證,是比較成熟穩定的。
所以綜合考慮有必要繼續使用TARS,將TARS改造成支持在K8S中自動調度。
而實現的主要難點是整合重合的功能,Tars沒有的能力是資源自動調度、docker鏡像管理和網絡虛擬化,TARS有但是K8S沒有的能力是高性能RPC框架、日誌、監控、調用鏈等服務治理能力,服務部署、版本發布與管理、名字服務和配置管理是tars和k8s都有的功能。
所以需要對重合的功能進行取捨,因為k8s支持服務自動調度,是業務的主流標準,所以服務部署只能選k8s,而k8s使用docker鏡像包含基礎環境依賴,是更好的版本管理方案。名字服務部分,TARS支持關流量,支持SET/IDC分組,客戶端容錯等k8s沒有的功能,所以有必要保留tars的名字服務,而tars的配置管理支持版本管理,應用/set/服務級配置引用與合併,會比k8s的configmap方案更適合在生產環境使用。
在分析了K8S與TARS的重合功能之後,我們最近實現並開源了這樣的解決方案。沒有對k8s進行改造,會使用原生的k8s能力,在TarsRegistry增加幾個接口,用於服務的自動註冊、心跳上報和節點下線,對於死機場景,會自動刪除長期未上報心跳的節點。另外新實現了一個命令行工具,用於在容器中管理服務,實現生成啟動配置、服務註冊和心跳上報等功能。該方案的優點有是保留TARS原生的開發框架和服務治理能力,而服務部署與調度則使用K8S。
k8s的pod與tars服務的生命周期是強關聯在一起的,在pod啟動後,默認先啟動tarscli supervisor進程來生成服務啟動配置,拉起並監控服務狀態,將服務同步到k8s的名字服務中。通過hzcheck命令來保證pod與服務狀態。在pod退出時,通過prestop子命令來自動下線節點。
那如何把一個TARS服務部署到k8s上呢?首先要部署tars的基礎服務,包含資料庫,名字服務,web管理頁面和服務治理相關的基礎服務,在我們的開源項目裡,有提供了yaml可以直接在k8s上快速把這些服務部署好。然後業務服務的部署也可以通過yaml文件來實現,這裡有列舉了兩個示例,具體使用可以參考開源主頁上的說明文檔。在部署完成之後,可以通過tarsweb的頁面看到服務的節點列表。
在管理頁面上,默認會有服務監控曲線,可以直觀看到服務的調用量,耗時,異常率和超時率。另外tars是支持無損變更的,可以從監控上看在鏡像變更期間,業務的監控曲線是沒有波動的。
利開園右
最後談一下TARS與雲原生:
我認為老闆會喜歡雲原生,因為不用招很多人開發和維護基礎架構,可以直接使用開源方案,然後在雲廠商託管服務。
而TARS本身具備豐富的服務治理能力,可以提高開發效率、減少運維成本,TARS與K8S的結合方案,可以在不改動業務代碼或以很小的成本就可以將服務部署到適合的雲廠商,在減低基礎設施運維成本的同時,也能避免與雲廠商綁定。所以推薦大家使用TARS+K8S的雲原生解決方案,這個方案目前已經在github上開源,觀察大家去關注。在github上搜索k8stars就可以找到。
這就是我的分享,謝謝!