開源之夏經驗分享|MOSN 社區韋鑫:做自己認為很酷的事
文|韋鑫
計算機科學與技術學院
HTNN 社區貢獻者
就讀於南京航空航天大學,是計算機科學與技術學院研三的同學,研究方向是分佈式系統。
本文 3756 字,預計閲讀 7 分鐘
今天 SOFAStack 邀請到了開源之夏 2024 MOSN 社區的中選學生韋鑫同學!在本項目中,他負責將 Sentinel-golang 流量控制能力集成進 MoE (MOSN on Envoy) 態。希望他分享的這段經歷,能讓更多人瞭解到 MOSN 開源社區,感受開源的魅力~
項目鏈接:https://github.com/mosn/htnn/tree/main/plugins/plugins/sentinel
項目信息
項目名稱:將 Sentinel-golang 流量控制能力集成進 MoE(MOSN on Envoy)生態
項目導師:付建豪
項目描述:MoE(MOSN on Envoy)融合了 MOSN 和 Envoy 生態,打造高性能、高擴展性的數據平面,MOSN 技術團隊已經把 MoE 技術貢獻到 Envoy 上游,可直接使用 Envoy 最新版本來開發。通過 MoE 技術可以使用全功能的 Golang 編程語言來編寫 Envoy 插件,極大提高了業務插件的開發效率。
本次使用 MoE 技術把 Sentinel-golang 項目和 Envoy 相融合,把 Sentinel 生態引入到 MoE 生態中。
項目實現思路
概述
在完成該項目前,HTNN 社區已經提供了limit_count_redis ,limit_req ,local_ratelimit 等限流插件。通過引入 Sentinel-golang,我們可以為用户提供更多的限流選擇、提升限流能力。
方案
當前在 Sentinel-golang 中,擁有以下流量治理能力:
- 流量控制(Flow):通過配置具體規則,對請求流量進行限制;
- 熱點參數流控(HotSpot):統計某些熱點數據中訪問頻次最高的 Top K 數據,並對其訪問進行限制;
- 熔斷降級(CircuitBreaker):當系統異常情況發生時,可以自動熔斷系統,保證系統的可用性;
- 併發隔離控制(Isolation):控制對資源訪問的最大併發數,避免因為資源的異常導致協程耗盡;
- 系統自適應保護(System):在系統負載高峯期間,可以限制請求流量,避免系統資源耗盡。
然而經過理論分析與實踐得出,以下流控治理能力在本項目插件中並不適用:
- Flow 中的基於內存使用水位的流控能力 (Low/HighMemUsageThreshold、MemLow/HighWaterMarkBytes) ,因為它獲取到的水位是流控插件,也就是網關的內存信息,並不是後端服務的內存使用情況,因此不符合預期;
- Isolation,插件的 Filter 生命週期為一次請求-響應 (如 DecodeHeaders、Onlog 階段均為單一協程),不需考慮多協程 (goroutine)的情況,因此不符合預期;
- System,與 Flow 的內存水位一樣,它獲取到的系統負載信息是網關的,而不是後端服務的,因此也不符合預期。
因此最終項目實踐裁切掉了一部分插件無需實現的 Sentinel-golang 的流控能力,分別實現了 Flow (部分)、HotSpot、CircuitBreaker 流控能力。
插件處理流程
一次請求-響應流程
可分為以下 3 種規則分別描述該流程:
- FLow 規則
DecodeHeaders:
- 首先從請求頭部、請求參數獲取流控資源名稱 res
- 以 res 作為入參調用 Sentinel API Entry,得到 entry 和 blockError
- 若 blockError 不為空則觸發流控規則,根據用户配置的響應信息進行返回
- 若 blockError 為空則請求放行,並將 entry 記錄到 Filter 上下文中
Onlog:
- 從 Filter 上下文中取出 entry
- 調用 entry.Exit() 完成本次流控記錄
- HotSpot 規則
DecodeHeaders:
- 首先從請求頭部、請求參數獲取流控資源名稱 res
- 以 res、用户配置的 params、attachments 作為入參調用 Sentinel API Entry,得到 entry 和 blockError
- 若 blockError 不為空則觸發流控規則,根據用户配置的響應信息進行返回
- 若 blockError 為空則請求放行,並將 entry 記錄到 Filter 上下文中
Onlog:
- 從 Filter 上下文中取出 entry
- 調用 entry.Exit() 完成本次流控記錄
- CircuitBreaker 規則
DecodeHeaders:
- 首先從請求頭部、請求參數獲取流控資源名稱 res
- 以 res 作為入參調用 Sentinel API Entry,得到 entry 和 blockError
- 若 blockError 不為空則觸發流控規則,根據用户配置的響應信息進行返回
- 若 blockError 為空則請求放行,並將 entry 記錄到 Filter 上下文中
Onlog:
- 從 Filter 上下文中取出 entry
- 獲取後端返回的響應狀態碼,並與用户配置的失敗狀態碼列表進行匹配
- 若匹配上,則以 entry 為入參調用 Sentinel API TraceError 統計錯誤(當達到用户配置的錯誤數量時會在下一次調用 Sentinel API Entry 時觸發流控,即熔斷)
- 調用 entry.Exit() 完成本次流控記錄
插件開發過程
接下來闡述 Sentinel 插件的開發過程,同時可以給對 HTNN 插件感興趣的同學提供開發參考路徑。
需要添加/修改的文件及目錄結構如下:
├── maintainer
│ └── feature_maturity_level.yaml # 插件成熟等級
├── plugins
│ ├── go.mod
│ ├── go.sum
│ ├── plugins.go # 引入插件
│ ├── plugins
│ │ └── sentinel # 插件主體
│ │ ├── config.go
│ │ ├── config_test.go
│ │ ├── filter.go
│ │ ├── filter_test.go
│ │ └── ...
│ └── tests
│ └── integration # 集成測試
│ ├── sentinel_route.yaml
│ └── sentinel_test.go
├── site
│ └── content # 中英文檔
│ ├── en
│ │ └── docs
│ │ └── reference
│ │ └── plugins
│ │ └── sentinel.md
│ └── zh-hans
│ └── docs
│ └── reference
│ └── plugins
│ └── sentinel.md
└── types
└── plugins
├── plugins.go # 引入插件
└── sentinel # 插件配置結構
├── config.go
├── config.pb.go
├── config.pb.validate.go
└── config.proto
插件配置結構:
- 進入
<span>types/plugins</span>目錄,創建插件配置結構目錄<span>sentinel</span> - 在
<span>sentinel/config.proto</span>中定義插件模型,即配置結構 - 通過根目錄的
<span>Makefile</span>,執行<span>make gen-proto</span>生成插件的 Go 配置結構 - 在
<span>sentinel/config.go</span>定義插件名稱、類型、執行順序等,並編寫 Validate 作為配置校驗邏輯 - 在
<span>plugins.go</span>中添加配置結構 package
插件開發及單元測試:
- 進入 plugins/plugins 目錄,創建插件目錄 sentinel
- 在
<span>sentinel/config.go</span>中編寫插件配置解析邏輯 - 在
<span>sentinel/filter.go</span>中編寫插件核心工作邏輯 - 在
<span>sentinel/config_test.go</span>中編寫配置解析的單元測試 - 在
<span>sentinel/filter_test.go</span>中編寫插件工作所依賴方法的單元測試
集成測試:
- 返回
<span>plugins</span>目錄 - 在
<span>plugins.go</span>中添加插件 package - 在
<span>tests/integration/sentinel_test.go</span>中編寫集成測試 - 通過當前目錄下的
<span>Makefile</span>,執行<span>make bulid_test_so</span>得到編譯產物,接着進行集成測試,如GreenOpstest -v ./tests/integration/sentinel\_test.go ./tests/integration/suite\_test.go
插件文檔:
- 進入
<span>site</span>目錄 - 在如下位置編寫中文文檔
<span>content/zh-hans/docs/reference/plugins/sentinel.md</span> 中
- 使用倉庫提供的翻譯工具得到
<span>prompt:go run cmd/translator/main.go -f .content/zh-hans/docs/reference/plugins/sentinel.md --from zh-Hans | pbcopy</span> - 將粘貼板內容提交到 LLM,如 ChatGPT 中得到英文文檔,保存到
<span>content/en/docs/reference/plugins/sentinel.md</span> 中
其他:
- 在
maintainer/feature_maturity_level.yaml中添加插件的成熟級別(feature maturity level) - 通過根目錄的
<span>Makefile</span>, 執行 make lint,make fmt 等命令做代碼提交前的檢查
參考
- 如何二次開發 HTNN:https://github.com/mosn/htnn/blob/main/site/content/zh-hans/docs/developer-guide/get\_involved.md
- 插件開發:https://github.com/mosn/htnn/blob/main/site/content/zh-hans/docs/developer-guide/plugin\_development.md
- 插件集成測試框架:https://github.com/mosn/htnn/blob/main/site/content/zh-hans/docs/developer-guide/plugin\_integration\_test\_framework.md
開源之夏個人隨訪
自我介紹
大家好,我是韋鑫,目前就讀於南京航空航天大學,是計算機科學與技術學院研三的學生,研究方向是分佈式系統。
參與該項目的原因
在本科期間,我就想要嘗試參與開源活動。我認為這是一件很酷、很有趣的事情,但總會認為自己知識儲備不足選擇望而卻步。然而,想到學生時代仍有不過這短短几年,與其自我矛盾、躊躇不前,不如先行動起來再説。因此在 2023 年,我根據自己的研究方向,第一次參與開源之夏的項目。事實證明,技術不足並不是妨礙參與開源活動的關鍵,缺乏熱情才是。在那裏,我認識了非常有趣的社區同學,瞭解了相關行業現狀,並在實踐中學習,在學習中實踐,這種相互交流討論、學以致用的過程讓我很有成就感。
在科研工作中,我瞭解並學習了需要使用到的 Kubernetes、Istio,進而瞭解到了 Envoy 及 MOSN。因此在本次開源之夏中,我選擇了與自身匹配度較高的、隸屬於 MOSN 社區下的 HTNN 子項目。HTNN 社區雖然正處於前期快速迭代階段,但是綜合社區文檔代碼等方面來看,對我而言仍然是一個非常難得的學習機會。
如何克服項目過程中的困難與挑戰
在代碼開發過程中碰到問題和挑戰是再尋常不過的事情,戰勝困難的最好方法就是直面困難。通常我會先儘自己所能去尋找問題的答案,儘管這樣會花費大量的時間,但也會在這個過程中能夠收穫許多。面臨實在無法解決或拿捏不準的問題時,我會將自己的探索過程及想法同步到社區,與社區同學和導師討論交流、集思廣益。
在進行此次項目的過程中,我與社區同學及導師討論了許多問題。令我印象最深的一個問題是 Sentinel Entry 的 Exit 時機問題。Sentinle Entry 的 Exit 時機不當可能會導致資源泄露、指標統計不準確等問題。最初我選擇在 EncodeHeaders 階段進行 Exit 操作,考量是 CircuitBreaker 熔斷策略需要在響應階段根據響應狀態碼統計指標。但在與社區同學討論後得出結論是:客户端可能會在 EncodeHeaders 階段之前中斷請求,此時無法執行其中的 Exit,從而出現上述問題。因此最終決定在客户端中斷請求也會被觸發的 OnLog 階段進行 Exit 操作,保證上述邊界情況出現時依然能正常釋放資源、統計指標。
將代碼邏輯從 EncodeHeaders 遷移至 OnLog 衍生出了無法獲取到響應頭部的問題。這是因為在 Envoy 1.31 中還不支持在 OnLog 中獲取頭部信息,需要額外執行頭部獲取操作來緩存該項信息,我也因此提交另外一個 PR。
你對 HTNN 的印象
MOSN 社區的 HTNN 是個非常有趣的項目,是一款基於雲原生技術的 L3 & L4 & L7 Cross-Layer 網絡全局解決方案產品。我曾有幸參與過 Envoy WASM 的插件開發、維護工作,但受限於 WASM 的成熟度,Golang 生成 WASM 會存在諸多的限制,因此僅僅是能夠使用全功能的 Golang 進行插件開發這一點就足以讓我眼前一亮,相信這一能力在今後也能夠吸引到更多 Gopher,另外 HTNN 還有其他更多寶藏等着我們去發掘。
麻雀雖小,五臟俱全。作為早期快速迭代的開源項目,HTNN 有着相當完備的説明文檔、國際化、單元測試、集成測試框架和 CI/CD,同時代碼也十分工整優雅。當有問題發 Issue 時,能夠很快的得到社區同學的響應和幫助;對於 PR,相關同學也會非常認真負責地進行 Code Review,並給出相當有見解的修改建議。因此我認為這對想要入門學習雲原生、服務網格、網關等相關技術的同學而言是一個非常棒且值得考慮的社區。
總而言之,HTNN 是一個開放、熱情、有活力且極具有強技術力的開源社區。
有哪些收穫
在此次項目中,我學會了如何開發 HTNN 插件。它基於 MoE 能力,即 Envoy Golang 擴展機制,使我在服務網格、雲原生網關方面的拼圖更加完整,這對我今後的開源活動、工作提供了十分寶貴的實踐經驗。
同時,社區完備的自動化能力讓我深刻地意識到嚴格的單元測試、集成測試、CI/CD 對於整個項目的重要性。它能夠很好地規範代碼、減少不必要的錯誤、提升代碼質量、減輕 Reviewer 的心智負擔,這也是我今後需要重點關注和學習的方向。
寄語
或許我們曾長久仰望,那些看似遙不可及的璀璨星辰;卻未曾察覺,在默默耕耘的歲月裏,自己正悄然蜕變成那顆最耀眼的星。相信自己,勇敢地去做自己認為很酷的事!