餓了麼異地多活技術實現(三)GZS&DAL

餓了麼技術團隊花了1年多的時間,實現了業務的整體異地多活,能夠靈活的在多個異地機房之間調度用戶,實現了自由擴容和多機房容災的目標。本文介紹這個項目的中五大核心基礎組件中的DAL與GZS,關於項目整體介紹以及其它組件的實現細節可以參考本系列的其它文章。

 

GZS (Global Zone Service:全局狀態協調器)

背景

多活改造的一個核心是多活流量路由,來源主要包括三個方面:

  • 從ezone外部發起的請求。比如用戶下訂單,商戶添加菜品等等。這個部分的請求由API Router路由到正確的ezone。詳情請參考https://zhuanlan.zhihu.com/p/32587960
  • 非多活業務調用多活業務。當非多活業務調用多活業務時,需要通過SOA Proxy從ezone內部路由到正確ezone。
  • job發起的請求。典型的場景是補償job,通常是批量拉取符合條件的數據進行業務操作。這種情況需要業務方篩選出本ezone需要處理的數據,各個ezone的job服務只處理自己ezone負責的業務數據。

不僅基礎組件依賴多活路由,一些特定的業務也需要獲得多活路由,因此必須有一個專門的組件來統一管理多活路由,並且在多活切換時能協調各個模塊的工作。

 

多活路由設計

多活路由由三個部分組成,ezone,shardingid,shardingkey。通過這三層路由關係的調整實現了多活的靈活資源調度以及failover。

  • ezone,代表的是一個可獨立支持完整關鍵業務的邏輯機房,一般來說應該是在一個物理機房內。
  • shardingid,代表的是按地理位置劃分出來的邏輯業務分組。一個shardid的業務會被路由到同一個ezone處理。
  • shardingkey,代表的是可以被路由的業務相關id,包括商戶id,訂單id等等。爲了減少多活改造對業務的侵入,我們採用了使用自己業務相關id進行多活路由的策略,由GZS來維護這層路由關係。

下圖是一個簡單的路由例子

一般來說ezone與shardid的關係是比較固定的,而shardingkey會是一個一直變化的狀態。shardingkey的路由規則主要由3種類型

  • 計算型:比如經緯度。GZS根據地理位圍欄計算出所屬的shardid,來找到目標ezone。這種類型的shardingkey會有一定的cpu消耗。
  • 映射型:比如城市id。每個城市id基本上也是與地理位置綁定的,GZS通過記錄一張城市id與shardid的映射表來路由。這種類型的shardingkey會有一定的內存消耗。映射型中有些shardkey總量是不斷增長的如商戶id。GZS必須實時維護路由關係,保證秒級別的更新。
  • 嵌入型:這種類型的shardingkey幾乎是最理想的,GZS可以直接根據預先定義的解析方式從shardingkey中得到shardid來做路由。

多活shard切換協議設計

多活切換的原則是:

  • 可用性優先:當發生故障切換機房時,優先保證系統可用,首先讓用戶可以下單喫飯,容忍有限時間段內的數據不一致,再事後修復。每個 ezone 都會有全量的業務數據,當一個 ezone 失效後,其他的 ezone 可以接管用戶。用戶在一個ezone的下單數據,會實時的複製到其他ezone。
  • 保證數據正確:在確保可用的情況下,需要對數據做保護以避免錯誤,在切換和故障時,如果發現某些訂單的狀態在兩個機房不一致,會鎖定該筆訂單,阻止對它進行更改,保證數據的正確。

 

shard切換協議如下:

  1. GZS將shard1狀態從“ACTIVE”轉化爲“BLOCK”,並路由推送到訂閱用戶(DAL ApiRouter soaproxy e.g.)
    1. DAL阻止在所有機房相應的shard的所有操作 (直接返回操作失敗:1036)
    2. API Router阻止相應shard的全部操作 (相應shard全部不可用)(返回固定錯誤碼:530)
  2. GZS將shard1狀態從“BLOCK”轉化爲“RESHARD”,shard1對應ezone從“ezoneA”改變爲“ezoneB”,“reshard_at”域爲當前時間
    1. DAL阻止在目的機房相應的shard的老數據的更新操作 (update 操作增加一個時間戳的判斷條件 created_at > reshard_at)
    2. DAL在其它機房保持阻止
    3. 訂閱用戶根據新shard-ezone映射路由。
  3. GZS 開始倒計時。倒計時結束 或者DRC 通知同步時間到達reshard_at
  4. GZS 狀態從“RESHARD”轉化爲“ACTIVE”
    1. DAL解除阻止

下面是一個簡單的例子,將shard 1 的下單流量從ezone1切換到ezone2。

設計實現

GZS的設計是緊密聯繫業務特性的產物。

 

爲什麼SDK使用訂閱推送

由於多活路由查詢對於其它多活組件(DAL,APIRouter,soaproxy)來說是個高頻操作,遠程查詢顯然不能夠勝任。同時對於計算型的shardingkey也不適合在GZS Server做。因此需要把路由信息推送到SDK,來保證查詢的高效。

 

路由訂閱是全量更新還是增量更新

GZS定義了一套增量更新的協議,適用於所有的多活路由類型。對於映射型的shardingkey使用增量更新,而對於其它類型的shardingkey直接使用全量更新的策略。

 

如何考慮一致性與可用性

如前文介紹的,多活路由是三層結構,對於一致性其實有着不同的要求。對於Sharding Id這層路由有着強一致性的要求,我們一組多活切換步驟來保證一致性。整個過程我們是犧牲可用性的。而對於邏輯Sharding key,嵌入型以及計算型的一致型是和Sharding Id一致的是綁定的。我們唯一需要考慮的是映射型的Sharding Key,對於這種類型主要採用BASE的思想來設計,不強調實時高一致性,而是要求一定時間內達到最終一致性。以店鋪ID爲例子,當新的店鋪添時,GZS保證秒級同步映射關係,中間不同步的狀態通過業務使用地理位置路由等方式繞開。

 

GZS 拓撲圖

 

DAL(Data Access Layer:數據訪問)

背景

DAL是proxy型的數據庫中間件,支持mysql協議,在多活項目啓動前已經承載了餓了麼全部生產mysql流量。在多活項目中,DAL責無旁貸扮演起保護數據正確性最後一道防線的角色。

 

多活DAL兜底

在前文我們提到了多活流量來源,來自ezone外的可以統一通過API Router收口。而來自ezone內部的流量,我們很難確認多活改造是否完成。因此需要在DAL層做兜底,防止多活數據錯亂。

DAL多活兜底主要體現在兩個部分

Global ezone

多活業務場景中,存在全局配置性的數據,呈現大量讀,少量寫的特性。針對這種業務,我們提供了global ezone這種解決方案。

這是一種跨機房的讀寫分離機制。查詢走本機房的數據庫salve,寫全部走一個Master,對業務方來說DB的配置透明的。

DAL可以方便的把業務在各個ezone的服務指向同一個master。當發生failover時,通過DB的主備切換,就可以把mysql的master從一個ezone遷移到另一個ezone。

 

未來的展望

多活切換目前是人工操作的,每次多活切換的決策總要耗費不少的時間。未來我們要把多活切換的過程做到智能化,自動化。

 

文獻引用

https://yq.aliyun.com/articles/57715

https://coolshell.cn/articles/10910.html

https://en.wikipedia.org/wiki/CAP_theorem

https://zhuanlan.zhihu.com/p/32009822

https://zhuanlan.zhihu.com/p/32587960

 

作者介紹

林靜 ,2011畢業於浙江大學,2015年加入餓了麼,現任餓了麼框架工具部架構師。

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