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