西瓜視頻基于 Hertz 的微服務落地實踐
01、西瓜視頻微服務架構設計
西瓜視頻介紹
西瓜視頻是一個開眼界、漲知識的視頻 App (Informative Video Platform),作為國內領先的中長視頻平臺,它源源不斷地為不同人群提供優質內容,讓人們看到更豐富和有深度的世界,收獲輕松的獲得感,點亮對生活的好奇心。
同時,西瓜視頻鼓勵多樣化創作,幫助人們輕松地向全世界分享視頻作品,創造更大的價值。目前平臺月活躍創作人超過 320 萬,月活躍用戶數超過 1.8 億,日均播放量超過 40 億,用戶平均使用時長超過 100 分鐘。
微服務架構設計時關注哪些方面
業務域劃分
上面四張圖分別對應我們的中視頻、電商、長視頻和作者側的業務。當然我們的業務場景遠不止這些,但是也反映出了 C 端場景多,業務域劃分較細的特點,因此我們在微服務架構設計的時候需要著重考慮服務劃分與解耦,在設計期間我們主要遵循兩個原則:
- 單一職責原則:確保每個微服務只負責一個特定的業務功能,避免職責混亂。
- 領域驅動設計:使用領域驅動設計方法來劃分服務邊界,確保各個服務獨立、可復用。
這樣設計之后,可以保證:
- 業務模塊的獨立性:每個微服務可以獨立開發、測試、部署和擴展,提升了開發效率和系統的靈活性。
- 技術棧靈活性:不同的微服務可以使用最適合其業務需求的技術棧,不需要統一技術選型。
- 故障隔離:一個服務的故障不會影響其他服務的運行,提高了系統的可用性和穩定性。
- 按需擴展:可以根據每個服務的負載情況獨立擴展,優化資源使用和成本。
性能
架構設計
上圖是西瓜視頻整體的微服務架構設計。
從上到下我們分為三層,分別是接入層,業務層和基礎組件層。
接入層
- 不同的分端:包括西瓜 APP,西瓜 PC,TV 端鮮時光以及 M 站。
- 負載均衡及網關
使用公司內部組件來提供負載均衡和通用網關能力。
- API 服務:使用 Hertz 框架的服務。
業務層
- 消費側業務,基礎業務,和其他的一些互動社區等 RPC 業務,這些服務采用的是 Kitex 框架。
消費側業務主要面向 C 端用戶的場景就是信息流和詳情頁。
信息流:主要是西瓜 APP 的全部頻道模塊,包括進入西瓜視頻后的首頁看到的推薦精選都對應我們的信息流服務。
詳情頁:我們知道推薦頻道更多對應的是一個沉浸式的場景,但是我們的視頻也會有其他的入口進入,比如個人主頁等場景,這個時候點擊一個視頻就會進入詳情頁,相對于沉浸式那種更專注于視頻本身播放的體驗,詳情頁會包含更多的視頻信息。
推薦系統:這里是一些推薦、廣告的服務,主要是提供底層的數據的 id 返回,包含廣告混排,推薦排序,廣告投放等功能。
打包服務:公司的業務線會比較繁雜,所以隨著發展抽象出了打包層,各個業務會有自己數據結構的打包服務,作為最終返回給客戶端的數據。這里就包括小視頻、中視頻、長視頻、直播等數據的打包。
- 互動模塊
在這些業務之外,我們還會有其他的一些互動和社區的功能模塊。這種都是有公司專門的關系中臺、評論中臺去維護。同時,我們的業務還會依賴業務用到的存儲,像 mysql、redis 等。
基礎組件
基礎組件,比如語言框架,像前面提到的 Hertz、Kitex 等框架,還有一些日志、監控,配置系統。
02、Hertz 框架介紹
背景
字節跳動從 2014 年開始使用 Golang,2016 年基于 Gin 框架封裝了 Ginex。但 Ginex 在迭代受開源 Gin 項目限制、代碼混亂膨脹導致維護困難、無法滿足性能敏感和功能擴展需求等問題。2020 年部分業務線嘗試魔改其他開源框架如 Fasthttp,但帶來了分散生產力和巨大維護成本的問題。為解決這些痛點,字節于 2020 年初立項開發自研高性能 Go 框架 Hertz,經過兩年多的迭代,Hertz 于 2022 年 6 月正式開源,目前已廣泛應用于字節內部逾 1.4 萬個服務,支撐日峰值 QPS 超 5000 萬,顯著降低資源使用和服務延時,接替大量基于 Gin 的存量服務,助力公司降本增效。
為什么選擇 Hertz
- 極致的性能
Hertz 的性能指標是作為一個核心指標開展設計和實現的。Hertz 默認使用自研的高性能網絡庫 Netpoll,在一些特殊場景相較于 go net,Hertz 在 QPS、時延上均具有一定優勢。關于性能數據,大家可以看一下 hertz-benchmark(https://github.com/cloudwego/hertz-benchmark),可以看到 Hertz 的 QPS 和時延指標在和其他三個知名框架對比中已經全面占優,對比 Gin 更是遙遙領先。
并且,框架整體的持續優化會貫穿框架的整個生命周期,持續不斷地進行下去。
- 易用性,開發者友好
在開發過程中,快速寫出正確的代碼往往是重要的。Hertz 在設計 API 時,考慮到用戶的使用習慣,參考業界主流框架使用 API 的方式,并加以優化。在 Hertz 在迭代過程中,積極聽取用戶意見,持續打磨框架, 比如很多用戶希望 Client 也有 Trace 的能力,為此,Hertz Client 支持了中間件能力。Hertz 也提供了命令行工具,一鍵生成代碼,提高框架的易用性。
Hertz 提供了一個簡單易用的命令行工具 hz,用戶只需提供一個 IDL,根據定義好的接口信息,hz 便可以一鍵生成項目腳手架,開箱即用使用 Hertz;hz 也提供更新能力,用戶的 IDL 如果發生改變,hz 可以更新腳手架。目前 hz 支持了 Thrift 和 Protobuf 兩種 IDL 定義。命令行工具內置豐富的選項,可以根據自己的需求使用。
- 豐富的文檔體系
即使是從來沒有使用過相關框架的新同學,都能夠通過相應的文檔,快速上手 Hertz,體驗 Hertz 所帶來的極致的開發體驗。
- 穩定性
Hertz 對內對外支撐了大量的業務服務,業務選擇 Hertz 的一個重要考量就是穩定性。Hertz 也制定了各種措施來保證框架的穩定性: - 基于線上流量特征的隨機模擬。Hertz 會根據線上真實的流量曲線,通過模擬和重放的方式,在測試環境中復現各種極端的高并發場景,從而驗證系統的承載能力和容錯能力。
- 核心 API 全覆蓋。Hertz 所有對外提供服務的 API 接口,無一例外都要經過完備的性能測試、壓力測試和負載測試,確保在各種極限情況下也能保持穩定和高效。
- 適配不同的線上部署環境。針對 Hertz 服務的不同場景,會準備不同的線上環境。這些環境硬件資源配置不盡相同,通過在不同環境中反復測試和驗證,可以全面評估系統的穩定性和可擴展性。
- 7*24 小時監控大盤。全天候實時監控系統的各項核心指標,一旦發現異常,能夠第一時間定位并通知值班人員介入處理。
- 嚴格的發布流程。Hertz 對系統的每一次上線升級都有特別嚴格的流程要求,必須經過線下測試、驗證、代碼 review 等多個環節,避免出現變更帶來的風險。
- 低廉的遷移成本
- 西瓜早期的 API 服務都是基于 Ginex 框架開發的。而 Hertz 立項之初,就將針對存量 Ginex 服務的遷移作為一個高優需要照顧的環節。在一些業務常用 API 上做了許多兼容性的設計。
- 同時,Hertz 也為存量項目提供了一鍵遷移的方案 Ginex 項目快速遷移 Hertz 1.x 指南,使用一鍵遷移工具,用戶可能無需/只需改動少量代碼即可完成
- Gin —> Hertz 項目的遷移。詳見:https://www.cloudwego.io/zh/docs/hertz/tutorials/service-migration/#gin
- 豐富的擴展能力
Hertz 采用了分層設計,提供了較多的接口以及默認的擴展實現,用戶也可以自行擴展。Hertz 目前支持了日志、監控、服務注冊與發現、網絡庫和協議等擴展能力,未來有望為用戶提供更多的擴展能力。
03、Hertz 遷移過程、踩坑經驗
遷移過程
1. 選擇要遷移的服務
遷移的第一步就是要先選擇要遷移的服務,根據帕累托原則,我們優先找出占據最大比例的成本來源,并處理這些高成本項目,以最大化資源利用效率。根據性能分析平臺,我們選擇了目前西瓜 CPU 資源消耗最大的兩個 api 服務進行遷移,分別對應我們的觀看歷史、點贊和彈幕的有關服務。
2. SDK 庫適配
西瓜業務線對 Ginex 做了封裝,提供一個 SDK 給其他 api 服務使用,比如在獲取觀看歷史的接口中,我們會先使用 SDK 中的 newRequestContext 方法來做一些請求上下文的初始化操作。在這個請求上下文中我們會有 ab 實驗、設備信息、地理位置信息等上下文的初始化。所以我們第一步就是針對 SDK 庫中相應的代碼進行適配。在這個適配過程中有一些三方的 SDK 庫的依賴,可以直接升級 SDK 對應的 Hertz 版本。
3. 執行一鍵遷移腳本
Hertz 同學提供了一鍵遷移腳本,只需在當前服務目錄下執行腳本即可完成大部分重復性的改造工作。
腳本通過正則匹配的方式完成大部分重復性替換工作,對于一些 gin、ginex 字樣會替換為Hertz 中的相應的實現。
4. 手動修改
Hertz 中間件的設計采用了洋蔥模型,洋蔥模型是一種中間件流程控制方式,做到核心邏輯和通用邏輯分離。
中間件可以做日志記錄、性能統計、安全控制、事務處理、異常處理等場景。
Hertz 預置了幾款中間件:
- Recovery 中間件:負責處理鏈路上的 panic
- Metrics 中間件:負責請求相關指標上報
對于業務自己實現的特殊中間件需要進行遷移,關于如何實現一個中間件 Hertz 也給出了實例。
以西瓜業務自己的特殊中間件 default_stable 為例,我們會在 header 中將 stable 設置為 1,用于做 SLO 數據統計。這種中間件我們就需要做一些適配工作,這里的話就會面臨接口不匹配的問題,可以參考 Gin —> Hertz API 對照表(https://github.com/hertz-contrib/migrate/blob/main/gin_to_hertz.md)進行修改。
踩坑經驗
1. Query tag 缺失導致請求參數解析失敗
- 表現
Query 請求參數在服務端沒有解析成功,調用 RequestContext 進行參數綁定的時候報錯。
- 原因
- 參考 Hertz 支持的參數綁定與校驗相關功能及用法,不通過 IDL 生成代碼時若字段不添加任何 tag 則會遍歷所有 tag 并按照優先級綁定參數,添加 tag 則會根據對應的 tag 按照優先級去綁定參數。
Hertz 支持的參數綁定與校驗相關功能及用法:
https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/binding-and-validate/ - 請求參數有關的結構體未設置 query tag,導致 query 請求參數在服務端沒有解析成功。
- 解決方式
- 結構體添加 query tag
- 通過單元測試提前規避
2. 未配置 looseZero 模式導致綁定數值類型報錯
- 表現
在一些場景下,前端有時候傳來的信息只有 key 沒有 value,這會導致綁定數值類型的時候報錯。
- 原因
- 未配置 looseZero 模式
- 解決方式
04、落地 Hertz 后的收益
上線前后對比發現 CPU 使用率下降約 10%,優化效果明顯,詳細的性能收益如下:
- 平均 CPU 核數從 2730 降為 2443
- 單核 QPS 處理能力提升:10.7%
- 內存占用率變化—優化前:31.1% ,優化后:29.3%
- 核心接口 LatencyPct99 下降:10.15%
項目地址
GitHub:https://github.com/cloudwego
官網:www.cloudwego.io