前端灰度發布落地方案
?
前言
前段時間在面試的時候遇到過前端灰度發布相關的問題,剛好在之前公司有設計過前端灰度發布的方案,這套方案也在多個系統上得到過驗證了,最近有時間整理,現在也拿出來和大家交流下,在結尾也給大家留下了一些的代碼實現,有興趣的伙伴可以去查看下
tips
關于灰度規則的一些放量算法也比較容易找到,這篇文章重點不是講算法,只是更多貼合實際場景把灰度方案落地,對于放量算法有高要求的伙伴可以自行搜一下放量算法相關,桶漏、令牌算法等
什么是灰度發布
將某個功能灰度發布(逐漸放量)給特定線上人群,避免新功能全量上線帶來的風險
上白話文,某項目當前處于1.0版本,但是想更新一個1.1版本,1.1版本內測沒有問題了,但是由于改動了關鍵的功能,想要實現只給一部分線上用戶使用體驗,看看反饋。
這個時候線上就需要一部分用戶繼續用1.0版本,一部分用1.1的版本,如果1.1版本接收到反饋的問題嚴重到影響上線了,那么就回退1.0版本,影響的用戶范圍比較小,如果1.1版本穩定,那就直接給所有用戶過度到1.1版本。實現這種場景效果,就是灰度發布。
什么是灰度規則?灰度規則可以是用戶等級、性別、地區、客戶端等業務信息或者設備信息,比如灰度規則設定為廣東地區的用戶放問1.1版本,那么廣東用戶訪問項目的時候就算命中了灰度規則,給他們轉去1.1版本,其他地區的用戶繼續使用1.0版本
常見灰度發布方案
灰度方案各式各樣,既有多樣就有對比,沒有最好,只有最合適自己的業務場景,這里給大家介紹幾種方案,以便大家做比較選擇
1. 簡單ngxin分流(推薦指數:??)
本身只依賴nginx來做的分流還算不上灰度發布的,但是偶然間跟朋友聊起了他們小公司的騷操作實現,賴著說要我寫進來,說他們已經試驗過了
- 兩份代碼,分別部署
- 通過nginx加權輪詢來控制訪問百分比(在客戶端cookie不存在標識的前提)
- 前端引入了sdk(瞄了下源碼,其實就是往cookie存入一個隨機不重復(還只是大概率不重復吧)的標識
- 二次訪問的時候,nginx通過對cookie中的唯一標識來返回對應的版本
優點: 簡單,不涉及后端操作
缺點:
- 只能簡單依賴nginx加權輪詢百分比來控制流量,全靠前端,無法結合業務做分流
- 可控性弱,在灰度版本出現問題的時候,只能通過修改nginx配置來讓用戶回退版本
- 問題收集能力差,只能等待用戶反饋
- 在客戶端cookie被清理掉后,用戶需要重新通過nginx的加權輪詢進入,有可能被分配到與上一個分配不同的版本
2. nginx + lua + redis(推薦指數:????)
tips:這套方案可能是沒找到好的資料或者對這套方案理解得不夠深刻,我們覺得靈活性有些欠缺,比較難結合復雜的業務做過多的灰度邏輯判斷,如果有大佬用過這套方案的,求不吝賜教。
- 當用戶請求到達前段代理服務nginx,內嵌的lua模塊解析nginx配置文件中的lua腳本代碼
- lua變量獲取到客戶端的ip地址,去查詢redis緩存內是否有該建值,如果有返回值執行灰度版本邏輯,否則執行當前生產環境版本
nginx + lua + redis方案網上的資料也比較多,大家可以自行了解,雖然我們對著套方案理解不透徹,從整個鏈路長度理論來看這套方案效率應該是比較高的,所以還是給大家貼了一些文章參考
參考文章1[1]
參考文章2[2]
參考文章3[3]
3. 服務端渲染分流(推薦指數:??????)
服務器渲染分流的方案,其實也是我覺得比較好使的一個方案,這里我先做一些流程簡述,后續也會單獨對著一塊做一些介紹
- 前端打包好的兩份代碼分別部署到服務器上(這里以單頁面應用為例,多頁面的話需要單獨處理一些其他細節)
- 在后臺管理添加版本(實際上就是讓服務端讀取單頁面的index.html)
- 客戶端訪問服務端,服務端根據灰度規則set-cookie并在redis存儲,返回對應版本的index.html
- 二次訪問通過服務端的時候,如果存在cookie并且redis已經存在對應的版本信息,則直接返回,否則重新走灰度流程
優點:靈活、可控性強,可結合業務體系做灰度放量規則 缺點:幾乎是后端一把梭,對服務器有壓力,需要多做相關優化,多頁面應用使用比較麻煩
4. 客戶端注釋判斷(比較難維護)(推薦指數:推條毛毛,不推薦)
客戶端通過注釋條件編譯,來做灰度,其實就是根據灰度規則對應在代碼層面上做判斷顯示哪些版本的功能,這種方案也有公司在使用,灰度功能一但多了,極其難維護,不推薦,這里就不過多介紹了
5. nginx + 服務端 + redis + [前端sdk] (推薦指數:??????)
整體方案概述
- 我們先把線上的穩定版本稱為stable版,本次發布的新功能版本稱為beta版
- 開發人員給stable和beta版本各自啟動了nginx服務,在運維層啟動了一層入口nginx服務,作為轉發
- 客戶端通過域名訪問項目,通過請求灰度規則,命中灰度規則后,并給客戶端設置cookie作為標識,并將用戶標識存放到redis,將用戶重定向到指定的版本
- 灰度規則接口請求的時候,如果已經帶有cookie則直接返回對應版本,不存在cookie則去查找redis,redis中存在對應信息則直接返回,如果不存在則走灰度規則識別流程
- 前端sdk功能:用于控制發起灰度規則請求的時機、回調操作和其他業務操作
sdk的使用場景:
項目中需要在特定的時機觸發灰度功能,點擊某個按鈕,或者進入某個頁面,比如某些應用是會彈出彈窗,告訴用戶 有內測版本,是否需要體驗,點擊同意后才跳轉到灰度版本
方案設計圖示
名詞代號
- stable:正式生產環境(1.0版本)
- beta:灰度版本(1.1版本)
- uuid:代碼演示中,沒有做賬號系統,沒有登錄行為,所以通過url上帶上uuid作為用戶id來走流程
具體實現(簡單演示)
- 分別創建兩個html假設是兩個項目,beta是新功能灰度版本,stable是當前生產環境版本
- 在前端引入sdk(前端sdk非必須,看業務場景使用)
- 前端發起請求,獲取版本信息(如果引入了sdk,可以通過配置做這一步驟)
4. 后端服務邏輯:
后臺實現代碼
//這里只是演示,直接通過鏈接獲取用戶id,實際場景應該是通過獲取用戶會話去判別用戶相關信息
const uuid = ctx.query.uuid;
//可以進入灰度版本的uuid,在數據庫存放
const uuids = ['123','456','789']
//redis 中存放了的的用戶id,如果清理了redis,則意味著,取消用戶的版本標識,這里簡單的用數組存放,實際應用場景根據各自的業務信息考慮是否需要多集合存放
const redisUuids = [{id: '789', version: 'beta'}, {id: '333', version: 'stable'}];
上面代碼邏輯是當uuid為123或者456或者789的時候就命中灰度規則,就進入beta版本 redis中已經存放了uuid為789和333的用戶了
5. 效果:
灰度問題處理操作
- 問:如果在上線后灰度版本出現嚴重的問題,需要緊急回退操作 答:直接后臺關閉灰度功能,清除redis,結束用戶的登錄會話(實際是清除客戶端cookie操作)
- 問:需要指定某個用戶進入某個版本 答:后臺修改redis信息,結束用戶的登錄會話
- 問:指定項目中某個頁面才啟用灰度 答:可以在前端sdk中處理相關邏輯,把相關的頁面路徑作為名單給前端識別(sdk最好動態引入,sdk放在cdn上)
代碼
彩蛋代碼
公司后端是用了java去實現的,在這里為了方便大家更好的去理解整個流程,也用node給大家實現了一遍,有興趣的小伙伴去可以直接去看代碼github[4],大體的設計思路是一樣的
注意點: 為了方便運行查看演示,我們是通過docker compose來跑的,在有docker和docker compose的前提下,可以直接通過命令跑起示例
docker-compose build
docker-compose up -d
localhost:8000
結語
方案千千萬,選擇自己合適的就好,演示代碼中只是簡單的寫了一些邏輯性的代碼,并不是真正可放到項目的邏輯,具體還是要結合實際的項目場景調整,前端sdk和java部分的代碼沒有放出來,是因為該方案已經在公司實行過的,不便放出,大家可以根據大致的思路來編寫,文中有錯的地方或者有更好的方案還望各位大佬不吝賜教