現在稍具規模的**和大型應用都不再是單機模式,而是分布式應用,基於多機的集群構建的應用,這樣服務能力就可以基本實現橫向擴容(scale out),不會像單機模式下的縱向擴容(scale up)會受到單機服務能力上限的限制。另外,隨著「微服務」概念的火爆,很多應用在構建之初就已經走在了分布式的路線上了,所以就目前行業的發展來看,基於分布式的應用會越來越普遍,甚至變成常態。加上docker這些容器技術的出現,應用分布式化的工具也越來越成熟。
眾所周知,構建分布式應用所面臨的複雜度遠遠超出集中式的單一應用,導致複雜性的因素有很多,在此只提其中一點:網路的不可靠性。在單一程序內部,對乙個函式的呼叫,結果只有兩種——成功和失敗,失敗的情況下,呼叫者可以決定做一些事情彌補。但是在跨程序的呼叫中,對乙個遠端(也可以在同乙個節點上)程序上執行的函式呼叫除了會得到成功和失敗,還會有第三種的情況——超時,這個現象被稱為分布式的三態。這也是困擾分布式應用構建的最核心因素之一,很多分布式應用的複雜度之所以上公升這麼多也是因為三態之中的超時引起的。
簡單看看超時給我們帶來的困擾,程序a呼叫程序b上的函式f,對於成功和失敗的結果,相信和單機下一樣,程序a都可以進行很好地的處理,因為結果是很明確的。如果程序a呼叫f之後,在允許的等待最大時間內沒有返回結果,就是呼叫超時了,此時程序a能做什麼?其實程序a什麼都做不了,因為超時是乙個不明確的結果——成功和失敗都有可能。詳細解釋下可能的情況:
成功的情況:程序a把資料通過網路傳輸到程序b上,f執行成功,通 網路返回執行結果給程序a,可是網路不太好,傳輸失敗了,程序a並 未在指定時間內收到結果,認為超時了。 失敗的情況:情況和成功的情況差不多,只是f執行失敗了,但是結 果依然傳輸失敗,程序a也認為執行超時了。 未執行的情況:程序a的資料傳送到程序b所在的節點過程中網路失敗 了,或者傳送到了程序b所在的機器上,但是程序b沒有消費掉在tcp 網路層的資料等等 由此可見,程序a對於超時確實無能為力,有太多的可能存在的情況了。但是分布式協作過程中又必須解決這個問題,不然分布式應用是沒意義的,這種情況下,一般會採用讓程序a嘗試重試——即重**起之前的呼叫。但是這樣也可能會帶來問題,因為超時的那次呼叫可能已經成功了,再次以同樣的引數呼叫f會不會帶來額外的問題?這就引出本文的主角——冪等性。
冪等性本來是乙個數學概念,在計算機方面用來表示對同乙個過程應用相同的引數多次和應用一次產生的效果是一樣,這樣的過程即被稱為滿足冪等性。
有了這個概念之後,假如之前的f是滿足冪等性的,那麼是不是意味著程序a在呼叫f超時之後,可以繼續重複呼叫f多次?這樣最起碼程序a可以在超時情況下做一些促進事情正向發展的努力。所以這種方式是分布式節點間常用的方式,那麼如何保證冪等呢?
在考慮實現冪等之前,先看看有哪些操作是天然冪等的,以sql為例。update tab1 set col1 = 1 where id = 2這樣的更新語句,無論執行多少次結果都是不受影響的,所以是冪等的。update tab1 set col1 = col1 + 1 where id = 2這樣的更新語句會隨著每次更新不斷變化,所以不是冪等的。所以在考慮之前,先識別出冪等和非冪等操作。
業務系統實現冪等的通用方式:一般是排重表校驗,在業務操作所在的庫建一張小表,名稱暫時搞成dup_forbidden,核心欄位就乙個biz_id,並且在這個欄位上建立乙個unique index,其他字段可以根據業務需求來擴充。那麼原來的業務f實現冪等的偽**如下:
begin
transaction
;count
=insert ignore dup_forbidden
(...
biz_id
...)
value
(...
biz_id
...)if(
count
>0)
commit
;
可以認為這是一套業務系統實現冪等的模板做法,通過insert ignore返回值來判斷是否已經執行過了,但是針對不同的情況可能還有變化。使用事務的目的是為了保證f和dup_forbidden的操作同時成功和失敗。本質上來看,dup_forbidden表就是通過unique index來遮蔽對f的多次呼叫,事實上很多業務已經存在dup_forbidden表的功能。
考慮如下場景:在乙個面向交易的分布式應用中,支付子系統完成了支付功能,支付子系統通知訂單子系統,通知的方式無非是呼叫訂單子系統的乙個函式f而已,只是呼叫的方式分為同步和非同步。無論是同步還是非同步,f都可能存在超時,所以為了支援重試,f必須是冪等的。f會首先根據傳入的訂單號來查詢訂單,檢查訂單狀態。如果是已經支付,就會直接返回成功。如果是待支付狀態,那麼會嘗試鎖定(悲觀鎖和樂觀鎖)訂單,修改狀態,指定其他操作,其中鎖定只是為了防止併發操作。偽**實現如下:
begin
transaction
;count
=update deal_tab
setstatus
=paid
where
id =
xx_id
andstatus
=unpaidif(
count
>0)
commit
;
從這個例子可以看出deal_tab訂單表本身已經可以作為dup_forbidden表的作用了,所以insert防重操作變成update來實現,當然這個是樂觀鎖的版本。悲觀鎖的版本如下:
begin
transaction
;deal
=select
*from
deal_tab
where
id =
xx_id
forupdateif(
deal
.status
==paid
)elseif(
deal
.status
=unpaid
)commit
;
當然基於悲觀鎖的做法對於高併發的系統是不建議的,畢竟長時間鎖定記錄會降低系統的tps。
當然,所有這些方案都是基於業務存在唯一的業務編號來設計實現的,可能會存在完全沒有業務編號的嗎?答案是it depends。即使沒有完全唯一的編號,我們也可以人為生成編號,比如呼叫方負責生成呼叫編號,同乙個呼叫編號發起的多次呼叫都被視為一次呼叫,既可以作為唯一鍵來排重。事實上,這種情況確實比較少!
業務系統實現冪等性的方式基本確定。系統關鍵介面的冪等性為以後系統的長期發展,特別是往分布式方向發展打下了很好的根基,可以大大簡化分布式應用的構建複雜度。
from:
系統冪等以及常用實現方式
現在稍具規模的 和大型應用都不再是單機模式,而是分布式應用,基於多機的集群構建的應用,這樣服務能力就可以基本實現橫向擴容 scale out 不會像單機模式下的縱向擴容 scale up 會受到單機服務能力上限的限制。另外,隨著 微服務 概念的火爆,很多應用在構建之初就已經走在了分布式的路線上了,所...
系統冪等以及常用實現方式
現在稍具規模的 和大型應用都不再是單機模式,而是分布式應用,基於多機的集群構建的應用,這樣服務能力就可以基本實現橫向擴容 scale out 不會像單機模式下的縱向擴容 scale up 會受到單機服務能力上限的限制。另外,隨著 微服務 概念的火爆,很多應用在構建之初就已經走在了分布式的路線上了,所...
冪等技術及實現方式
一 什麼是冪等 冪等 idempotent 是乙個數學與計算機的概念,常見於抽象代數。在程式設計中乙個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同,也不同擔心重複執行會對系統造成改變,例如,settrue 函式就是乙個冪等函式,無論執行多少次,其結果都是一樣的。二 冪等的實現方案...