一、背景
1.1 爲什麼要搭建熱修復平臺
隨着公司的快速發展,需求的快速增加,App迭代也越來越頻繁,如果移動應用出現問題,不僅僅影響用戶體驗,還會影響公司口碑,甚至可能造成資損。需要快速修復線上問題,對比常規的開發流程而言,熱修復更加靈活方便,優勢很多:
- 無需重新發版,實時高效修復bug;
- 用戶無感知修復,無需下載新的版本,代價小;
- 修復成功率高,能把損失降到最低;
- 因此熱修平臺愈加重要,需要搭建一個高效,好用且安全的熱修復平臺。
1.2 思考
搭建熱修復平臺,首先要考慮熱修方案的選擇,但這不是本文的重點,我們這裏不做過多討論。目前有贊 Android 側的熱修是基於 Tinker 自建的後端服務 + Android SDK 實現的。
下圖簡要描述了熱修的主要過程:
看似簡單的流程,在多人開發團隊中,其實存在很多問題:
- 每次版本打包發佈,如何保存基準包及mapping等文件用於後續熱修生成補丁?
- 熱修代碼的分支如何規範管理?
- 如何構建補丁包,構建瞭如何保存 ?
- 補丁包如何快速高效的本地驗證 ?
- 補丁發佈策略,發佈審批等
- 補丁下發數據如何統計?
- 針對特定用戶怎麼查詢熱修狀態?
- 熱修出了問題怎麼定責,怎麼對熱修代碼追溯?
1.3 熱修平臺定義
針對以上問題,我們認爲熱修平臺應具備以下特點:
- 支持打包文件保存
- 定義標準的熱修分支規範
- 支持補丁包的構建保存
- 支持方便的本地驗證測試
- 支持權限審批
- 支持補丁全量,灰度及條件發佈
- 支持熱修狀態查詢,數據統計
- 支持歷史熱修代碼查看
二、熱修復平臺
2.1 寫在前面
在後面介紹熱修復平臺的過程中,會多次提到 MBD 及 APUB,這裏先做下說明,便於下文理解:
- MBD(Mobile Build): 有讚的移動應用構建平臺, 支持App構建, 熱修構建及SDK構建。
- APUB(App Publish): 應用及熱修等發佈平臺,APUB 的上游爲 MBD,承接了
CI
系統的產物。下游則是 C 端用戶,作爲應用發佈生命週期的最後一環,爲所有應用補全了熱修復 和 灰度分發相關的能力。
同時爲了講清楚熱修復平臺,本文以Android爲例按照熱修過程,順序介紹。
2.2 打包文件保存
Android 側使用 Tinker
首先要考慮的是構建產物及 mapping
等文件的保存,用於後續打補丁包
由於有贊內部 App 發版構建, 熱修構建,SDK構建等都是通過 MBD 構建平臺,且MBD 本身已支持打包產物的自定義上傳,因此藉助 MBD 構建平臺就可以做到保存 apk&mapping
文件。MBD 構建平臺打包是在運行着 GitLabRunner
的黑蘋果上進行的, GitLabRunner
是 GitLab
基於 Go
實現的腳本解釋器,如果感興趣可以自行了解下,這裏不再展開。
App 使用 MBD 打包需要先指定打包腳本,腳本爲 yaml
格式,其中 artifacts
指定哪些文件要上傳到 CDN
, 配置中的 paths
中指定了 Tinker
構建產物目錄 bakApk&mapping
,因此Tinker 產物會上傳到 CDN
, 腳本如下:
build:Git API
artifacts:
untracked: false
name: "out"
preview_pattern: "app-full-release.apk"
cdn_path: "xxxxxxxx"
paths:
- app/build/bakApk
- app/build/outputs/apk/
- app/build/outputs/mapping/full/release/
script:
- env
- ./gradlew clean assembleFullRelease -PTINKER_ID=$CI_BUILD_ID -PBAK_PATH=$CI_PROJECT_DIR/app/build/bakApk -Paar=$JOB_COMPONENT_DEPENDENCIES -PMBD_RELEASE=$JOB_BUILD_RELEASE -PMBD_TEST_VERSION=$JOB_COMPONENT_VERSION -PMBD_BRANCH_NAME=$CI_COMMIT_REF_NAME --no-daemon
2.3 熱修分支規範
MBD 操作拉取分支修復問題
移動團隊達到一定規模後,需要同步制定相應的分支規範,其中熱修相關的分支管理需要考慮兩個問題:
1. 應該從哪個分支拉取代碼修改打補丁?
2. 修復問題後熱修代碼合併問題?
這裏有必要簡單說明下:
有贊每次發版都會有開車的概念,所有待發布的功能都會上車合併到一個從master 分支拉出的 bus/${version}-${date}
的分支,在 bus/${version}-${date}
分支打出包後,開發同學自測然後交由項目的測試迴歸,沒問題後,最後經App的測試同學迴歸,迴歸通過後開發同學會將 bus/${version}-${date}
分支合入master 構建 release
包。
由於考慮到也可能會拉分支對老版本發佈熱修,因此上述MBD構建成功後也會入庫記錄該 release
版本構建時的 commit hash
。
基於以上兩點我們規定每個 release
版本都有一個固定的熱修分支爲 hotfix/${version}-mbd
,熱修分支的管理也是直接由MBD構建平臺統一規範處理的如圖所示:
MBD 構建平臺每個App構建,熱修構建,SDK構建都是一個集成單(多次構建行爲的集合,每次正式構建前都可能會有若干次測試構建),舉例來說如 App2.3.5
版本發現問題需要熱修復:
- 首先要在MBD 構建平臺搜索
2.3.5
版本的release
集成單,release
集成單中包含一個熱修復按鈕 - 點擊熱修復按鈕判斷
2.3.5
版本是否已經存在hotfix/2.3.5-mbd
分支? - 存在直接創建熱修復集成單,不存在MBD平臺調用
GitLabAPI
創建hotfix/2.3.5-mbd
分支 - 開發者在
hotfix/2.3.5-mbd
拉取創建修改問題的分支,如hotfix/xxx_bugfix
至此解決了 應該從哪個分支拉取代碼修改打補丁?
的問題,合併到哪個分支的問題,暫且不表,下文會講到。
2.4 補丁構建及保存
如上所述,熱修構建也是在MBD平臺完成的,由於之前app發版構建的產物已經打包
上傳到了 CDN
,再次構建時 MBD 平臺只需把產物下載解壓到 Tinker
基準包路徑,
同App打包邏輯,熱修構建也是通過 yaml
腳本配置,指定要上傳補丁文件的相對路徑,補丁構建命令執行結束後會上傳補丁文件到 CDN
,用於後續補丁下發,簡要過程如圖:
補丁構建腳本
patch:
artifacts:
untracked: false
name: "patch"
preview_pattern: "patch_signed.apk"
paths:
- app/build/outputs/apk/full/tinkerPatch/full/release/patch_signed_7zip.apk
- app/build/outputs/apk/full/tinkerPatch/full/release/patch_signed.apk
script:
- env
- pwd
- mv app/build/bakApk base/
- ls base/
- ./gradlew clean tinkerPatchFullRelease -Paar=$JOB_COMPONENT_DEPENDENCIES --no-daemon -POLD_BUILD_FLAVOR=$CI_PROJECT_DIR/base
- rm -rf base
2.5 驗證熱修
補丁上傳到CDN 後,爲了確保下發的補丁沒問題,需要驗證補丁,這是至關重要的一步。那怎麼加載補丁呢?Tinker
也提供了加載本地補丁包的 API
:
TinkerInstaller.onReceiveUpgradePatch(context, 補丁包的本地路徑);
因此我們只需要把 CDN
文件下載到特定路徑,在App重啓時檢測補丁文件是否已下載,如果已下載直接加載補丁即可。
驗證補丁首先要考慮怎麼方便開發者使用,步驟越少越快越好,因爲通常發佈熱修本身就是非常緊急的問題,由於有贊內部有移動助手App(支持常用的開發功能,開發環境切換,抓包等) 移動同學都會使用,因此可以把熱修驗證功能放在移動助手App。
從使用簡便程度上來說,二維碼似乎是不錯的選擇,因此我們定下的方案是,移動助手App 掃碼獲取二維碼信息,二維碼中包含:
- 補丁MD5 安全校驗 、簽名
- 補丁 CDN 地址
- 補丁對應App版本及基準包 CDN 地址
- 補丁對應App包名
其中第[3]點用於檢測驗證熱修的手機當前安裝的版本是否是基準包,如果不是提示下載安裝補丁對應基準包版本,避免浪費時間。
第[4]點用於補丁合成後,根據包名重啓App,主要是考慮到 Tinker
的機制補丁本地合成後,需要再次冷啓動使補丁生效。
移動助手App 掃碼上圖中的二維碼後,請求補丁信息,執行拉取補丁本地合成補丁,如果合成成功後被熱修App啓動後會看到熱修合成成功頁面,否則不能明確的知道是否已熱修合成,開發者會比較迷惑,同時爲了方便多次合成測試的場景,比如第一次補丁問題沒有修復,需要再次合成,也支持了清除補丁功能。如圖所示:
2.6 發佈策略
驗證補丁沒問題後,需要根據情況選定發佈策略,目前支持三種熱修發佈策略
2.6.1 全量發佈
全量發佈,不用解釋,補丁對應版本App所有用戶都可拉取補丁
2.6.2 灰度發佈
灰度下發支持按人數灰度 與 按比例灰度,按照人數灰度相對簡單,因此這裏只說下按比例灰度,灰度如果按照總人數的百分比進行下發,有可能會下發到不活躍用戶的設備上,讓百分比下發失去意義。目前一個簡單的方式是實現哈希碰撞算法,概率可調,當App端請求補丁時,根據設備的唯一標識進行碰撞,落到概率區間內則下發補丁。
2.6.3 條件發佈
很多時候在發佈一個補丁時,需要在小範圍內進行驗證,比如特定某個系統版本或者特定某個用戶;在驗證通過後再進行全網用戶的下發,這中場景下可以使用條件下發。
Apub 平臺在發佈補丁時可以選擇使用條件下發,除上傳補丁外,還可以填寫條件語句,只有滿足條件的設備纔會執行修復補丁。
其中條件語句由 key/value/運算符 組成,條件語句的規則與代碼中的條件表達式一致,支持 “==、!=、>、<、>=、<=、&&、||” 等運算符,如:
userId == 10023451 && roleType == 1
後端對DSL解析引擎可參考:https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/Rhino
另外特定版本的App 可能會發布多個補丁,如果結合使用多種下發補丁也會遇到些新的問題,舉例來說如果先條件發佈了一個補丁,再全量發佈了另一個補丁App應該怎麼處理?因此制定了App補丁使用規則:
- 若第一次下發補丁,包含了條件值,不符合條件的設備補丁不會生效。
- 若非第一次下發補丁,上一個補丁版本是全量下發,不符合條件的設備會請求上一個版本補丁。
- 若非第一次下發補丁,上一個補丁版本非全量下發(灰度/條件/開發),不符合條件的設備若之前請求過補丁,會保留執行之前的補丁,若沒有請求過補丁(新用戶),不會請求到補丁。
2.7 發佈審批
在確定了補丁使用哪種發佈方式後,還需要由指定人(通常爲TL)統一收斂權限,同時對熱修代碼做二次檢查(Code review) ,有贊不會允許未經複覈檢驗的熱修代碼隨意的發佈線上,萬一出了問題,可能會影響大量用戶。
對於有贊權限管理感興趣讀者的可以看這篇文章《有贊權限與審批流程的標準化》
引用上文中舉的例子說明:如果 A
同學需要修改 App2.3.5
版本的問題,發佈熱修復。
- 開發者需要從
hotfix/2.3.5-mbd
拉取hotfix/xxx_bugfix
分支 - 在
hotfix/xxx_bugfix
分支修改問題並構建補丁 - 接着申請發佈補丁,在審批通過之後,發佈熱修的同學在
Apub
平臺上操作下發在 MBD平臺構建並上傳到CDN
的補丁
仔細想想,是不是遺漏了什麼?還記得上文說到熱修分支規範時,修復問題後熱修代碼的合併問題麼?
爲了避免開發者在修改問題後直接發佈補丁,代碼忘記合併導致後續版本也有問題的情況,同時也爲了規範管理熱修分支。如圖:
Apub
發佈平臺在A
發起審批時,自動創建了hotfix/xxx_bugfix->hotfix/2.3.5-mbd
的MR
並自動寫入審批單申請理由中。- 在
A
點擊下發補丁時調用GitLabAPI
獲取MR
狀態,如果MR
已合併則允許下發,否則提示A
催促審批人合併代碼纔可下發補丁 - 最後下次發版時將
hotfix/2.3.5-mbd
分支添加到下一趟發版列表中,將bug
修復代碼帶到下一趟車中,最終合入master
2.8 熱修數據統計
補丁下發後,還需要實時觀察熱修生效情況,如果有問題要及時暫停下發或回滾補丁,有贊熱修提供了基礎的數據統計,包含已修復設備數量,合併失敗錯誤統計等。
2.9 設備熱修狀態查詢
在某些場景下,可能需要查詢特定用戶或特定用戶賬號的熱修狀態
常見的一種情況是:
用戶反饋了個線上問題,開發同學確認問題並修改發佈補丁後,悻悻的回覆用戶已經修復了,重複殺掉App打開幾次即可。用戶說好我試試,過了一段時間,又反饋說還是有問題啊,但是明明已經發布熱修復了(頭皮發麻),這時就可以根據用戶賬號信息查詢熱修狀態了,如下圖。
2.10 熱修代碼回溯
還有一些特殊情況,歷史版本裏發佈的熱修復導致了新的問題,需要確認問題責任人,或者排查特定問題,需要排除熱修代碼的影響,需要查看該版本發佈的熱修復代碼。
針對該情況,我們把上文中發起審批時創建的MR落庫記錄,並提供了查看代碼變更按鈕,點擊按鈕直接跳轉記錄的 GitLabMR
, 即可查看代碼變更。
三、平臺架構及流程
3.1 熱修平臺架構
上面講的內容比較多也比較雜,可以結合熱修平臺架構圖來看,有個全局的視角:
3.2 熱修流程梳理
最後我們還以上文中的例子回顧下分享的內容,有贊發佈熱修復的流程:
Android 發佈熱修流程
- 開發者在 MBD平臺搜索需要熱修版本的集成單,點擊熱修復按鈕,MBD會創建
hotfix/2.3.5-mbd
分支,同時創建一個熱修集成單 (MBD 構建平臺每個App構建,熱修構建,SDK構建都是一個集成單) - 開發者需要從
hotfix/2.3.5-mbd
拉取創建hotfix/xxx_bugfix
分支 - 在
hotfix/xxx_bugfix
分支修改問題提交代碼並在MBD平臺創建的熱修集成單上操作構建補丁 - 然後使用有贊移動助手App 掃碼驗證補丁
- 接着在Apub發佈平臺選擇熱修發佈方式,填寫申請發佈理由申請發佈補丁,Apub 平臺會自動創建
hotfix/xxx_bugfix
->hotfix/2.3.5-mbd
的MR
,並把MR
地址自動填充到申請理由中,開發者等待審批,審批通過之後,確認MR
合併,即可發佈操作下發補丁
iOS 發佈熱修流程
上文很少提及iOS 熱修復,主要是因爲iOS 熱修相對簡單,沒有 Android基準包等複雜邏輯:
- 根據具體問題,編寫熱修腳本,通過iOS 熱修SDK,本地運行調試
- 調試通過後在 Apub平臺上傳熱修腳本,並選擇熱修發佈方式,填寫申請發佈理由申請發佈補丁,iOS 側由於修復機制等原因,沒有自動創建
MR
等邏輯 - 審批通過之後,即可下發補丁
四、總結
本文主要介紹了有讚的熱修復平臺,及在搭建過程中遇到的一些問題。熱修平臺實現了高效、穩定、可靠的熱修復補丁上傳、驗證、分發、權限管理等功能,並提供補丁基本數據統計,可以直接複用到各業務線,避免重複建設。
有贊熱修復平臺,是結合有贊移動團隊實際開發過程遇到的問題,逐步解決逐漸完善的,讀者可以結合自身團隊打造合適的熱修復管理平臺,希望有贊熱修復平臺的建設經驗可以對你有所幫助。
如果你有比較好的建議,可以評論回覆,如有任何問題,歡迎指正。
本文轉載自公衆號有贊coder(ID:youzan_coder)。
原文鏈接: