1、什麼是冪等?
一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。這裏的意思就是用戶對於同一種操作發起的一次或者多次請求,其結果狀態是一致的,不會因爲多次請求而產生了其他影響。
2、爲什麼需要冪等
引用一個經典的例子,那就是支付。用戶購買商品支付,支付扣款成功,但是返回結果的時候網絡異常,此時錢已經扣了。用戶再次點擊按鈕,假設這裏的請求沒有做冪等處理,此時會進行第二次扣款,返回結果成功,用戶查詢餘額返發現多扣錢了,流水記錄也變成了兩條。而實際期望的是,一筆訂單隻進行一次扣款。
3、常見的冪等控制實現方式有哪些?
>>>>使用數據庫操作做冪等:
- 查詢操作(select是天然的冪等操作,操作前置select狀態,查詢一次和查詢多次,在數據不變的情況下,查詢結果是一樣的。這種處理方式只適合沒有併發的情況,操作前,先查詢是否被處理過,防止過多的寫操作及異常。併發容易出現ABA問題,需要結合版本號。也可以使用select+insert方案,但僅適合在一些小型或者併發不高的項目。)
- 刪除操作(刪除操作也是冪等的,刪除一次和多次刪除都是把數據刪除。注意可能返回結果不一樣,刪除的數據不存在,返回0,刪除的數據多條,返回結果多個)
- 唯一索引unique index(通過指定數據庫創建唯一索引或者組合唯一索引,都可以避免髒數據,保證冪等,但是注意處理異常。處理前,先前置插入數據到db,捕獲Duplicate Exception判斷是否重複處理。插入:比如在以上的支付場景中,如果一個訂單隻會支付一次,所以訂單ID可以作爲唯一標識。這時,我們就可以建一張去重表,並且把唯一標識作爲唯一索引,在我們實現時,把創建支付單據和寫入去去重表,放在一個事務中,如果重複創建,數據庫會拋出唯一約束異常,操作就會回滾。更新或插入:
insert into goods_category (goods_id,category_id,create_time,update_time) values(#{goodsId},#{categoryId}, now(), now()) on DUPLICATE KEY UPDATE update_time= now()
update goods set name=#{newName}, version=version+1 where id=#{ id} and version<${ version}
)
- 悲觀鎖(獲取數據,用的不多,容易鎖表。
select * from table_xxx where id='xxx' for update;
>>>>使用三方做冪等:設計全局唯一ID
- 分佈式鎖(數據處理在分佈式環境下一向是很很麻煩的,因爲Synchronize和ReentrantLock在分佈式環境下是達不到併發鎖的效果的。所以分佈式鎖,一般都採用第三方,例如,redis,zookeeper,就好比,去第三方認證獲取一個permit,處理完了就去註銷這個permit,確保同一時刻,只有一個能執行成功
分佈式環境下,經常還需要考慮一個全局唯一索引的問題,常用解決方案如下:
- 雪花算法
- 令牌桶算法
- 漏桶算法
- 自定義唯一索引生成器)
- token機制(圍繞着三方面問題:
- 如何保證請求接口響應結果的一致性;
- 如何防止惡意請求或攻擊;
- 分佈式環境下如何保證請求接口的響應結果;
可以選擇token+redis方案來實現:
- 請求之前,先向服務器申請一個token,服務器緩存token到redis,加上超時時間;
- 請求的時候帶上token;
- 服務器從redis獲取token校驗,通過後處理、響應、刪除token;未通過校驗,則提示token無效;
注意:
- token在redis上設置的超時時間控制合理,防止惡意請求;
- 服務器校驗token時,採用redis.delete操作,直接使用get+delete規避併發帶來的問題;
- token一定程度上可以限流;
- 狀態機(如果狀態機已經處於下一個狀態,這時候來了一個上一個狀態的變更,理論上是不能夠變更的,這樣的話,保證了有限狀態機的冪等。在冪等的處理機制中,狀態機是個很有用的東東,已經確定的狀態不會回滾處理,比如訂單業務裏面,訂單是已支付狀態,再來這個訂單的支付請求,肯定是不行的嘛,懟回去。很多場景或者系統中都用到狀態機,所以設計合理的狀態機制,是很有用且很必要的。這種方法適合在有狀態機流轉的情況下,比如就會訂單的創建和付款,訂單的付款肯定是在之前,這時我們可以通過在設計狀態字段時,使用int類型,並且通過值類型的大小來做冪等,比如訂單的創建爲0,付款成功爲100。付款失敗爲99。在做狀態機更新時,我們就這可以這樣控制
update `order` set status=#{ status} where id=#{ id} and status<#{ status}
)
4、什麼時候該使用冪等?
一般需要冪等的場景是寫場景,並非所有場景下我們都需要冪等設計,只有一些數據敏感、需要保證強一致性的場景下才會需要。
當某個業務流程存在被重複處理的場景,且要求該業務流程無論被執行多少次,最終結果都要和只處理一次時的狀態保持一致(請求的次數不影響最終處理狀態),這時就需要冪等。
5、使用場景
- 前端相同請求表單被重複提交。例如:①提交按鈕被用戶多次點擊;②請求超時但實際處理成功,前端重試;
- 上層業務重複調用。例如:由於上層業務邏輯的不合理,重複調用底層服務處理。
- 請求超時重試。例如:rpc調用超時重試、代理層超時重試、中間件客戶端超時重試等。
- 消息重複消費。例如:mq不保證消息唯一,可能重複推送;消息處理ack超時重複推送等。
- 任務調度中心重複調度。例如:定時任務、延時任務回調時,單個任務被多次重複調用。
- 單個請求業務處理時,部分流程異常。例如:處理單個請求時,步驟1成功、步驟2異常,步驟1未回滾,第二次請求時,步驟1仍會重複處理。
- 網絡報文重發。一般極少出現,server端正常收到報文,但client端未收到ack,於是client端進行重發。