參考:
1、冪等性的定義
Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.
一次和多次請求某一資源應該具有同樣的影響。如果第一次請求對資源產生了副作用,以后的多次請求都不會對資源有副作用。
2、為何需要冪等性
以取錢為例, withdraw(account_id, amount)
是從account_id
的賬號中取走amount
數額的錢。但是當網絡故障或者出bug,沒有收到操作成功的OK信息。頁面請求多次提交,那么賬號就會多次扣錢。
針對這個問題解決方案有兩種:
- 分布式事務。引入分布式事務的中間件,來保證操作的事務性,保證ACID性,但是隨着中間件的引入,整體架構會更加復雜,不夠輕量,不利於異構系統的集成。
- 冪等性設計。通過一些冪等性設計方式,解決此問題。更加輕量級。
3、解決冪等性的方式
(1)悲觀鎖
獨占資源,阻塞其他需要鎖的線程。
(2)樂觀鎖
每次不加鎖,假設沒有沖突去完成某項操作,如果因為沖突失敗就重試,直到成功為止。使用的機制就是CAS(Compare and Swap),CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。但是存在ABA問題,可以通過version版本號自增控制。例如: UPDATE tab1 SET col1=1,version=version+1 WHERE version=#version#
當前線程如果得到的版本號不是之前拿到的,就會重試。
ABA問題:
主要是針對當CAS保存的A是一個鏈表的頭指針的情況,CAS只會檢查頭指針是否變化,不會檢查這個鏈表是否有變化,從而出現問題。解決方式就是每次操作version版本號自增。
(3)去重表/唯一索引
將操作的流水單號orderNo做為去重表的唯一索引,每次請求都根據流水單號向去重表中插入一條數據。因為表中唯一索引而插入失敗,則返回操作失敗,直到第一次的請求完成(成功或失敗)。可以看出防重表作用是加鎖的功能。
(4)token令牌
int create_ticket()
bool idempotent_withdraw(ticket_id, account_id, amount)
create_ticket的語義是獲取一個服務器端生成的唯一的處理號ticket_id,它將用於標識后續的操作。idempotent_withdraw和withdraw的區別在於關聯了一個ticket_id,一個ticket_id表示的操作至多只會被處理一次,每次調用都將返回第一次調用時的處理結果。這樣,idempotent_withdraw就符合冪等性了,客戶端就可以放心地多次調用。
基於冪等性的解決方案中一個完整的取錢流程被分解成了兩個步驟:1.調用create_ticket()獲取ticket_id;2.調用idempotent_withdraw(ticket_id, account_id, amount)。雖然create_ticket不是冪等的,但在這種設計下,它對系統狀態的影響可以忽略,即使出現未收到token令牌也沒有關系,可以重新發請求生成令牌。加上idempotent_withdraw是冪等的,所以任何一步由於網絡等原因失敗或超時,客戶端都可以重試,直到獲得結果。