天天看點

複雜系統如何在不停機更新同時保持穩定?你必須考慮以下幾個點...背景閑魚搜尋服務基本架構保持相容無狀态服務更新有狀态服務更新服務發現風險防控總結

作者:閑魚技術-蘭林

背景

在網際網路行業,線上服務的更新更新可謂家常便飯。據統計,在過去的一個季度中閑魚工程師們執行了千餘次釋出,總計更新的代碼數量超過百萬行。

這些釋出中,有一些可能隻更新了幾行代碼,而有一些可能執行了整個叢集的遷移更新。而無論這些變更的影響面有多大,我們都必須保證線上服務的可用性,使用者無感覺。本文将以閑魚搜尋服務的遷移更新為例,向大家介紹其背後的技術方案。

閑魚搜尋服務基本架構

閑魚的底層搜尋服務由查詢規劃服務 Search Planner、查詢了解服務 Query Planner、打分排序服務 Rank Service 以及搜尋引擎 Heaven Ask 3 所組成。它們之間的互相調用關系如下圖所示:

複雜系統如何在不停機更新同時保持穩定?你必須考慮以下幾個點...背景閑魚搜尋服務基本架構保持相容無狀态服務更新有狀态服務更新服務發現風險防控總結

可以看到,整個搜尋服務是由多個互相獨立的微服務所構成的。不同的微服務之間互相隔離,通過預先向外暴露的接口提供服務。所有的微服務最終通過 Search Planner 收口,對外提供統一、完整的搜尋能力。

在底層搜尋服務之上,還有業務邏輯層和接入網關層,具體架構在此不再贅述。使用者的搜尋請求先通過網關層轉發給邏輯層處理,再向底層搜尋服務發起搜尋請求。這條請求鍊上包含數十個叢集,調用深度達到兩位數,整個過程中提供服務的伺服器數量可能有成百上千。

對于這樣一個複雜的系統,更新過程顯然無法一蹴而就。好消息是各個微服務之間合理的解耦合給更新工作帶來了很大的便利,有效避免牽一發動全身而導緻無從下手,使我們可以分門别類地處理更新問題。

  • 注1:Search Planner 是一個基于函數式、服務化、可視化、并行化開發架構所建構的搜尋服務網關層。
  • 注2:Query Planner 的主要作用是了解使用者輸入,然後對搜尋詞進行算法優化。最終獲得更好的搜尋召回結果。
  • 注3:Rank Service 是實時打分排序服務,它的作用是根據多元度的特征對搜素引擎召回的海選結果進行算法打分。分數越高的商品就越有機會出現在搜尋結果的前列。
  • 注4:Heaven Ask 3 (問天3)是阿裡巴巴研發的一款穩定高效、功能強大的搜尋引擎。為阿裡集團包括淘寶、天貓在内的核心業務提供搜尋服務支援。

保持相容

開始更新之前,我們首先需要确認被更新的服務是否保持了向前與向後相容性。保持相容不僅減少了工作量,也減少了更新所導緻的故障風險。

為了盡量避免更新導緻的不相容,我們可以總結一些開發原則:

  • 遠端過程調用(RPC)需要能夠忽略未知參數,并且允許缺失參數。
  • 如果需要删除已有參數,需要與所有依賴方确認。可以先将參數标記為 Deprecated 而不是直接移除。
  • 使用參數時,區分預設值和缺失值。
  • 如果接口無法保持相容,則建立新接口代替舊接口。不要破壞舊接口的相容性。

在更新時,先更新那些沒有外部依賴的服務。等到被依賴方更新完畢之後,再去更新依賴方。确定了每一個服務的更新順序之後,我們再根據服務的實際情況确定更新方案。

無狀态服務更新

正式進入更新流程,我們首先關注搜尋鍊路中的被設計成無狀态服務的部分,例如用于處理業務邏輯的 Java 微服務、用于處理查詢邏輯的 Search Planner 等。它們的共同特點是,每個請求處理完畢之後,關于該次請求的資源即被釋放。不同的請求之間沒有互相依賴和時序要求。同一個無狀态服務内不同的機器節點是完全等價的。

無狀态服務的特點使得它們很容易通過水準擴充來動态擴縮容。是以在保證相容的前提下,它們的更新流程相對通用并且簡單:

  1. 根據服務最小可用度決定分批數。
  2. 選取一批待更新的容器,停止服務。
  3. 批量更新容器、更新鏡像。
  4. 等待這一批容器全部恢複服務後,繼續更新下一批容器。
複雜系統如何在不停機更新同時保持穩定?你必須考慮以下幾個點...背景閑魚搜尋服務基本架構保持相容無狀态服務更新有狀态服務更新服務發現風險防控總結

一般來說我們可以通過把狀态存儲在消息隊列、緩存、資料庫或者其它外部中間件中來達成服務的無狀态。把服務設計成無狀态的好處顯而易見:更新時不需要配置設定額外的機器資源,更新速度快,變更代價小,因而可以支援頻繁的疊代更新。但是,這種設計也給狀态通路和更新帶來了額外的開銷,在某些性能敏感的場合可能是不适用的。

有狀态服務更新

我們繼續關注有狀态的部分。有狀态服務更新的麻煩之處在于,狀态的存儲、恢複、轉移往往由服務根據實際情況單獨設計(或者根本沒有設計),因而更新較為困難。我們可以簡單列舉一些相對通用的有狀态服務更新可選方案。

  • 接入層網關提供熱更新的能力(例如 Nginx),把狀态的保持隔離在接入層内部。适合需要長時間保持狀态的場景。
  • 漸進更新,新請求逐漸切換到新服務上處理,舊服務處理完存量請求後銷毀。适合短時間保持狀态的場景(例如遊戲服務、實時音視訊通訊服務)。
  • 建立全新的服務副本,通過資料雙寫保持新舊服務狀态一緻,逐漸用新服務取代舊服務。

在閑魚搜尋的架構中,搜尋引擎本身提供的雖然是無狀态服務,但是引擎内部儲存了用于處理索引分區,增量進度的各種狀态。最終使用的更新方案如下:

  1. 使用新版本鏡像建立一個完全獨立的新引擎。
  2. 新舊引擎全量資料同步。
  3. 增量資料同時向新舊引擎發送。
  4. 新引擎上線,逐漸擴大承接流量的比例。
  5. 舊引擎不再承接流量後下線。
複雜系統如何在不停機更新同時保持穩定?你必須考慮以下幾個點...背景閑魚搜尋服務基本架構保持相容無狀态服務更新有狀态服務更新服務發現風險防控總結

和無狀态服務的更新相比,這種方式不僅額外使用了一倍的機器資源,而且每次更新都需要做一次複雜而繁瑣的服務配置。如果服務本身不是無狀态的,還需要自行編碼實作切流邏輯,保證同一個使用者的請求能夠落到同一個叢集上。整體更新成本較為昂貴,隻适合更新頻率非常低的服務。如果服務的更新頻率較高,則應該根據服務的實際情況設計實作更新成本更低的方案。

服務發現

在更新過程中,服務發現機制承擔着重要作用。它為我們提供了以下功能:

  • 保證分布式一緻性
  • 服務優雅上下線
  • 負載均衡
  • 流量調控與請求降級
  • 同機房優先排程
  • 跨機房容災排程
複雜系統如何在不停機更新同時保持穩定?你必須考慮以下幾個點...背景閑魚搜尋服務基本架構保持相容無狀态服務更新有狀态服務更新服務發現風險防控總結

服務發現是流量調控的總閥門。一個成熟穩定的服務發現機制不僅可以有效避免釋出導緻的請求成功率抖動,也為發生異常時快速復原止血提供了保證。

風險防控

對搜尋鍊路的每一個叢集按照依賴順序進行服務更新、挂載、切流無疑是高危操作,稍有不慎就可能引起線上故障。是以,我們按照阿裡巴巴安全生産三闆斧原則對更新流程進行了梳理:

  • 可監控

    重要鍊路的重要名額均提前保證監控覆寫。例如請求總量,請求成功率,請求響應時長等等。確定重大問題可以通過監控名額及時發現。

  • 可灰階

    任何變更都不允許未經灰階直接全量釋出到線上。對于無狀态服務,我們一般通過調整服務發現中的權重或者調整機器比例來完成灰階放量。對于部分不能随機灰階的情形,我們設計了按使用者分批放量的機制。

  • 可復原

    變更系統提供了通用的一鍵復原能力,但并非是最快的方式。在很多情況下,我們在執行變更前就做好了把待更新的機器或叢集在服務發現上重新挂載或移除的準備,從問題發現到恢複的時間基本是秒級的。

總結

綜上所述,複雜系統不停機更新的原則和流程可以概括如下:

  1. 服務間解耦與隔離,確定單次更新的範圍和影響可控。
  2. 根據相容性和依賴關系決定服務的更新順序。
  3. 根據服務是否無狀态決定更新方式。
  4. 提前準備好監控和復原方案,灰階更新。

閑魚搜尋服務更新的整個執行過程經曆了兩個月的時間。這其中我們既保證了使用者無感覺,線上服務穩定運作,也保證了與我們合作開發的算法團隊以及其他工程團隊的正常開發不受影響。

在實際執行的過程中,我們還遇到了很多細節上的問題。例如建立新服務時未能提前合理預估預算需求,導緻更新過程中不斷挪借預算,拆東牆補西牆。又比如異地多活部署帶來的延遲問題迫使服務保持單元化,給更新過程中的流量調控工作帶來了很多挑戰。這些暴露的問題也為我們繼續完善架構和方案提供了指引。

希望本次的分享能夠給大家帶來一些幫助和啟發。

繼續閱讀