您的位置:  首頁 > 技術 > go語言 > 正文

Go 生態下的字節跳動大規模微服務性能優化實踐

2022-08-22 10:39 https://my.oschina.net/u/5632822/blog/5565821 字節跳動云原生 次閱讀 條評論

Go 是一門很有特色的編程語言,已經被廣泛應用到不少領域,隨著使用場景的發展,一些性能相關的問題也開始逐漸暴露出來。本次分享將以字節跳動的性能優化工作為例,介紹基于 Go 生態的微服務體系下,分析系統性能、優化不同層次軟件以提升運行性能、提高資源使用效率的一些實踐和經驗,會特別介紹在 Go 語言 SDK 側的一些優化工作。

項目背景


微服務是一種將復雜應用拆分為微小的服務單元,每個服務單元都可以獨立升級甚至替換,從而實現快速交付和迭代的文化。

字節跳動是對微服務技術使用得非常極致的企業之一:伴隨業務的迅速擴張,微服務以其靈活迭代、高可擴展、高度兼容的特性,幫助字節跳動快速建立起一套基礎設施系統,滿足服務水平擴縮容、業務高速發展變化和不同團隊靈活協作的需求。時至今日,字節跳動的在線微服務類型數量已超過 10 萬。

但作為一家快速發展的企業,字節特殊的內部業務場景也對微服務落地提出了一些挑戰,如:

  • 大規模:一是集群規模非常大,二是業務的領域比較廣泛,業務領域涵蓋了短視頻、內容推薦、電商等各類場景;
  • 快迭代:一是演進速度快,很多新特性被很快發布出來,二是新技術演進快,開發者樂于學習使用新技術;
  • 多語言:字節內部的服務以 Go 語言為主,占據 55% 以上,同時兼容了許多其它語言;字節早期創業階段的微服務主要是使用 Python 進行編寫,后期逐步轉到 Go 語言。

從編程語言的角度看,Golang 能在字節內部得到大規模應用,離不開它對于微服務的幾大優勢

  • 簡單易用上手簡單,很多人只需花費一周左右就能開始獨立承接任務;
  • 高并發Go 語言天然適合 I/O 密集場景,支持高并發,能更好地利用多核心 CPU 的能力,很適合編寫包含大量網絡通信的微服務系統;
  • 性能合適Go 語言編譯速度很快,程序啟動也很迅速,同時具有還算不錯的運行時性能。

當然,世上沒有完美的事物。從性能角度來看,微服務也為字節跳動基礎架構團隊帶來了兩個性能代價:通信代價,不同服務之間通過網絡進行通信,用戶必須壓縮數據包,將其變成與平臺、語言無關的協議發送出去,由對方解碼之后使用,因此會造成通信上的開銷。特別是在 Service Mesh 被大規模推廣和使用后,通信需要消耗更多的資源;治理負擔,微服務架構是一個松耦合架構,其要求各個微服務自發進行演化生長。如果組織缺乏自上向下的管理,很容易導致微服務野蠻生長,造成治理負擔。

?Go 服務性能分析


集群性能優化一般有如下思路:收集原始性能數據——建立指標體系——跟蹤監控異常/手動分析——定位性能瓶頸——優化方案。

需要注意的是,只做一次優化是遠遠不夠的,我們更希望將相關最佳實踐做成系統或工具,日常運行下去,在字節內部,我們的做法是構建統一性能平臺。

?收集原始性能數據

原始數據共有三種來源,一是業務數據,包括 QPS、RT 等;二是系統數據,包括 CPU、內存等;三是運行時數據,包括 PProf 和 FuncProf 數據。

其中,PProf 是通過采樣方式,在一秒鐘內默認打 100 個點,如果踩到了一個點就相當于占了 1% 時間。字節跳動基礎架構語言團隊在內部的 Go 發行版增加了 FuncProf 的功能,開始執行時進行計時,停止執行時按下暫停,最后將數據合并。下圖展示了數據的流向,我們需要從業務集群拉取業務數據,同時可能還需要和監控系統、運維系統進行交互。

?建立指標體系

獲取原始數據之后,我們需要依靠指標體系對數據進行分析和判斷。指標體系能夠幫助我們揭示集群性能特征,回答基本問題(比如性能對不對,是否變差)。同時,指標的選擇至關重要,不同的指標選擇會導致完全不同的結論。

字節跳動基礎架構語言團隊秉承著指標選擇的規范——保證指標的可擴展性和可迭代性,弱指標強于沒指標。該指標可能并不足以完全解釋數據,但是能揭示部分問題也比沒有指標強。

當衡量 CPU 時,業界有很多成熟的算法,比如將 workload 的使用關系和資源掛鉤,這需要該領域的專家協助執行,我們目前采用的方式是單核 QPS。當然,不同類型服務的請求特征是不一樣的,比如打包發送視頻業務和賬戶查詢業務肯定有完全不同的請求特征;而 CPU 核心的差別更大,芯片技術一直在高速發展,不同型號的 CPU 單核性能可能相差數倍。

然而我們認為“表達能力偏弱的指標強于沒有指標”。并且在進行比較時,我們會避免絕對值的比較,盡量采用相對值進行比較,從而更充分地利用原始指標。舉一個例子:

上圖顯示了一天內單節點 CPU 的利用率變化情況,變化幅度大,并且波峰和波谷的差距很大。那么圖中哪個時間段對性能分析是有意義的?我們會更關注 T1 時段,即峰值 CPU 利用率。團隊將峰值的數據采集完之后,會在集群維度進行一定程度的歸一化處理,利用規模效應磨平單點上的偏差。

圖中可以看到處理結果呈現單核 QPS 趨勢,在實際應用中,這個指標很大程度上能反映系統的性能特征。當然,我們也在嘗試更多精細化的分析工作,歡迎對這方面感興趣的朋友加入我們團隊共同探索。

?性能追蹤

性能追蹤方法包括自動和手動兩種方法,自動方法是指代碼主動識別問題,手動方法需要人工操作去觸發。其中,自動發現問題分為兩個維度:單機維度和集群維度,我們可以在單機和集群維度上檢查是否存在問題并做出響應。

如下圖所示,字節內部使用 Agent 在后臺自動檢測單機是否存在性能瓶頸,如果發現問題,它會通知性能平臺及時采樣案發現場數據,由此我們可以在單機維度抓取性能下降的數據。

?定位性能問題

在分析完性能問題之后,我們需要對具體的組件進行修改。我們的思路是為性能平臺用戶提供自頂向下的逐步鉆探的分析流程。

我們在單機收集數據,包括 CPU 利用率、代碼的 Stack 、Frame 等信息,然后將它們打散,在不同的維度形成不同的組合并展示。如下圖所示,首先我們在集群維度展示一個熱力圖。

該熱力圖基于整個業務線的角度,將許多的服務放在一起分析哪條業務線消耗資源最多;同時,我們也會在服務層匯聚一個 profiling 分析;最后我們基于兩個角度在組件層定位問題,一是基于平臺角度去看指標時是一個自底向上不停組合出不同指標的情況;二是用戶在分析時是一個自上而下的鉆探視圖過程。

?優化方案

軟件類型一般劃分為業務軟件和系統軟件。其中,SDK/三方庫屬于業務軟件,基礎庫、語言運行時、容器/OS屬于系統軟件。業務代碼的特征是:寫很容易,修改很頻繁,它的優化并不具備普適性;系統軟件的特征是修改和維護比較費勁,優化具有普適性,可以被推廣到很大范圍,絕大部分業務都可以受益;同時修改業務軟件的收益一般大于修改系統軟件。

字節內部的優化方案是體系化優化,在單節點中從上到下,對業務層、基礎庫組件、編程語言每個層次進行優化,跨節點優化會涉及合并部署。某個性能優化項目數據顯示,通過我們的優化手段,CPU 資源大約節約了 19%。

?

?Go 服務性能優化


本章節將具體展示字節內部的 Go 服務性能優化手段和措施,涵蓋了從業務到語言的實踐過程。

業務層優化?

業務層優化面臨的挑戰主要有兩點:

  • 服務間的差異性巨大:比如推送文字服務和推送視頻服務的業務代碼之間存在很大的差異,難以出現通用優化技術;

  • 工具如何更加有效右下圖展示了基本的業務代碼分析思路,然而事實上大家工作重心不同,并不能要求所有同學都按同一個套路思考;這時候打造一套好用、高效的工具,降低性能分析的心智負擔就很重要了。

關于業務層優化,這里總結了幾點比較容易獲取收益的優化經驗:

  • 減少復雜度不過度設計,簡單而直接的做法往往會更高效,比如減少網絡通信次數和數據量;

  • 重視編碼規范問題如果能夠在項目前期得到解決,將會帶來更大的收益;

  • 升級組件到“比較新”的版本在控制好穩定性的前提下,新版本的軟件一般會帶來更好的性能,比如升級 Go v1.17 版本對于 calling convention 的優化具有一定的效果。

這里舉一個業務層優化案例:A/B 測試。這是一種用戶體驗研究方法,被廣泛應用于字節跳動產品命名、交互設計、推薦算法、用戶增長、廣告優化和市場活動等各方面決策上。

一開始我們并不知道 A/B 測試是瓶頸,只是性能平臺按照從業務線到組件的方式下鉆,會報告出這個組件消耗大量資源,優化之后可能帶來可觀的收益。

通過分析這個組件的關鍵特征數據,A/B 測試的參數規模引起了我們注意。下圖展示了在較短時間內某個集群上 A/B 測試參數個數的變化情況。隨著時間的推移和業務的增長,這個指標發生了巨大變化,同時伴隨性能劣化的趨勢。

在微服務系統中,眾多的微服務都是通過網絡松耦合在一起,如果需要將一個 A/B 測試配置傳遞給鏈路上的每個服務,將它放到參數中是一個比較簡便的做法,事實上之前的系統確實也是這么做的,但是隨著配置數據的增長,這個傳遞變成了性能瓶頸之一。

針對這個問題,我們最后采取的解決方案是短期縮減規模,調整業務系統將 A/B 測試參數進行分割、控制之后,系統達到了 10% 以上的優化效果。中長期來看,優化通信和系統架構,加強監控和審核會是更重要的發展方向。

?基礎庫優化

我們認為能夠脫離當前公司運維環境使用的公共代碼大概率是屬于基礎庫范疇的,字節跳動將這部分代碼中的優秀組件獨立成了一個開源項目——gopkghttps://github.com/bytedance/gopkg)。這里面的代碼都是經過字節生產環境的殘酷考驗和反復驗證,有較高的實用價值。

“庫的設計其實就是語言的設計”,在字節內部我們還把基礎庫中最常用的優秀組件集成到了語言運行時中,比如各類算法和數據容器,讓業務同學開箱即用,不引入額外依賴或修改源碼即可受益。同時,我們也嘗試向上游開源社區貢獻相關代碼,讓更多人受益,比如近期我們將排序算法 PDQSort 貢獻到 Golang 社區,成為 Go1.19 版本的標配。

?語言運行時優化

為了實現更高的性能,字節跳動基礎架構語言團隊對 Go SDK 進行了定制優化,在兼容社區版本的前提下,面向后端服務優化。

一般我們認為 Go SDK 包含兩個部分:接口和實現。接口層優化包含語法、標準庫和一些常見的命令,比如 go build、go tool 等;而實現層一般是用戶不會直接接觸的編譯器、垃圾回收器、標準庫實現等,這部分的改動大部分是對用戶代碼透明的,用戶不用改代碼就可以享受收益。

為了達到優化性能的目的,我們的思路是:對接口層只增加不修改;對實現層做有意義的性能改進,并保留切換社區行為的開關。這樣既保持和社區生態極高的兼容性,又能對更影響性能的實現邏輯進行高度優化。

內存管理優化

我們認為 Go 的內存管理面臨的問題之一是過于為 GC 暫停優化(雖然這是它最大的賣點),它為此付出了分配效率、GC 吞吐等代價。其中最容易在微服務上觀察到的問題是:內存分配動作占用過多的 CPU。一些典型服務上大約百分之十幾的 CPU 資源都被用來運行內存分配動作,這些動作分散在一次請求處理的各處代碼中,最終直接拖慢了整體執行效率。

對于 15% 的代價,我們做了一些詳細的分析,發現在字節的微服務系統上,大部分分配的對象都是小對象,并且很多對象都沒有指針(Go 會將有指針和無指針的對象存儲在不同內存區域),所以我們思考有沒有更快的分配思路?

Go 的內存分配使用類似?TCMalloc?(https://google.github.io/tcmalloc/) 的分配方式,如下圖所示。它的做法是:用戶先去查找 mcache,它會通過索引把一個 size 取整到一個固定大小,比如將 19 取整到 24,然后查找 24 對應的 bucket 池, 然后找出一個空 bucket 返回給用戶。這種邏輯涉及到 bucket 的查找,分配的不同對象可能位于較遠的地址空間,局部較差。

為了簡化這部分開銷,我們選擇了 Bump-pointer 分配方式,如下圖所示。Bump-pointer 分配的做法非常簡單:使用一個指針 P 指向一段連續的空閑內存空間,需要分配 N 個字節的內存時,就把 P 的值返回給用戶,同時執行 P += N 即可。

我們制作了一個特性:GAB(Goroutine-Allocation-Buffer),為每個 Goroutine 保留一塊用于 Bump-pointer 分配的 Buffer,讓堆內存分配的請求盡量落到這個 Buffer。為什么做 G 這層,而不是 M 或 P 層呢?這是經過測試的經驗性結論,G 層效果最好。為了保證兼容性,我們把這個 Buffer 直接映射為 TCMalloc 風格管理的一個 bucket 中,因此它與現有 Go 運行時的管理機制完全兼容。最后效果上表現為一個 TCmalloc 的 bucket 中匯聚了多個 Bump-pointer 快速分配的對象。

對象的分配只是第一步,如果我們從不回收內存,最終還是會 OOM 掛掉。GAB 內存的回收仍然是依賴于 Go 運行時自身的標記-清理算法,如果 bucket 作為一個整體死掉,就可以一次性批量回收大量 GAB 對象,性能很高,微服務的內存使用行為很多時候符合分代假說,所以大部分對象都可以輕松回收。但是如果 bucket 中有少量活躍對象呢?比如少量請求數據被放到了 cache,這樣正常路徑就無法回收,為此我們制作了 CopyGC 的回收機制,通過移動對象的方式回收空閑空間。

這個特性整體效果比較明顯,如下圖所示,CPU 占用率降低了 5% ~12%。

編譯器 Beast mode

Go 編譯器雖然編譯速度很快,但是并沒有選擇生成性能最高的代碼,因此字節跳動基礎架構語言團隊研發了一個額外的編譯模式,即編譯器 Beast mode。正如隱身戰斗機會有個額外的 Beast mode 用于火力壓制,編譯器 Beast mode 擁有更多的優化手段,執行效率更高。我們選擇在開發階段使用標準編譯模式,提高開發效率;發布到線上時使用 Beast mode 編譯生成性能更高的二進制。

這里舉一個額外優化的例子:常量傳播優化。比如說要在 Go 中分配一個 slice ,N 被賦值 1 ,如果后面沒有對 N 進行修改,Go 之后會一直將 slice 分配在堆上。當我們進行了常量傳播優化之后,這個常量會直接被各個編譯器吃掉,Go 就可以把它分配到棧上。

這個編譯器優化效果比較明顯,在很多 Benchmark 上都取得了比較好的效果,如下圖所示,time/op 越少越好:

調度器優化

調度器的優化思路比較簡單,由于微服務面向網絡通信,我們將業務代碼中的 polling 動作和調度器里面的 polling 動作打通。這里不展開詳細介紹,如下圖所示:

下圖展示了相關性能數據,藍色部分展示了優化過的效果,可見效果比較理想。

?

?未來展望


關于未來展望,字節語言團隊未來主要會關注以下三個方向:

  • 極速運行時我們首先會關注如何將加速運行時,比如加入動態代碼生成能力、Balanced GC 能力,支持 Adaptive Runtime、定制硬件支持;同時歡迎對系統軟件開發感興趣的朋友加入我們一起研發;
  • 生態友好:字節在開源上比較活躍,字節內部同學的日常工作與 Github 聯系十分緊密。字節內部框架團隊同學開源了一系列微服務產品——CloudWeGohttps://github.com/cloudwego),歡迎大家使用。雖然字節在開源方面宣傳并不多,但確實是一個不斷實踐開源的組織,之后我們也會將 Go 方面的許多優化開源出來,做一個對生態更友好的組織;
  • 工具實踐其將思路強加給別人,不如將一個好用工具推薦給他們,用工具固化一些最佳實踐,讓更多用戶可以輕松參與性能優化。

掃描二維碼,加入字節跳動基礎架構語言團隊

- END -

近期,字節跳動宣布 KubeWharf 項目正式開源。

KubeWharf 是字節跳動基礎架構團隊在對 Kubernetes 進行了大規模應用和不斷優化增強之后的技術結晶。這是一套以 Kubernetes 為基礎構建的分布式操作系統,由一組云原生組件構成,專注于提高系統的可擴展性、功能性、穩定性、可觀測性、安全性等,以支持大規模多租集群、在離線混部、存儲和機器學習云原生化等場景。

展開閱讀全文
  • 0
    感動
  • 0
    路過
  • 0
    高興
  • 0
    難過
  • 0
    搞笑
  • 0
    無聊
  • 0
    憤怒
  • 0
    同情
熱度排行
友情鏈接
18禁高潮出水呻吟娇喘mp3,日本熟妇乱人伦A片免费高清,成人午夜精品无码区,狠狠色噜噜色狠狠狠综合久久,麻豆一区二区99久久久久,年轻的妈妈4,少妇被又大又粗又爽毛片,护士张开腿让我爽了一夜,男男互攻互受h啪肉np文,你好神枪手电视剧免费观看啊,97人妻一区二区精品免费,久久久婷婷五月亚洲97号色,freegaysexvideos男男中国,国产精品国产三级国av麻豆,国产精品又黄又爽又色无遮挡网站,亚洲av无码一区二区三区网站,亚洲国产精品久久久久蜜桃,国产真人无码作爱视频免费,国产成人精品亚洲一区二区三区,亚洲欧洲日产最新,老司机带带我精彩免费,国产成人久久精品激情,日本最新av免费一区二区三区,边摸边吃奶又黄又激烈视频
<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>