寫在前面的話
灰度這個概念,來自數字圖像領域,最初是描述黑白數字圖像的灰度值,范圍從 0 到 255,0 表示黑色,255 表示白色,中間的數值表示不同程度的灰色。
灰度系統的誕生源于交叉學科的建設,在互聯網上也不例外。對于一個軟件產品,在開發和發布的時候肯定希望用戶能夠順利的看到想讓其看到的內容。但是,發布沒有一帆風順的,如果在發布的某個環節出了問題,比如打錯了鏡像或者由于部署環境不同觸發了隱藏的bug,導致用戶看到了錯誤的頁面或者舊的頁面,這就出現了生產事故。為了避免這種情況出現,借鑒數字圖像處理的理念,設計師們設計出了一種介于 0 和 1 之間的過渡系統的概念:讓系統可以預先發布,并設置可見范圍,就像朋友圈一樣,等到風險可控后,再對公眾可見。這就是灰度系統。
灰度系統版本的發布動作稱作 灰度發布,又名金絲雀發布,或者灰度測試,他是指在黑與白之間能夠平滑過渡的一種發布方式。在其上可以進行A/B testing,即讓一部分用戶繼續用產品特性A,一部分用戶開始用產品特性B,如果用戶對B沒有什么反對意見,那么逐步擴大范圍,把所有用戶都遷移到B上面來。(概念來自知乎)
立即學習“前端免費學習筆記(深入)”;
對于前端領域,演進到現在,灰度系統主要有如下幾點功能:
-
增量灰度:小的patch可以增量的添加在發布版本上,也可以通過開關一鍵關閉
-
用戶灰度:增量和全量版本都可對不同群體或者某幾個特定的用戶進行灰度可見
-
版本回退:每一個版本都在灰度系統里可見,可以一鍵回退
前端灰度系統工作流程圖如下:
sequenceDiagram 前端項目-->灰度系統:?部署階段 前端項目->>灰度系統:?1.CI?寫入打包資源 前端項目->>灰度系統:?2.CI?打包完成后更新資源狀態 前端項目-->灰度系統:?訪問階段 前端項目->>灰度系統:?1.頁面訪問,請求當前登錄用戶對應的資源版本 灰度系統-->>前端項目:?2.從對應版本的資源目錄返回前端資源
灰度規則
關于灰度資源優先級的說明如下:
灰度策略 | 優先級 |
---|---|
未生效 | 低 |
生效 | 高 |
全量 | 一般 |
如此就起到了灰度的作用:全量表示所有人都可以看;生效表示只有在規則中的用戶才可以看到這部分增量更新,優先級最高;未生效表示不灰度,優先級最低。
灰度系統數據庫設計
為什么灰度系統有后端:前端項目 CI 部署后,會產生一個 commit 號和一個鏡像記錄,并且打包后的文件存放在服務器中某一個深層的文件夾目錄中,灰度系統需要存入該部署的目錄地址,便于在切換灰度時查找不同版本的文件。
先介紹一個要部署的前端項目(你可以根據自己的前端項目動態調整)。
本項目針對的前端項目是一個基于微服務架構的項目,
下面是設計ER圖:
我們依此來分析:
子項目表
該表用于存放所有子項目的信息,新建一個微服務子項目時,會在這個表里新建一個條目,數據示意如下:
灰度用戶表
用于灰度系統登錄的用戶,擁有灰度權限的人才可以加入。
資源表
資源表存放項目在 CI 中寫入的 commit 信息和 build 完以后在服務器的存放位置,數據示意如下:
其中 branch 是跑CI的分支,data 存放打包資源目錄信息,一般結構如下:
gitProjectId 存放該產品在 gitlab 中的項目號, status 表示構建狀態:0:構建完成 1:部署完成 2:構建失敗,3:部署失敗。
這里簡單提一下 CI 是如何寫入灰度系統數據庫的,過多詳情不做解釋,寫入數據庫方式很多,這只是其中一種實現方式。
-
首先在 CI build 環節往服務器寫入打包信息的 JSON:
其中 build.sh 負責把傳入的參數寫到一個 json 中。
-
在 CI 部署環節,通過調用腳本創建資源:
其中 run_gray.js:
const?{?ENV,?file,?branch,?projectId,?gitProjectId,?user,?commitMsg?}?=?require('yargs').argv; axios({ ????url:?URL, ????method:?"POST", ????headers:?{ ????????remoteUser:?user ????}, ????data:?{ ????????Action:?"CreateResource", ????????projectId, ????????branch, ????????commitMsg, ????????gitProjectId, ????????channel:?Channel, ????????data:?fs.readFileSync(file,?'utf8'), ????????status:?"0" ????} }).then(...)
其中 status 的變化,在 CI 部署服務器完成后,追加一個 UpdateResource 動作即可:
if?[[?$RetCode?!=?0?]];?then?curl?"$STARK_URL"?-X?'POST'?-H?'remoteUser:?'"$GITLAB_USER_NAME"''?-H?'Content-Type:?application/json'?-d?'{"Action":?"UpdateResource",?"id":?"'"$ResourceId"'",?"status":?"2"}'?>?test.log?&&?echo?`cat?test.log`;?fi
灰度策略表
灰度策略是對灰度資源的調動配置。其設計如下:
其中,prijectId 表示灰度的項目,resourceId 表示使用的資源,rules 配置了對應的用戶或用戶組(看你怎么配置了,我這里只配置了單獨的 userId),status 是灰度的狀態,我設置了三種:
-
default: 未生效
-
failure: 生效
-
success: 全量
狀態生效表示是增量發布的意思。
到這里,數據庫設計就完畢了。
灰度系統接口API開發
有了數據庫,還需要提供能夠操作數據庫的服務,上邊創建資源的接口就是調用的灰度自己的API實現的。主要的API列表如下:
名稱 | 描述 |
---|---|
getResourcesByProjectId | 獲取單個產品下所有資源 |
getResourcesById | 通過主鍵獲取資源 |
createResource | 創建一個資源 |
updateResource | 更新一個資源 |
getIngressesByProjectId | 獲取單個產品下灰度策略任務列表 |
getIngressById | 通過主鍵獲取單個灰度策略任務詳情 |
createIngress | 創建一個策略 |
updateIngress | 更新一個策略 |
剩余的接口有用戶處理的,有子項目管理的,這里不做詳述。除了上邊的必須的接口外,還有一個最重要的接口,那就是獲取當前登錄用戶需要的資源版本的接口。在用戶訪問時,需要首先調用灰度系統的這個接口來獲取資源地址,然后才能重定向到給該用戶看的頁面中去:
名稱 | 描述 | 接收參數 | 輸出 |
---|---|---|---|
getConsoleVersion | 獲取當前用的產品版本 | userId,products | resource鍵值對列表 |
getConsoleVersion 接受兩個參數,一個是當前登錄的用戶 ID, 一個是當前用戶訪問的微服務系統中所包含的產品列表。該接口做了如下幾步操作:
-
遍歷 products,獲取每一個產品的 projectId
-
對于每一個 projectId,聯查資源表,分別獲取對應的 resourceId
-
對于每一個resourceId,結合 userId,并聯查灰度策略表,篩選出起作用的灰度策略中可用的資源
-
返回每一個資源的 data 信息。
其中第三步處理相對繁瑣一些,比如說,一個資源有兩個起作用的灰度資源,一個是增量的,一個是全量的,這里應該拿增量的版本,因為他優先級更高。
獲取用戶版本的流程圖如下:
graph?TD 用戶登錄頁面?-->?獲取所有產品下的資源列表 獲取所有產品下的資源列表?-->?根據灰度策略篩選資源中該用戶可用的部分?-->?返回產品維度的資源對象
最后返回的資源大概長這個樣子:
interface?VersionResponse?{ ????[productId:?number]:?ResourceVersion; } interface?ResourceVersion?{ ????files:?string[]; ????config:?ResourceConfig; ????dependencies:?string[]; }
其中 files 就是 JSON 解析后的上述 data 信息的文件列表,因為打包后的文件往往有 css和多個js。
至于這個后端使用什么語言,什么框架來寫,并不重要,重要的是一定要穩定,他要掛掉了,用戶就進不去系統了,容災和容錯要做好;如果是個客戶比較多的網站,并發分流也要考慮進去。
前端頁面展示
前端頁面就隨便使用了一個前端框架搭了一下,選型不是重點,組件庫能夠滿足要求就行:
-
登錄
-
查看資源
-
配置策略
部署以后,實際運行項目看看效果:
可以看到,在調用業務接口之前,優先調用了 getConsoleVersion來獲取版本,其返回值是以產品為 key 的鍵值對:
訪問轉發
這里拿到部署信息后,服務器要進行下一步處理的。我這里是把它封裝到一個對象中,帶著參數傳給了微服務的 hook 去了,可以期待一下后續的手寫一個前端微服務的系列文章;如果你是單頁應用,可能需要把工作重心放在 nginx 的轉發上,通過灰度系統告知轉發策略后,Nginx負責來切換路由轉發,可能只是改變一個路由變量。 (,下面我簡單的給個示意圖:
graph?TD 灰度系統配置灰度策略?-->?告知Nginx資源地址? 告知Nginx資源地址?-->?Nginx服務器配置資源轉發