互金平台灰度發布的三段式探索與實踐【轉載】
- 2019 年 10 月 4 日
- 筆記
http://dbaplus.cn/news-72-1441-1.html
作者介紹
小亞,互聯網金融公司應用運維主管,參與運維工作九年,涉及領域包含航空、金融、廣告等。近兩年主要負責金融業務運維的線上業務發布、維護等工作。
分享目錄:
- 應用邏輯架構
- 發布實踐1.0及問題
- 發布實踐1.1—平滑發布
- 發布實踐1.2—灰度發布及驗證
- 發布實踐—後續探討
本文將從某互聯網金融平台的線上版本發布工作出發,介紹了整個發布過程的優化及改造,以及對於灰度發布的探索及最終實踐。
先要說明一點,任何脫離實際業務的技術工作都是耍流氓,技術需要服務於業務。因此,本文盡量淡化了業務方面的因素,聚焦於技術層面,建議在實際運用中還是要根據各自的業務場景去變化和調整。
其次,本文重點描述了線上發布的實施改造思路及演進過程,但對於其它相關聯的一些點,比如發布規範流程、配置管理、監控、自動化工具的實施等不做過多涉及,如有興趣可後續交流。
應用邏輯架構

圖1 應用邏輯架構圖
客戶端
包含手機APP、Web頁面(主站/營銷站等)、H5頁面等,即訪問發起方,來自於真實用戶。
WEB
主要實現轉發功能,利用Nginx實現,同時包含一些業務策略和跳轉設置。
BFE
Business Front End,業務前端,實現接入和業務聚合功能,有點類似於API網關,但和業務有一定耦合,用Tomcat war包發布。
APP
業務應用層,實現具體業務功能,目前幾十個APP模組,用Tomcat war包發布。
Data
數據層,如資料庫、快取、分散式文件系統等。
公共組件
包含配置中心,任務調度中心,服務註冊發現中心,消息隊列等(這4個公共組件和灰度發布有一定關係,後續會單獨介紹)。
注意:
- WEB->BFE:通過Nginx反向代理轉發流量,HTTP請求;
- BFE->APP和各APP間調用:通過在服務註冊中心內註冊,進行RPC調用,由BFE統一返回。
公共組件介紹
公共組件各家公司差異較大,有自研、純開源或二次開發,我廠綜合各方面因素後,選型如下:
- 配置中心
Disconf,百度的開源產品,用起來一般,更新較慢,基本滿足配置管理需求。各APP啟動時會從Disconf中獲取配置資訊,也支援熱更新。
- 任務調度
Light task scheduler,簡稱LTS,用於Job類的統一管理調度,相當於統一管理的Crontab,業內相似的有噹噹網開源的Elastic-Job,不過LTS相對來說比較輕量級。各APP啟動時會在lts中註冊為任務節點,執行計劃任務。
- 服務註冊發現
Dubbo,阿里開源產品,有一定年數了,經受過考驗。如果重度依賴Spring的,可以考慮Spring Cloud系列。各APP啟動時會在Dubbo中進行註冊Provider和Consumer 的Service介面,用於相互調用。
- 消息隊列
RocketMQ,也是阿里的產品,性能不如Kafka,但用在金融行業應該沒問題。各APP啟動時會連接到RocketMQ中,進行後續消息的消費。
發布實踐1.0及問題
介紹完基本背景後,我們來聊聊核心問題:線上發布。
這裡的線上發布指上文中的BFE和Service服務,都是基於Java開發,部署方式是war包,容器是Tomcat。
原始發布方式如下:

圖2 BFE發布流程

圖3 APP發布流程
大家可以發現,BFE只是多了一部分切換Nginx的操作,因此後續重點對APP的發布進行說明。
上述APP的發布方式實施不久後,就遇到了幾個問題,而且對業務造成了一定影響,總結有如下幾條:
- APP發布時,直接重啟Tomcat,導致節點正在處理的請求會受到影響,嚴重時會有數據異常。
- APP發布時如果節點正在作為task_tracker運行lts任務,會導致任務失敗並retry。
- APP發布時如果節點正在消費RocketMQ中的消息,會導致消息消費異常,甚至進入retry或dlq隊列。
- APP發布完成後沒有即時驗證機制,直接暴露給用戶,如有異常影響面很廣。
- 線上無法同時存在新老版本的APP來用於長時間的驗證。
竟然有這麼多問題,淚崩~~
仔細分析上述問題,可以歸結為兩類:
- 平滑發布問題:即以上問題前三點。發布時要儘可能平滑,對用戶及業務影響最小(補充一句,當然也可以通過冪等及自動或人工補償機制去完善,這是另一個維度)。
- 發布驗證問題:即以上問題最後兩點。發布完成要能小範圍的即時驗證,最好是能定位到個體,且如有需要,驗證時間可以延長。
接下來就結合實踐,介紹下如何解決這兩個問題。BTW,在過渡期間內,大家只能熬夜停服發布或者在晚上低峰期發布,苦不堪言。
發布實踐1.1—平滑發布
平滑發布,即發布時盡量減少對業務的影響,能夠柔和地對服務進行下線。為做到這一點,必須要結合現有公共組件的特點,在程式碼部署前先對服務進行平穩下線,確認下線完畢後再進行發布工作。
1
Dubbo
由於所有APP的介面都有在Dubbo中進行註冊,因此需要有辦法能夠對其Provider Service介面進行下線或屏蔽,使其不提供服務,即其它服務無法調用它的介面。
Service介面下線後,此APP機器自然無任何流量流入,因此也無流量返回,達到下線APP機器的目的,然後即可部署程式碼。
官方有提供Dubbo-Admin工具,用於對Dubbo中各APP及其Service介面進行管理,裡面自然也包含有實現下線的功能,可以有3種方法:
- 屏蔽,貌似一直沒有效果;
- 禁用,可以成功禁用;
- 權重調節,可以設置0-100的權重,設置為0時即不提供服務。
經過選型,我們選用更靈活的權重調節方案,通過Dubbo-Admin對需要下線機器的APP應用介面許可權設置為0。

圖4 Dubbo權重調節
2 RocketMQ
同理,如果要被重啟的APP機器正在消費消息隊列中的消息,也需要等消費完成後才能進行發布,因此需要查詢該APP機器所對應的Consumer Group及綁定的Queue,然後下線,即解除綁定。在RocketMQ的web-console中我們增加了對應介面,進行下線。

圖5 RocketMQ控制台
3 LTS
對於任務調度這一塊,我們也必須要讓APP機器不再接受任何新任務,以免重啟發布時任務執行失敗。
我們的做法是在ZooKeeper里對需要停止跑Job任務的APP機器,增加一個Znode,比如」機器ID=offline」,當JobTracker去調度TaskTracker執行任務時,一旦檢測到包含有此Tag的機器,就不會再給這些APP機器分配任務,以此達到任務解耦。
4 檢查機制
為了平滑發布的順利進行,檢查確認機制不可或缺,即確保Dubbo/Rocketmq/Lts中的下線都已生效,並且無流量發生,我們從以下兩個維度去檢查:
- 介面檢查,調用Dubbo、RocketMQ、LTS的API介面,檢查APP機器狀態,是否為已經下線。當然,在做了下線功能的同時,我們也有檢查功能和上線功能,可供調用。
- 監控檢查,調用CAT、ELK的API介面,檢查APP機器的請求訪問數和日誌流量是否都已經為0,已經處於下線狀態。
經過上述改造後,我們新的發布流程如下,基本解決了平滑發布問題,發布時對業務的影響降到了最低;

圖6 發布流程圖解
發布實踐1.2—灰度發布及驗證
這一章主要解決發布驗證的問題,即如何驗證以確保線上發布的準確性,有問題時確保影響面最小。
1 停服後如何小範圍驗證
這裡先來個小插曲,不知道各位有沒有碰到過類似情況,大版本發布時通常會掛停服公告,把請求切斷在Web層,然後運維小夥伴會進行APP發布,此時通常會把所有APP都進行程式碼部署,因為是大版本,十分兇殘。

圖7 停服頁面
下面問題來了,等發布完成後,產品經理通常會說,能不能先不要開服,對外還是保持停服頁面,但讓我們幾個人能夠驗證下功能,以肯定確定以及確認這次發布沒有遺漏或漏測的坑。
如果你是運維的小夥伴,會怎麼搞,大家可以腦洞下~~
先分享下我們的做法,我們會在辦公網路單獨申請一個HDFB的wifi(灰度發布),然後當你連上這個wifi出公網解析時,所有和我們業務相關的域名會解析到另一個入口,這個入口對應一個灰度發布的WEB層,配置和線上一模一樣,限制只能辦公網路訪問,所有人員在辦公室通過這個入口即可訪問和驗證新版本,但公網用戶不可達。

圖8
2 灰度發布實踐
通過之前的平滑發布和小範圍驗證的摸索,開始進行灰度發布實踐之路。
灰度發布,我相信大家對這個詞都有各自的理解和體會,它也有很多相似的概念或變體。比如分組發布,藍綠髮布,金絲雀發布,甚至於A/B測試。這裡不想糾結於某個具體的名詞或概念(需要煩請自行百度),還是致力於解決實際中碰到的兩個問題:平滑發布和發布驗證。
平滑發布問題前文中已有描述,至於發布驗證問題,前文介紹了在停服情況下通過HDFB WEB層進行驗證,但有兩個問題:
- 一是只適用於停服發布,如果某次只發布幾個APP模組,無法單獨驗證。
- 二是驗證時間有限(停服窗口一般不會太大),如果需要長時間驗證,無法滿足。
為了解決上面的問題,思考過程如下:
- BFE,接入匯聚層,可以通過Nginx反向代理進行分組,即可區分流量,進行分組;
- APP,由於會在多個公共組件中進行註冊,因此需要在公共組件中對接入的APP及其Service介面進行分組,具有相互隔離的能力,即可區分驗證;
- 針對公共組件的優化,又有以下兩種做法:
- 多搭建幾套公共環境,用於不同分組,但很快被否定,維護成本太大。
- 在一套公共環境中,支援多個分組,在APP中引入對應的framework jar包,支援灰度分組參數GROUP。
因此,按照這個思路,如果需要進行灰度發布及長時間驗證時,會是下面的架構圖:

圖9
此處以GROUP=BLUE及GROUP=GREEN為例來進行說明(當然也可以分成更多的組),描述APP機器灰度發布流程(BFE類似,只是增加一步切換Nginx操作,不單獨描述)。
正常情況下,各APP機器啟動時,引入framework.jar包,並指定自己所屬GROUP,假設初始時為BLUE。
當需要發布及進行驗證時,平滑下線所有APP的一部分機器,然後對需要部署程式碼的APP進行發布,啟動時修改所有下線APP機器的所屬GROUP=GREEN。
發布完成後,可以通過單獨的HDFB WEB入口,進行驗證,此時線上仍可正常提供服務。
確認無誤後,重複上述步驟,增加GROUP=GREEN機器比例,當超過一半時,GROUP=GREEN直接提供線上服務,即把線上WEB層直接指向BFE(GROUP=GREEN)的分組。
隨即把GROUP=BLUE機器再全部進行程式碼發布。
發布完成後,線上APP統一運行在GROUP=GREEN的環境。
通過這種方式,我們即完成在不需要停服的情況下,對線上APP進行灰度發布及驗證,對應的各基礎組件截圖如下:
配置中心Disconf:通過版本來對應GROUP的功能

圖10 Disconf
註冊中心Dubbo:通過在Service的名稱前加上GROUP以分組

圖11 Dubbo
消息隊列RocketMQ:通過在Topic的後面加上GROUP以分組

圖12 RocketMQ
任務調度中心lTS:通過給每個task id加入灰度分組資訊,以區分不同的TaskTracker執行節點,新的task只在新程式碼的機器上運行。

圖13 lts
發布實踐–後續探討
下面,我們談一談灰度發布的前提條件、應對思路以及後續的優化改善。細心的同學一定發現了,前面講的灰度發布流程,應該是有一定先決條件的,體現在以下幾個方面:
- 數據層的變化導致新老版本無法兼容的,不能使用灰度發布。
問題詳述:灰度發布最終的數據落地還是一份,因此如果資料庫的表結構變更或者分散式快取數據結構存在差異及不兼容的情況,就不能使用灰度發布。
應對思路:這個沒有特別好的辦法,只能從研發層面去規範,比如APP訪問數據時,盡量別出現select * from table的操作,而且架構設計時要及早考慮這點。
- APP層中各APP的新老Service介面無法兼容的,不能使用灰度發布。
問題詳述:舉個例子,比如APP1和APP2,APP1和APP2各自內部的介面,要能新老版本兼容.APP1和APP2之間相互調用的介面,也要能相互兼容。
應對思路:這個原則上要求一般的程式都要滿足,比如至少要求跨一個版本的兼容,多個版本間就不需要了。但實際操作時會略困難,牽涉到開發流程規範問題,需要開發測試同學一起配合,能做到單模組級別的測試,且各模組間要相互保持兼容和一致。
- 日常流量對灰度發布的影響有多少。
問題詳述:灰度發布過程中,需要逐步切走部分線上機器,用於驗證;如果線上請求量較大,需要慎重,選擇在低峰段進行。
應對思路:這個目前的解決辦法是通過增加機器來解決,我們目前採取雙機房四區域,4倍的流量冗餘,每次按照25%的流量依次進行灰度發布。
關於灰度發布的後續優化及改善,目前有考慮到幾個方面,總結如下,後續會逐步改進:
首先,當然是一個效率問題,目前雖然已經實現自動化,但發布過程中還是需要一定的人為介入,而且驗證周期較長,後續要考慮如何更流暢的使用。
其次,是不是每個發布都要走灰度進行,還是平滑發布後就能直接對外提供服務,比如一個Hotfix的修改,要不要灰度?這個需要有一定的標準。
再次,如果需要長時間來驗證灰度環境,線上會同時存在兩個甚至以上的版本,不利於運維維護,且監控方面需要加強。
最後,能否利用灰度發布的方式,在線上進行流量回放及全鏈路壓測,也是一個後續摸索的話題。