在微服務的開發(fā)中, 我們會開發(fā)很多功能, 并在最短時間內上線, 也就是說經過了單元測試, API 測試, 自動化的集成測試, 以及少量的手動端到端的測試, 新增的改動就會在幾天內部署到產品線上.
這樣做符合"唯快不破"的理念, 而且很多開發(fā)人員也充滿自信, 不過畢竟風險太大, 產線出問題, 影響了客戶使用可不是鬧著玩的. 為穩(wěn)妥起見, 最好還是做灰度發(fā)布, 并且加一個開關, 一旦出問題, 馬上對部分或全體用戶關閉最新改動. 這個開關, 我們就叫做 Feature Toggle 特性開關.
我所在的開發(fā)團隊以前采用過一種叫 Train Release 方式來發(fā)布產品, 一個大的 Train Release 包含多個組件, 既有PC客戶端 (windows/macos/linux), 移動客戶端 (ios/android), Web 站點和服務, 后臺多種專用服務器.
我們一般分為三個大類
- Web: 各種 Web 站點以及管理工具
- Client: PC/Mobile 客戶端
- Server: 各種后臺服務
每個大類又分為多個子類, 一個 train release 通常要做大半年, 有專門的 release manager 負責協(xié)調, 開發(fā)工程師發(fā)布新版本后, 還要經歷 ATS/BTS/Production 的升級, 部署, 測試, 試用, 最終新版本交付到用戶手中經常歷時一年, 這就算比較順利的了, 如果中間出現(xiàn)了什么周折風波, 一年多新版本才能上線.
這個玩法顯然沒辦法滿足用戶的急迫的需求, 于是新的版本管理方法應運而生. 對于單個微服務來說, 我們希望能隨時發(fā)布和上線,只要它通過了構建流水線的層層檢查和測試, 可是對于整個系統(tǒng)來說, 成千上百個微服務每天不停地升級部署, 難免會在彼此集成的接口和流程方面有所疏漏, 所有我們對主要的幾個產品線實施了 monthly release, 即每月發(fā)布一個版本. 當然, 重大問題的緊急修復是可以隨時發(fā)布上線的.
- 月初: 主要需要和設計基本就緒
- 月中: 主要功能完成, 集成測試開始
- 月底: 經項目負責人及主要干系人驗收通過, 發(fā)布新版本
現(xiàn)代的微服務提倡小迭代, 快修改, 快上線, 每月只能上一個版本, 這顯然不能滿足快速持續(xù)交付的需求, 但是產品的穩(wěn)定性是必需保證的, 既不能由于頻繁部署而導致系統(tǒng)出現(xiàn)問題, 也不能因噎廢食限制新版本上線的頻率.
如果我們想每天上線新的版本, 我們就必須要做好分支管理, 版本管理和特性開關的控制
分支管理
分支類型 Branch Types:
- master branch 主分支
- feature branch 功能分支
- dev branch 開發(fā)分支
- hot-fix branch 緊急修復分支
分支策略 Branch Strategy :
- 小步快走, 多個開發(fā)分支, 一人一個, 未完成單元測試不準合并到其他分支
- 采用少量或很短生命周期的 feature branch, 未完成集成測試不準合并到主分支
- 產品分布只能在主分支上
- 月度新功能在測試驗收之前放在 feature branch 上
每日從主分支更新改動,并在月底測試驗收通過后合并到主分支, 并打上標簽 tag - 緊急修復放在 hotfix 分支上
代碼審查和測試過后, 立即合并到主分支
版本管理
軟件應用都有一個版本, 微服務也是如此, 版本可以用如下形式
mainVersion.minorVersion.microVersion.patchVersion
比如 2.2.0.147, 2.2.1.12, 四個小節(jié)夠了, 不宜過多
在產品的 API 中, 我們通常還會加上 git commit 信息
比如
GET https://mdd.example.com/mdd/api/v1/build_info
{
"version": "2.2.1",
"buildNumber": "247",
"gitTag": "2.2.1.247",
"gitCommit": "c8c30d043982c902202d436564b2fd726e61de32",
"gitBranch": "master"
}
新版本替換舊版本總有一個過程, 如果服務器比較少, 可以快速一次級升級, 但是對于大型 SaaS 服務提供商, 服務器數(shù)量至少上千臺, 集群上百個, 數(shù)據中心遍布全球, 時區(qū)都不相同, 不可能一步到位. 同時, 由于對新版本新功能的穩(wěn)定性,性能和運行效果并非十分有底, 也會采用灰度發(fā)布的策略, 逐個集群升級,
- 先升級副集群, 再升級主集群
- 先升級小客戶, 再升級大客戶
- 先升級亞太區(qū), 再升級歐洲區(qū), 最后升級北美區(qū)
為避免對客戶造成影響, 時間一般都在當?shù)貢r間的凌晨
升級的時候應按以下步驟進行
- 先檢查度量數(shù)據, 確認流量在主集群上運行正常,
- 把備份集群設置為 suspend 狀態(tài), 也就是在共享的數(shù)據存儲表中改一下狀態(tài), 確保流量不會跑到備份集群上.
- 把備份集群中服務器逐個升級到最新版本
- 在備份集群上作一些簡單的驗證測試, 運行若干主要的 TaP 測試用例, 看測試結果是否有問題, 如果有問題, 且不是環(huán)境問題, 嚴重性不容輕視, 則立即回滾, 此次升級取消
- 觀察度量數(shù)據, 確認已經沒有活躍的用戶在主集群上
- 將主集群設為 suspend 狀態(tài), GSLB 會根據 Health Check 的響應把流量分派到備份集群, 再重復上述 3, 4步, 升級主集群
由于升級需要一個過程, 產線上總有一段時間存在兩個或兩個以上的版本, 如果不加區(qū)分, 用戶會比較困惑, 一會兒界面不一樣了, 一會兒功能又變了, 過了一會兒, 可能又變回來了.
在 Sharding 比較嚴格的系統(tǒng), 這個問題并不突出, 不同的用戶會落在對應的特定集群上, 不會出現(xiàn)上述變來變去的情況.
我們做過的一個系統(tǒng)為了充分利用系統(tǒng)資源, 采用了在數(shù)據中心內部根據用量分派流量的設計, 這時就必須要好好考慮這個問題.

于是, 我們就引入了特性開關這個利器
特性開關 Feature Toggle
僅靠版本難以解決上面提到的用戶體驗因為版本變化造成的功能與界面來回變化的問題, 這時候, 我們應該使用特性開關 Feature Toggle
開關的類型
- feature/function toggle 功能開關
- release toggle 發(fā)布開關
- operation toggle 運維開關
- Permission toggle 授權開關
- Experiment toggle 體驗開關
這里, 我們主要討論功能或特性開關和發(fā)布開關
特性開關的層次
- organization 組織
- site 站點
- user 用戶
- device 設備
特性開關的使用
前面我們提到開關有五種類型, 不同類型有不同的應用場景
A/B 測試
在一部分站點上打開開關, 另一部分站點上關閉開關, 互相對照測試, 收集并觀察度量數(shù)據
也可以直接把開關的設置開放給用戶, 用戶按自己的喜好選擇, 在站點改版常用這一招來看用戶是否接受和喜歡新的變化
用戶試用
針對特定的用戶群體, 打開開關供用戶試用和測試
特性發(fā)布
待所有所需最低版本都已升級部署完畢, 打開所有的相關特性開關, 正式發(fā)布這一新功能
特性開關服務 Feature Service
我們可以開發(fā)一個 Feature Service, 可以供開發(fā)和運維人員調用它的 API 來設置 Feature Toggle, 其他的 Service 可以調用它的 API 來讀取這些 Feature Toggle 來決定應用不同的邏輯和行為, 而所有這些操作無需重啟服務進程和更改配置文件.
FeatureToggle 比較簡單的就定義一個布爾值 true or false , yes or no, 或者 on or off
比如
{
"enableAutoLogout": true
}
稍微復雜的可以有枚舉值: off, test, on, 并且加上失效時間
{
"enableAutoLogout": "test",
"expireTime": "2018-12-31T23:59:59Z"
}
以 user level 的 feature toggle 舉例如下
- 設置 Feature Toggle
POST /featuretoggles/users
{
"userIds": ["123", "456", "789"],
"featureToggles":
[
{
"key": "enableAutoLock",
"value" "true"
},
{
"key": "enableAutoLogin",
"value" "true"
}
]
}
- 讀取 Feature Toggle
GET /featuretoggles/users/:userId
{
"featureToggles":
[
{
"key": "enableAutoLock",
"value" "true"
},
{
"key": "enableAutoLogin",
"value" "true"
}
]
}
具體的實現(xiàn)很簡單, 利用傳統(tǒng)DB 或者 Redis, Cassandra 這樣的 NOSQL 存儲讀寫就可以了, 存儲結構示例如下
Table FeatureToggle:
| level | Id | toggleName | toggleValue |
|---|---|---|---|
| org | 123 | enableRateLimit | true |
| site | 456 | enableCircuitBreaker | true |
| user | 789 | enableAutoLock | true |
相關類庫
Java 工具庫
- Togglz
- FF4J
- Fitchy
- Flip
Python 工具庫
- Gargoyle
- Gutter
.NET, C# 工具庫
- FeatureSwitcher
- NFeature
- FlipIt
- FeatureToggleNET
- List on NUget
- FeatureBee
Ruby and Ruby on Rails 工具庫
- rollout
- feature_flipper
- flip
- setler
- switches
- FluidFeatures