得物社區(qū) Golang 灰度環(huán)境探索和實(shí)踐
1、背景
灰度發(fā)布可以在服務(wù)正式上線前,提前用小流量對(duì)新功能進(jìn)行驗(yàn)證,提前發(fā)現(xiàn)問題,避免故障影響所有用戶,對(duì)業(yè)務(wù)穩(wěn)定性非常有價(jià)值。
得物社區(qū)后端技術(shù)棧以 golang 為主,本文記錄了社區(qū)后端在灰度環(huán)境建設(shè)過程中遇到的挑戰(zhàn),以及對(duì)應(yīng)的探索和實(shí)踐。
名詞解釋
- 小得物:得物內(nèi)部小流量灰度環(huán)境。
- ARK:得物內(nèi)部配置中心。
- DLB:得物內(nèi)部負(fù)載均衡中間件。
- DMQ:得物內(nèi)部消息中間件。
- DRPC:golang 后端 RPC 系統(tǒng)。
本文對(duì)涉及內(nèi)部敏感信息部分做了打碼和脫敏處理,敬請(qǐng)理解。
2、小得物灰度引流架構(gòu)優(yōu)化
2.1 小得物 V1
跟 Java 網(wǎng)關(guān)對(duì)接注冊(cè)中心不同,社區(qū) HTTP 是依賴容器 Service 和 Ingress。
對(duì)社區(qū)來說,因?yàn)橹挥?C 端有外部流量的應(yīng)用才有部署小得物的價(jià)值,所以希望:
- 小得物可以只部署部分應(yīng)用。
- 未部署小得物的 HTTP 入口應(yīng)用,HTTP 流量導(dǎo)向生產(chǎn)。
- 未部署小得物的 gRPC 下游應(yīng)用,gRPC 流量導(dǎo)向生產(chǎn)。
gRPC 流量比較簡單,通過 RPC 系統(tǒng)流量路由功能即可實(shí)現(xiàn),這個(gè)在后面流量路由部分會(huì)介紹。
要實(shí)現(xiàn)小得物環(huán)境只部署部分應(yīng)用,正確路由流量而不報(bào)錯(cuò),需要網(wǎng)關(guān)層、RPC 等調(diào)用層感知集群內(nèi)后端服務(wù)有沒有部署。
Ingress 這層,其實(shí)相當(dāng)于接了 k8s 的注冊(cè)中心,它是可以感知到集群是否有可用 upstream。但開源配置無法支持這個(gè)需求,二開比較復(fù)雜,這個(gè)也不在社區(qū)的控制范圍內(nèi)。
這個(gè)時(shí)期社區(qū)應(yīng)用正在進(jìn)行容器新老集群遷移,在容器 Ingress 之前加了一層 DLB(可以簡單理解為 Nginx),通過 location 來區(qū)分應(yīng)用是否部署新集群,以及新老集群流量灰度。
于是參考生產(chǎn)環(huán)境在小得物 Ingress 之前加了一層 DLB,通過 location 和 upstream 配置實(shí)現(xiàn)流量有生產(chǎn)應(yīng)用兜底。雖然依賴人工配置,但中間件都是現(xiàn)成的,而且這部分配置變化頻率較低,只有應(yīng)用上下架時(shí)需要修改。
年中時(shí)社區(qū)第一批約 15 個(gè) C 端應(yīng)用上線小得物,同時(shí)對(duì)小得物環(huán)境的監(jiān)控告警等基礎(chǔ)設(shè)施進(jìn)行了完善。
2.2 小得物 V2
V1 的核心問題在于引流機(jī)制是 DNS。DNS 的優(yōu)勢在于它是在客戶端生效,是去中心化的。但也有很多缺點(diǎn),比如控制維度單一,只有客戶端 IP、地域。只依靠這個(gè),灰度流量大小難以精準(zhǔn)控制,想要的基于 UID、header 的灰度規(guī)則也沒法實(shí)現(xiàn)。
想要做 UID 灰度引流,一般都是在入口網(wǎng)關(guān)上做?;叶扰渲每赡芙?jīng)常需要開關(guān)、調(diào)整流量大小,如果配置錯(cuò)誤或出現(xiàn) bug,則影響所有流量。
因此想到一個(gè)折中的方案,從生產(chǎn) DLB 根據(jù) UID 引入 5% 灰度流量至小得物 DLB,小得物上再通過二次灰度規(guī)則控制流量大小在 0-5%。最大流量限定為 5%,生產(chǎn)只配置一次,后續(xù)開關(guān)、規(guī)則調(diào)整均在小得物 DLB 上進(jìn)行。雖然多用了一個(gè) DLB,但減少生產(chǎn) DLB 配置變更頻率,縮小了爆炸半徑。
之前做新老集群遷移的生產(chǎn) DLB,本來準(zhǔn)備下掉,現(xiàn)在正好可以利用起來。對(duì) DLB 進(jìn)行了版本升級(jí),配置好灰度規(guī)則后,就有了現(xiàn)在 V2 的架構(gòu)。?
架構(gòu)升級(jí)后:?
- 灰度流量可按 uid 規(guī)則引入,灰度用戶流量總是進(jìn)小得物,用戶范圍可控,規(guī)則清晰。
- 灰度流量入口與交易互不影響,流量大小可在 0-5% 范圍內(nèi)靈活調(diào)整?;叶攘髁恳?guī)則是通過旁路控制,不在生產(chǎn)主鏈路 DLB 上進(jìn)行,最大流量值限制為 5%,縮小爆炸半徑。
- xdw DLB 配置通過 openAPI 控制,且與發(fā)布平臺(tái)打通,小得物新版本發(fā)布可 0-5% 梯度引流驗(yàn)證。
- UID 規(guī)則外,添加 header 頭引流規(guī)則,測試驗(yàn)證方便。App 可一鍵切換至小得物,由用戶自由選擇。驗(yàn)證小得物 api 時(shí)帶上 header 頭即可路由至小得物,再加上 trace 2.0 全面覆蓋,方便定位流量路徑。
3、發(fā)布流程優(yōu)化?
3.1 依賴隊(duì)列自動(dòng)生成
每個(gè)版本版本 owner 都需要整理版本清單,標(biāo)記出應(yīng)用的依賴關(guān)系,最后手動(dòng)導(dǎo)入到發(fā)布平臺(tái),生產(chǎn)依賴梯隊(duì)。
組內(nèi)大佬覺得這些工作可以自動(dòng)化完成,便寫了一個(gè)代碼靜態(tài)分析工具來解決:
- 對(duì)版本分支、線上分支分別進(jìn)行靜態(tài)掃描。
- 使用 go 標(biāo)準(zhǔn)庫的 parser 包將其解析 為 AST 語法樹,根據(jù)查找 proto client 樁代碼包引用生成單應(yīng)用 RPC 調(diào)用依賴圖。
- 將兩個(gè)版本依賴圖進(jìn)行 diff,找出版本變化部分。
- 將版本所有應(yīng)用的依賴圖進(jìn)行關(guān)聯(lián),最終生成版本依賴圖。
可以有人會(huì)問,為什么不基于 trace 來做?原因新功能可能沒有流量,或是有些路徑執(zhí)行不到,trace 數(shù)據(jù)需要線上流量跑一段時(shí)間才能完整。而通過靜態(tài)分析,源碼中沒有秘密,只要是寫在代碼中的依賴都能覆蓋到。這套靜態(tài)分析工具還可以實(shí)現(xiàn)循環(huán)調(diào)用分析,RPC 圈復(fù)雜度分析,幫助開發(fā)進(jìn)行微服務(wù)治理。
同時(shí)與發(fā)布平臺(tái)打通,發(fā)布時(shí)觸發(fā)靜態(tài)分析,自動(dòng)生成發(fā)布依賴狀態(tài)圖。以前都是版本 owner 手動(dòng)畫這個(gè)圖,在辦公溝通群眾同步。通過自動(dòng)化手段,大幅提高了效率和用戶體驗(yàn)。
流程圖:
效果圖:
3.2 批量發(fā)布、梯度引流、灰度分析
在發(fā)布平臺(tái)和穩(wěn)定生產(chǎn)小得物團(tuán)隊(duì)的幫助下,社區(qū)小得物發(fā)布使用了新的批量發(fā)布流程。
發(fā)布時(shí)同時(shí)支持同時(shí)發(fā)布 ARK 配置,版本變更在發(fā)布平臺(tái)內(nèi)完成閉環(huán)。不必喊應(yīng)用 owner 去 ARK 修改配置,再人工確認(rèn)后,再發(fā)布程序代碼。
在前文提到的小得物 V2 架構(gòu)中,灰度流量在社區(qū)小得物 DLB 中控制。因此在小得物發(fā)布過程中,可以直接通過 openAPI 將小得物流量摘除。沒有了流量,就可以無視應(yīng)用間依賴,直接批量將所有應(yīng)用并發(fā)部署,大幅提高小得物環(huán)境部署效率。
同時(shí)摘流后,再通過 API 將流量梯度拉升,從 0% 緩慢提升至 5%,每次引流都會(huì)觸發(fā)穩(wěn)定生產(chǎn) SOS 事件中心的自動(dòng)巡檢,根據(jù)配置的巡檢規(guī)則,計(jì)算出得分,展示與七天平均值偏差較大的異常點(diǎn),幫助版本 owner 提前發(fā)現(xiàn)灰度問題。
效果圖:
4、全鏈路灰度
4.1 RPC 調(diào)用路由
RPC 路由這個(gè)功能,大多數(shù)據(jù) RPC 調(diào)用系統(tǒng)都有。社區(qū)目前的 RPC 是基于 grpc-go 擴(kuò)展實(shí)現(xiàn)的,很多人都說 grpc 沒有服務(wù)治理功能,但實(shí)際上 grpc 有著良好的擴(kuò)展性和豐富的生態(tài)。得物 go 框架基于 grpc-go 只用了千余行代碼即可實(shí)現(xiàn)擁有服務(wù)發(fā)現(xiàn)、多注冊(cè)中心、多服務(wù)名、地址路由、自定義 interceptor 等完備功能的 RPC 調(diào)用系統(tǒng)。
在 grpc resolver 擴(kuò)展點(diǎn),在服務(wù)發(fā)現(xiàn)階段根據(jù)規(guī)則過濾調(diào)用不包含 xdw 元數(shù)據(jù)的地址,即可實(shí)現(xiàn)服務(wù)路由功能。
在 drpc pickers 配置項(xiàng)中配置注冊(cè)中心元數(shù)據(jù)表達(dá)式 env == "xdw" ,優(yōu)先路由至小得物節(jié)點(diǎn),在下游服務(wù)未部署小得物時(shí)兜底至生產(chǎn)節(jié)點(diǎn),保證可用性。
同時(shí)為了解決業(yè)務(wù)應(yīng)用 RPC 服務(wù)名、注冊(cè)中心地址、路由規(guī)則等配置維護(hù)困難、且不統(tǒng)一的痛點(diǎn),我們做了點(diǎn)微創(chuàng)新,參考 Istio 做了一個(gè)中心配置下發(fā),懶加載的功能。
在所有應(yīng)用中都相同的注冊(cè)中心地址、服務(wù)名配置維護(hù)在控制中心配置中。server 會(huì)查找與 target 同名的 service 作為服務(wù)名注冊(cè),client 根據(jù) target 名來查找服務(wù)名,只有被客戶端樁代碼實(shí)際調(diào)用的服務(wù)才會(huì)被 watch。
應(yīng)用配置只需要引用 drpc 控制中心配置地址即可,pickers 路由規(guī)則可以統(tǒng)一下發(fā)到所有服務(wù)。而像超時(shí)等個(gè)性化配置應(yīng)用端可以覆蓋遠(yuǎn)端,框架會(huì)將其做合并處理。
控制中心遠(yuǎn)端配置:
應(yīng)用端配置:
4.2 MQ 消息路由
社區(qū)小得物與生產(chǎn)環(huán)境公用一套 DB、 MQ 中間件。應(yīng)用代碼中 MQ producer、comsuer,HTTP、GRPC API 是在一個(gè)進(jìn)程中。如果消息沒有隔離邏輯,小得物打開消費(fèi),則會(huì)與生產(chǎn)節(jié)點(diǎn)成為同級(jí)消費(fèi)者,消費(fèi)生產(chǎn)消息。而小得物環(huán)境機(jī)器配置較低,消費(fèi)速度慢會(huì)影響業(yè)務(wù)。
在沒有 MQ 消息隔離前,采取一個(gè)笨辦法,直接關(guān)閉小得物 MQ 消費(fèi)。但這樣小得物的消息是靠生產(chǎn)處理,在小得物有 MQ 相關(guān)新版本變更時(shí),需要考慮新老兼容的問題。
隨著社區(qū)阿里云 MQ 遷移 DMQ 進(jìn)入收尾階段,DMQ Go SDK 也趨于穩(wěn)定,開始嘗試使用程序化方案解決 MQ 灰度消費(fèi)的問題。
最開始跟小得物團(tuán)隊(duì)了解了一下最初的方案,小得物和生產(chǎn)使用不同的 MQ 實(shí)例,這樣就要求 producer、consumer 在小得物全量部署。對(duì)于跨業(yè)務(wù)域的 topic 需要消息同步機(jī)制。感覺復(fù)雜度過高,資源成本和維護(hù)成本都很高。
后面看到一篇 阿里云分享的 RocketMQ 灰度方案,其采用消息打標(biāo)、group 隔離、SQL 屬性過濾實(shí)現(xiàn)消息灰度,感覺這才是理想的方案。
這里說一下 tag 過濾和 SQL 過濾,tag 過濾大家比較常用,但一條消息只能有一個(gè) tag,常被業(yè)務(wù)占用,且不能支持 != 這樣的條件。而 SQL 過濾就靈活得多,可以使用消息 properties 自定義 kv 鍵值對(duì),SQL 的 NOT、BETWEEN、IN 等關(guān)鍵詞都可以使用。
找中間件團(tuán)隊(duì)溝通,他們表示 SQL 過濾性能較差,暫不支持。建議使用 Java 染色環(huán)境類似的方案,在客戶端過濾。雖然客戶端過濾,有很多無效的網(wǎng)絡(luò)傳輸,但成本較低,只需要改造一下業(yè)務(wù)框架中 MQ SDK 即可,也能解決 MQ 灰度的問題。經(jīng)過壓測,小得物環(huán)境過濾生產(chǎn)環(huán)境高 QPS 生產(chǎn)的消息或是 group 積壓的大量消息, 對(duì)應(yīng)用不會(huì)造成較大的性能影響,于是采用了此方案。
4.2.1 消息消費(fèi) consumer 隔離
consumer 消費(fèi)的隔離比較簡單,MQ 的機(jī)制是不同 group 消息消費(fèi)都是獨(dú)立的,每個(gè) group 都能收到topic 全量消息。
在業(yè)務(wù)框架中根據(jù)染色環(huán)境配置,增加不同的處理邏輯。
如果是染色環(huán)境(小得物):
- producer 發(fā)送消息時(shí),在消息 properties 中添加流量標(biāo) X-Flow-Flag=[prefix]。
- consumer 啟動(dòng)時(shí)自動(dòng)給配置的 group 添加 [prefix]。消費(fèi)時(shí)過濾掉 properties 不包含流量標(biāo) X-Flow-Flag=[prefix] 的消息,直接 ack。
如果是基準(zhǔn)環(huán)境(生產(chǎn)):
- producer 發(fā)送消息時(shí),無特殊處理。
- consumer 啟動(dòng)時(shí)使用配置中的 group。消費(fèi)時(shí)過濾掉 properties 包含流量標(biāo) X-Flow-Flag=[prefix] 的消息,直接 ack。
4.2.2 事務(wù)消息 producer 隔離
事務(wù)消息比較特殊,主要體現(xiàn)在 trans producer 有一個(gè)回查邏輯。trans producer 不光會(huì)向 server 發(fā)消息,還會(huì)接受 server 發(fā)送的回查消息。
查看了一下 DMQ 的 Java 源碼,發(fā)現(xiàn) Boroker 回查時(shí)是通過消息 properties 中的 group 來查找在線 producer。那么跟 consumer 類似,給 trans producer 配上 group ,給小得物 group 加上環(huán)境前綴即可實(shí)現(xiàn)事務(wù)回查隔離。用于 trans producer 的 group 只是一個(gè)標(biāo)識(shí),甚至不需要在 DMQ 后臺(tái)申請(qǐng)。
5、總結(jié)
目前社區(qū)已經(jīng)通過小得物灰度環(huán)境的運(yùn)營取得一些收益:
- 在業(yè)務(wù)穩(wěn)定性上,能在正式上線前發(fā)現(xiàn)了一些測試、預(yù)發(fā)環(huán)境難以發(fā)現(xiàn)的問題,縮小影響范圍,減少上線出問題后匆忙排查、緊急回滾的緊張時(shí)刻,降低了系統(tǒng)風(fēng)險(xiǎn)。
- 在開發(fā)效率上,通過摘流批量發(fā)布、依賴梯隊(duì)自動(dòng)生成、發(fā)布流程編排等手段,大大降低了版本發(fā)布人力和時(shí)間成本。以前版本十來個(gè)應(yīng)用發(fā)布,需要多個(gè)開發(fā)介入,前后依賴等待、觀察,耗費(fèi)較大人力,生產(chǎn)發(fā)布需要 4 個(gè)小時(shí)以上;現(xiàn)在由一個(gè)版本 owner 負(fù)責(zé),在小得物驗(yàn)收通過后,一鍵發(fā)布至生產(chǎn)環(huán)境,小得物加生產(chǎn)在 2 個(gè)小時(shí)內(nèi)能搞定。幾乎解放了 0.5 天的時(shí)間,開發(fā)可以把這個(gè)時(shí)間投入到下個(gè)版本的技術(shù)方案設(shè)計(jì)上去。
但社區(qū)灰度環(huán)境只解決了部分問題,還有很多技術(shù)難點(diǎn)、體驗(yàn)優(yōu)化、流程規(guī)范待完善,例如:
- 和前端同學(xué)合作,打通中后臺(tái)、H5 頁面前后端灰度鏈路。
- 涉及外部業(yè)務(wù)域、數(shù)據(jù)同步中間件等場景的 MQ 消息和灰度流量閉環(huán)。
- 擴(kuò)大灰度窗口期,下探“深水區(qū)”,優(yōu)化QA驗(yàn)證和產(chǎn)品走查流程。
- 優(yōu)化開發(fā)用戶體驗(yàn),降低小得物環(huán)境維護(hù)成本。例如:抽出小得物、生產(chǎn)公共配置,只維護(hù)一份。
革命尚未成功,同志仍需努力!?