彈力設計之冪等性設計

所謂冪等性設計,就是說,一次和多次請求某一個資源應該具有同樣的副作用。用數學的語言來表達就是:f(x) = f(f(x))。

比如,求絕對值的函數,abs(x) = abs(abs(x))。

爲什麼我們需要這樣的操作?說白了,就是在我們把系統解耦隔離後,服務間的調用可能會有三個狀態,一個是成功(Success),一個是失敗(Failed),一個是超時(Timeout)。前兩者都是明確的狀態,而超時則是完全不知道是什麼狀態。

比如,超時原因是網絡傳輸丟包的問題,可能是請求時就沒有請求到,也有可能是請求到了,返回結果時沒有正常返回等等情況。於是我們完全不知道下游系統是否收到了請求,而收到了請求是否處理了,成功 / 失敗的狀態在返回時是否遇到了網絡問題。總之,請求方完全不知道是怎麼回事。

舉幾個例子:
訂單創建接口,第一次調用超時了,然後調用方重試了一次。是否會多創建一筆訂單?
訂單創建時,我們需要去扣減庫存,這時接口發生了超時,調用方重試了一次。是否會多扣一次庫存?
當這筆訂單開始支付,在支付請求發出之後,在服務端發生了扣錢操作,接口響應超時了,調用方重試了一次。是否會多扣一次錢?

因爲系統超時,而調用戶方重試一下,會給我們的系統帶來不一致的副作用。

在這種情況下,一般有兩種處理方式。

1、一種是需要下游系統提供相應的查詢接口。上游系統在 timeout 後去查詢一下。如果查到了,就表明已經做了,成功了就不用做了,失敗了就走失敗流程。
2、另一種是通過冪等性的方式。也就是說,把這個查詢操作交給下游系統,我上游系統只管重試,下游系統保證一次和多次的請求結果是一樣的。

對於第一種方式,需要對方提供一個查詢接口來做配合。而第二種方式則需要下游的系統提供支持冪等性的交易接口。

全局 ID

要做到冪等性的交易接口,需要有一個唯一的標識,來標誌交易是同一筆交易。而這個交易 ID 由誰來分配是一件比較頭疼的事。因爲這個標識要能做到全局唯一。

如果由一箇中心繫統來分配,那麼每一次交易都需要找那個中心繫統來。 這樣增加了程序的性能開銷。如果由上游系統來分配,則可能會出現 ID 分配重複的問題。因爲上游系統可能會是一個集羣,它們同時承擔相同的工作。

爲了解決分配衝突的問題,我們需要使用一個不會衝突的算法,比如使用 UUID 這樣衝突非常小的算法。但 UUID 的問題是,它的字符串佔用的空間比較大,索引的效率非常低,生成的 ID 太過於隨機,完全不是人讀的,而且沒有遞增,如果要按前後順序排序的話,基本不可能。

在全局唯一 ID 的算法中,這裏介紹一個 Twitter 的開源項目 Snowflake。它是一個分佈式 ID 的生成算法。其核心思想是,產生一個 long 型的 ID,其中:

41bits 作爲毫秒數。大概可以用 69.7 年。
10bits 作爲機器編號(5bits 是數據中心,5bits 的機器 ID),支持 1024 個實例。
12bits 作爲毫秒內的序列號。一毫秒可以生成 4096 個序號。

其他的像 Redis 或 MongoDB 的全局 ID 生成都和這個算法大同小異。也可以根據實際情況加上業務的編號。

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