筆者在剛進入阿里的時候,其實連灰度是什麼也不知道,可是灰度這個概念在大廠很是廣泛,只要有必定用戶量的應用都會涉及到灰度發佈,因此準備進大廠的同窗,灰度發佈這個概念必定要了解一下。javascript
灰度發佈,又被稱之爲金絲雀發佈,是指某次新發布功能特性和舊功能特性之間可以以平滑過渡的方式呈現給用戶,就像金絲雀的羽毛同樣多種顏色平滑漸變。html
舉個例子,某個已上線處於運行中的系統須要一次新的功能迭代,可是因爲功能變更較大,因此發佈須要考慮用戶的使用反饋以及代碼可能存在一些未知的異常,這時候則須要將新的功能逐步地一批一批的推送給用戶。前端
在這個逐步放量的過程當中,能夠根據用戶接受度(用戶投訴多很少)和觀察本次功能是否存在上線前未發現的異常,來決定是否繼續發佈推送新功能,若是新功能反饋較差或者存在功能異常問題,則中止放量或者回滾到以前穩定的版本,及時修改問題。vue
這樣便避免一次推送狀況下,若是出現問題則形成線上問題忽然上升形成阻塞用戶使用的問題。java
1.提早收集用戶使用意見,及時完善產品功能
2.控制未知異常只出如今小範圍內,不影響大多數用戶
3.發現產品是否存在外在問題(如合規),可及時回滾至已舊版本react
1.放量規則nginx
若是逐步推送新功能,則必須有一種規則讓用戶按照某些特徵分紅不一樣的羣體,這個規則能夠是年齡,城市,或者用戶註冊時的id。例如,用戶註冊時有一個從0自增的序號位,當灰度放量時能夠以該序號爲維度,從小到大的放量,直至百分百完成。程序員
一個完善的系統在設計之初必定會考慮到灰度方案,若是你仔細觀察用戶的uid在註冊的時候必定有一個序號位,像身份證號裏第十五位是從0-9的序號位,通常的用戶UID會留兩位做爲自增序號位,灰度時這兩位通常被做爲灰度特徵。
面試
2.資源新舊版本ajax
能明確的標識出要給用戶展現兩種頁面形態,能夠是之前端靜態版本號的形式,如每次發佈資源後,靜態資源的版本號連接改變一下,這時候灰度則實際上是兩個不一樣資源請求連接逐步從舊到新的過程。
下面是灰度的實現基本原理,最關鍵的仍是判斷灰度用戶這一步操做,能夠在請求發出去前進行判斷,而後直接請求對應的資源,也能夠請求到了服務端後,服務端先區別出用戶是否屬於灰度名單內,再返回對應資源內容,具體還要看前端應用是怎樣的形式部署的,服務端渲染或者是客戶端渲染均有關係。
1.服務端渲染應用
服務端渲染應用會在返回客戶端以前將靜態模板渲染好,知道這個是很是重要的,這意味着前端灰度這個過程要在用戶的請求返回以前就完成,在客戶端不處理任何灰度相關的內容。以下圖:
簡單描述一下,這裏用戶首先發起請求後,服務器並不會直接組裝靜態資源,而是先去灰度規則裏獲取名單,而後將灰度名單拿到進行判斷處理來決定渲染那一套模板資源給到客戶端,最終給處於灰度名單裏的用戶展現新版本頁面,而非名單內的用戶繼續使用舊版本的頁面內容,如須要放量時,直接在灰度規則裏進行修改便可。
看看下面代碼示例
// 服務端代碼
// 靜態模板
const model1 = () => {
return ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> </head> <body> <div id="mydiv">我是A界面</div> </body> </html> `
}
const model2 = () => {
return ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> </head> <body> <div id="mydiv">我是B界面</div> </body> </html> `
}
const isPass = getRule(req.uid) // 查詢規則
if (isPass) { // 在白名單
res.render(model2)
} else {
res.render(model1)
}
...複製代碼
上面的代碼中,有A,B兩個版本的界面,用戶請求的資源在返回以前先經過getRule獲取灰度規則,肯定了是否在白名單裏,而後決定返回那一套模板內容。
2.先後端半分離的應用
這裏先後端半分離的應用是指在有一部分前端應用的html文件依舊在服務端上,可是實際上卻在客戶端渲染的,相信你們見的比較多,例以下面的這段代碼。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> </head> <body> <div id="react-content"></div> </body> <script src="http://cdn.com/1.0.1/my.js"></script> </html>複製代碼
當咱們使用react或者vue的時候,最後將代碼打包到一個JavaScript文件裏,在一個html文件中加載使用,而這個html文件則會被放到一個服務端系統裏,當用戶請求到資源時,將這段html返回給客戶端,客戶端拿到內容後加載在http://cdn.com/1.0.1/my.js 的網絡資源,而後本地渲染。
這裏注意一下,咱們每次打包的資源都會有一個版本號,好比上面的版本是1.0.1,這裏在cdn上的存貯路徑也是1.0.1/my.js,使用路徑的惟一性來區別與其餘版本不一致,固然也能夠在文件名上加版本號,如 /my_1.0.1.js,只要能識別出資源的惟一性均是能夠的。
下面來一段僞代碼看看這裏灰度又該怎麼作。
// 服務端代碼
const isPass = getRule(req.uid) // 查詢規則
let version = 1.0.1; // 舊版本號
if (isPass) { // 在白名單
version = 2.0.0
}
// 靜態模板
const model = version => {
return ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> </head> <body> <div id="react-content"></div> </body> <script src="http://cdn.com/{version}/my.js"></script> </html> `
}
res.render(model(version)) // 返回帶版本號的模板複製代碼
這段代碼中,由於兩次迭代需求,前端開發者將前端資源打包成了兩個資源包,分別上傳到CDN的不一樣位置處,以版本號做爲標識來肯定新舊內容。
當用戶的請求被接收到後,先經過getRule獲取灰度規則來肯定給當前用戶展現哪個版本號的資源,而後返回帶着資源版本的模板內容,客戶端接收到該模板以後,再加載對應的版本號資源,從而達到灰度要求。
3. 客戶端渲染的前端應用
目前來講最多見的一種部署類型,前端開發完成後,直接打包至CDN上,而後利用nginx來請求到靜態資源,這時候CDN服務器並不會去作灰度判斷相關的操做,即這時候不能讓後端經過一段獲取灰度邏輯來控制版本,而此時前端數據請求都是異步ajax的方式,那灰度又該怎麼作呢?
第一種思路:
咱們能夠在前端代碼裏寫兩套內容,在頁面渲染以前發起異步獲取灰度規則的請求,將結果拿到後在客戶端決定渲染那一套頁面,從而達到灰度的要求。
看看下面的僞代碼:
// 客戶端端代碼
// 組件
const component1 = () => {
return (<div>我是A組件</div>)
}
const component2 = () => {
return (<div>我是B組件</div>)
}
const isPass = $.ajax('/getRule?uid') // 查詢規則
...
render() {
if (isPass) { // 在白名單
return model2()
} else {
return model1()
}
}
...複製代碼
上面的這段客戶端代碼便可完成用戶灰度,可是有一個問題,當後期需求增多的時候前端代碼將很是龐大,並且每次的新需求發佈的時候勢必要去測試迴歸舊的版本是否被改動了,將維護兩套內容,隨着應用體積變大維護將變得很是累。
第二種思路:
若是咱們繼續保持版本號來區分每次的迭代,只是在渲染前獲取到正確的版本資源來渲染是否是就能夠解決上面的問題呢?
看看下面的僞代碼:
// 客戶端端代碼
const syncLoadJs = function (version, fn) {
const oScript = document.createElement('script');
oScript.setAttribute('src',`https://cdn.com/{version}/my.js`);
oScript.setAttribute('type','text/javascript');
oScript.onload = fn;
oScript.onerror = function() { window.location.href = '/error.htm' };
document.body.appendChild(oScript);
},
...
const isPass = $.ajax('/getRule?uid') // 查詢規則
let version = 1.0.1; // 舊版本號
if (isPass) { // 在白名單
version = 2.0.0
}
syncLoadJs(version, function(){
ReactDOM.render()); // 客戶端獲取完資源後進行渲染
})
...複製代碼
這一種方式則是在客戶端渲染以前先進行異步獲取名單來決定資源版本,在拿到資源版本以後纔會進行頁面渲染工做。
可是這裏存在一個問題,每個頁面都須要去獲取灰度規則,而後判斷是否灰度,這個灰度請求將阻塞頁面的,可能會形成較差的用戶體驗,因此咱們能夠考慮使用客戶端的localStrage來存儲這個用戶是否爲灰度用戶,而不是每次請求資源時都發請求去判斷是否爲灰度用戶,而後按期的更新localStrage內存儲的值,取代大量的請求形成的體驗問題。
如上內容均爲本身總結,不免會有錯誤或者認識誤差,若有問題,但願你們留言指正,以避免誤人,如有什麼問題請留言,會盡力回答之。若是對你有幫助不要忘了分享給你的朋友!也能夠關注做者,查看歷史文章而且關注最新動態,助你早日成爲一名全棧工程師!
往期精彩回顧