有贊移動熱修復平臺建設

一、背景

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 的黑蘋果上進行的, GitLabRunnerGitLab 基於 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 版本發現問題需要熱修復:

  1. 首先要在MBD 構建平臺搜索 2.3.5版本的 release集成單, release集成單中包含一個熱修復按鈕
  2. 點擊熱修復按鈕判斷 2.3.5 版本是否已經存在 hotfix/2.3.5-mbd 分支?
  3. 存在直接創建熱修復集成單,不存在MBD平臺調用 GitLabAPI 創建 hotfix/2.3.5-mbd 分支
  4. 開發者在 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 掃碼獲取二維碼信息,二維碼中包含:

  1. 補丁MD5 安全校驗 、簽名
  2. 補丁 CDN 地址
  3. 補丁對應App版本及基準包 CDN 地址
  4. 補丁對應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 版本的問題,發佈熱修復。

  1. 開發者需要從 hotfix/2.3.5-mbd拉取 hotfix/xxx_bugfix 分支
  2. hotfix/xxx_bugfix分支修改問題並構建補丁
  3. 接着申請發佈補丁,在審批通過之後,發佈熱修的同學在 Apub平臺上操作下發在 MBD平臺構建並上傳到 CDN的補丁

仔細想想,是不是遺漏了什麼?還記得上文說到熱修分支規範時,修復問題後熱修代碼的合併問題麼?

爲了避免開發者在修改問題後直接發佈補丁,代碼忘記合併導致後續版本也有問題的情況,同時也爲了規範管理熱修分支。如圖:

  • Apub 發佈平臺在 A 發起審批時,自動創建了 hotfix/xxx_bugfix->hotfix/2.3.5-mbdMR 並自動寫入審批單申請理由中。
  • 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 發佈熱修流程

  1. 開發者在 MBD平臺搜索需要熱修版本的集成單,點擊熱修復按鈕,MBD會創建 hotfix/2.3.5-mbd 分支,同時創建一個熱修集成單 (MBD 構建平臺每個App構建,熱修構建,SDK構建都是一個集成單)
  2. 開發者需要從 hotfix/2.3.5-mbd拉取創建 hotfix/xxx_bugfix 分支
  3. hotfix/xxx_bugfix分支修改問題提交代碼並在MBD平臺創建的熱修集成單上操作構建補丁
  4. 然後使用有贊移動助手App 掃碼驗證補丁
  5. 接着在Apub發佈平臺選擇熱修發佈方式,填寫申請發佈理由申請發佈補丁,Apub 平臺會自動創建 hotfix/xxx_bugfix -> hotfix/2.3.5-mbdMR,並把 MR地址自動填充到申請理由中,開發者等待審批,審批通過之後,確認 MR合併,即可發佈操作下發補丁

iOS 發佈熱修流程

上文很少提及iOS 熱修復,主要是因爲iOS 熱修相對簡單,沒有 Android基準包等複雜邏輯:

  1. 根據具體問題,編寫熱修腳本,通過iOS 熱修SDK,本地運行調試
  2. 調試通過後在 Apub平臺上傳熱修腳本,並選擇熱修發佈方式,填寫申請發佈理由申請發佈補丁,iOS 側由於修復機制等原因,沒有自動創建 MR 等邏輯
  3. 審批通過之後,即可下發補丁

四、總結

本文主要介紹了有讚的熱修復平臺,及在搭建過程中遇到的一些問題。熱修平臺實現了高效、穩定、可靠的熱修復補丁上傳、驗證、分發、權限管理等功能,並提供補丁基本數據統計,可以直接複用到各業務線,避免重複建設。

有贊熱修復平臺,是結合有贊移動團隊實際開發過程遇到的問題,逐步解決逐漸完善的,讀者可以結合自身團隊打造合適的熱修復管理平臺,希望有贊熱修復平臺的建設經驗可以對你有所幫助。

如果你有比較好的建議,可以評論回覆,如有任何問題,歡迎指正。

本文轉載自公衆號有贊coder(ID:youzan_coder)。

原文鏈接

https://mp.weixin.qq.com/s?__biz=MzAxOTY5MDMxNA==&mid=2455761058&idx=1&sn=e7296c9a1e0f3e9a72fd4a0b8b246d06&chksm=8c687687bb1fff91faf37ca84044bb90a5ddbe55dd80bbccef30f361549ecf1f81364d9516ac&scene=27#wechat_redirect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章