最系統的冪等性方案:一鎖二判三更新

文章很長,且持續更新,建議收藏起來,慢慢讀!瘋狂創客圈總目錄 博客園版 爲您奉上珍貴的學習資源 :

免費贈送 :《尼恩Java面試寶典》 持續更新+ 史上最全 + 面試必備 2000頁+ 面試必備 + 大廠必備 +漲薪必備
免費贈送 :《尼恩技術聖經+高併發系列PDF》 ,幫你 實現技術自由,完成職業升級, 薪酬猛漲!加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷1)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷2)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷3)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領

免費贈送 資源寶庫: Java 必備 百度網盤資源大合集 價值>10000元 加尼恩領取


最系統的冪等性方案:一鎖二判三更新

尼恩說在前面

在40歲老架構師 尼恩的讀者交流羣(50+)中,最近有小夥伴拿到了一線互聯網企業如阿里、滴滴、極兔、有贊、希音、百度、網易、美團的面試資格,遇到很多很重要的面試題:

問題1:你們項目,怎麼做冪等設計的?

問題2:接口的冪等性,怎麼設計?

問題3:業務訂單的冪等性,怎麼設計?

問題4:付款請求的冪等性,怎麼設計?

問題4:前端重複提交選中的數據,後臺只產生一次有效操作,怎麼設計?

最近又有小夥伴在面試阿里、網易,都遇到了相關的面試題。

很多小夥伴回答了一些邊邊角角,但是回答不全面不體系,面試官不滿意,面試掛了。

藉着此文,尼恩給大家做一下系統化、體系化的梳理,使得大家內力猛增,展示一下雄厚的 “技術肌肉、技術實力”,讓面試官愛到 “不能自已、口水直流”,然後實現”offer直提,offer自由”。

當然,這道面試題,以及參考答案,也會收入咱們的 《尼恩Java面試寶典PDF》V161版本,供後面的小夥伴參考,提升大家的 3高 架構、設計、開發水平。

《尼恩 架構筆記》《尼恩高併發三部曲》《尼恩Java面試寶典》的PDF,請到公號【技術自由圈】獲取

本文目錄

什麼是冪等性?

所謂冪等性,就是一次操作和多次操作同一個資源,所產生的影響均與一次操作的影響相同。

"冪等(idempotent、idempotence)是一個數學與計算機學概念,常見於抽象代數中。

冪等函數,或冪等方法,是指可以使用相同參數重複執行,並能獲得相同結果的函數。

冪等性,用數學語言表達就是:

f(x)=f(f(x))

維基百科的冪等性定義如下:

冪等(idempotent、idempotence)是一個數學與計算機學概念,常見於抽象代數中。

冪等函數,或冪等方法,是指可以使用相同參數重複執行,並能獲得相同結果的函數。

這些函數不會影響系統狀態,也不用擔心重複執行會對系統造成改變。

例如,“setTrue()”函數就是一個冪等函數,無論多次執行,其結果都是一樣的,更復雜的操作冪等保證是利用唯一交易號(流水號)實現.

在軟件或者系統中,重複使用冪等函數或冪等方法不會影響系統狀態,也不用擔心重複執行會對系統造成改變。

通俗點說:

一個接口如果冪等,不管被調多少次,只要參數不變,結果也不變。

冪等性是對於寫操作來說的,一個寫操作,一般都需要保證:

  • 冪等性
  • 可用性
  • ACID事務屬性。

當然,這裏僅僅聚焦 冪等。

爲什麼需要冪等性?

如果客戶端重複調用,服務端會遇到如下的很多問題:

  1. 創建訂單時,重複調用是否產生兩筆訂單?
  2. 扣減庫存時,重複調是否會多扣一次?

這就是出現了冪等性問題。

按照冪等性要求,需要保證一次請求和多次請求同一個資源產生相同的副作用。

所以:創建訂單時,重複調用是否產生兩筆訂單? 當然不能。

所以:扣減庫存時,重複調是否會多扣一次? 當然不能。

這些,都是需要冪等性機制去保障。如果不支持冪等操作,那將會出現以下情況:

  • 電商超賣現象
  • 重複轉賬、扣款或付款
  • 重複增加金幣、積分或優惠券

等等,非常慘的。

什麼樣的原因導致冪等性問題?

原因之一:底層網絡阻塞和延遲的問題

在系統高併發的環境下,很有可能因爲網絡阻塞等等問題,導致客戶端不能及時的收到服務端響應,甚至是調用超時。 這時候用戶會重複點擊,重複請求。

在消息隊列組件中,客戶端也有重試機制,如果投遞失敗/投遞超時,則會重新投遞。 對於服務端來說,可能會收到重複投遞的一份消息。

在RPC組件中,客戶端也有重試機制,如果投遞失敗/投遞超時,則會重試調用。 對於服務端來說,可能會重複收到通用的調用。

原因之二:用戶層面的重複操作

比如下單的按鍵在點按之後,在沒有收到服務器請求之前,用戶還可以被按。

或者,用戶的App閃退/人工強退,之後重新打開重新下單

需要冪等性的 兩大場景

可能會發生重複請求或重試操作的場景,在分佈式、微服務架構中是隨處可見的。

  • 網絡波動:因網絡波動,可能會引起重複請求

  • 分佈式消息消費:任務發佈後,使用分佈式消息服務來進行消費

  • 用戶重複操作:用戶在使用產品時,可能無意地觸發多筆交易,甚至沒有響應而有意觸發多筆交易

  • 未關閉的重試機制:因開發人員、測試人員或運維人員沒有檢查出來,而開啓的重試機制(如Nginx重試、RPC通信重試或業務層重試等)

大致可以分爲兩大類:

  • 第一類:單數據CRUD操作的冪等性保證方案
  • 第二類:多數據併發操作的冪等性保證方案

第一類:單數據CRUD操作的冪等性保證方案

首先,來看看單數據CRUD操作的冪等性保證方案

對於單數據CRUD操作,很多具備天然冪等性

  • 新增類動作:不具備冪等性
  • 查詢類動作:重複查詢不會產生或變更新的數據,查詢具有天然冪等性
  • 更新類動作:
    • 基於主鍵的計算式Update,不具備冪等性,即UPDATE goods SET number=number-1 WHERE id=1
    • 基於主鍵的非計算式Update:具備冪等性,即UPDATE goods SET number=newNumber WHERE id=1
    • 基於條件查詢的Update,不一定具備冪等性(需要根據實際情況進行分析判斷)
  • 刪除類動作:
    • 基於主鍵的Delete具備冪等性
    • 一般業務層面都是邏輯刪除(即update操作),而基於主鍵的邏輯刪除操作也是具有冪等性的

大家看到,對於單數據CRUD操作, 只有在下面的三個場景,保證冪等即可:

  • 主鍵的計算式Update
  • 基於條件查詢的Update
  • 新增類動作

第二類:多數據併發操作的冪等性保證方案

大部分,都是這種場景。

現在的應用,大部分都是微服務的。並且一個操作會涉及到多個數據的併發操作,會通過RPC調用到多個微服務。

分爲兩種情況:

  • 多數據同步操作,一般是服務端提供一個統一的同步操作api,客戶端調用該api完成,直接獲得操作結果。

  • 多數據異步操作,由於同步操作性能低,在高併發場景都會同步變異步,於是乎,服務端還要額外提供一個查詢操作結果的api,去查詢結果。第一次超時之後,調用方調用查詢接口,如果查到了就走成功的流程,失敗了就走失敗的流程。

多數據併發操作的經典場景,參考如下:

1. 高併發搶紅包

在搶一份紅包的時候,點擊了搶,開始異步搶紅包。

搶到就有,沒搶到就沒有。

搶完之後,無論我們重複點擊多少次,紅包都會提示你已經搶過該紅包了。

2. 高併發下單

高併發下單的一個很基本的問題,就是要避免重複訂單。

如果用戶操作一次,由於超時重試等原因,一看下了兩個單,甚至10個重複單。

用戶不暈倒在廁所纔怪。

3. 高併發支付

在支付場景,支付平臺會生成唯一的支付連接,不會再次生成另外的支付連接。

如何保證冪等呢 ?

冪等性的的確保方案,非常多,大致如下圖所示

一些基礎性的冪等性解決方案

  • 全局唯一ID

如果使用全局唯一ID,就是根據業務的操作和內容生成一個全局ID,在執行操作前先根據這個全局唯一ID是否存在,來判斷這個操作是否已經執行。

如果不存在則把全局ID,存儲到存儲系統中,比如數據庫、Redis等。如果存在則表示該方法已經執行。

使用全局唯一ID是一個通用方案,可以支持插入、更新、刪除業務操作。

一般情況下,對分佈式的全局唯一id,可以參考以下幾種方式:

  • UUID

  • Snowflake

  • 數據庫自增ID

  • 業務本身的唯一約束

  • 業務字段+時間戳拼接

  • 唯一索引(去重表)

這種方法適用於在業務中有唯一標識的插入場景中,比如在以上的支付場景中,如果一個訂單隻會支付一次,所以訂單ID可以作爲唯一標識

這時,我們就可以建一張去重表,並且把唯一標識作爲唯一索引,在我們實現時,把創建支付單據寫入去重表,放在一個事務中,如果重複創建,數據庫會拋出唯一約束異常,操作就會回滾。

  • 插入或更新(upsert)

這種方法插入並且有唯一索引的情況,比如我們要關聯商品品類,其中商品的ID和品類的ID可以構成唯一索引,並且在數據表中也增加了唯一索引。這時就可以使用InsertOrUpdate操作。

  • 多版本控制

這種方法適合在更新的場景中,比如我們要更新商品的名字,這時我們就可以在更新的接口中增加一個版本號,來做冪等:

boolean updateGoodsName(int id,String newName,int version);

在實現時可以如下:

update goods set name=#{newName},version=#{version} where id=#{id} and version<${version}
  • 狀態機控制

這種方法適合在有狀態機流轉的情況下,比如就會訂單的創建和付款,訂單的付款肯定是在之前,這時我們可以通過在設計狀態字段時,使用int類型,並且通過值類型的大小來做冪等,比如訂單的創建爲0,付款成功爲100,付款失敗爲99。

在做狀態機更新時,我們就可以這樣控制:

update goods_order set status=#{status} where id=#{id} and status<#{status}

以上就是保證接口冪等性的一些方法。

綜合性的解決方案:一鎖二判三更新

前面的方案,都是一些基礎性的方案。

在實際的業務中,一般會結合起來使用。

在雙11和雙12的活動中,對於冪等性問題,支付寶團隊摸索出來了一個綜合性的解決方案:一鎖二判三更新。這個方案,可以作爲一個比較通用的綜合性的冪等解決方案。

何爲“一鎖二判三更新”?

簡單來說就是當任何一個併發請求過來的時候

    1. 先鎖定單據
    1. 然後判斷單據狀態,是否之前已經更新過對應狀態了
  • 3.1 如果之前並沒有更新,則本次請求可以更新,並完成相關業務邏輯。

  • 3.2 如果之前已經有更新,則本次不能更新,也不能完成業務邏輯。

一鎖、二判、三更性的核心步驟

第一步:先加鎖

高併發場景,建議是redis分佈式鎖,而不是低性能的DB鎖,也不是CP型的 Zookeeper鎖。

如果普通的redis分佈式鎖性能太低,該如何?

還可以考慮引入 鎖的分段機制, 比如內部分成100端,總體上,就大概能線性提升 100倍。

第二步:進行冪等性判斷

冪等性判斷,就是 進行 數據檢查。

可以基於狀態機、流水錶、唯一性索引等等前面介紹的 基礎方案,進行重複操作的判斷。

第三步:數據更新

如果通過了第二步的冪等性判斷, 說明之前沒有執行過更新操作。

那麼就進入第三步,進行數據的更新,將數據進行持久化。

操作完成之後, 記得釋放鎖, 結束整個流程。

關於redis分佈式鎖、 Zookeeper鎖、分段鎖的內容,請參見5000頁《尼恩Java面試寶典》的相應的專題。

說在最後:老馬識途,有問題找老架構求助

以上的內容,如果大家能對答如流,如數家珍,基本上 面試官會被你 震驚到、吸引到。

最終,讓面試官愛到 “不能自已、口水直流”。 offer, 也就來了。

其實, “offer自由” 不難實現, 前段時間一個跟尼恩捲了2年的武漢小夥,9年經驗, 在年底大裁員的極度嚴寒/痛苦被裁的背景下, offer拿到手軟, 實現真正的 “offer自由” 。

在面試之前,建議大家系統化的刷一波 5000頁《尼恩Java面試寶典PDF》,裏邊有大量的大廠真題、面試難題、架構難題。很多小夥伴刷完後, 吊打面試官, 大廠橫着走

在刷題過程中,如果有啥問題,大家可以來 找 40歲老架構師尼恩交流。

尼恩一直深耕技術,不是在研究技術,就是在研究技術的路上,加尼恩微信之後不一定立馬通過, 但是,最多1-2小時就會審覈的。

深研技術,遠離浮躁。作爲資深技術人,尼恩實在太忙了.....

特別要說的是:很多小夥伴簡歷投出去後如泥牛入海、不冒一泡、沒有面試機會。

遇到這種難題,可以找尼恩來改簡歷、做幫扶。

另外,遇到架構升級、晉升受阻、職業打擊等職業難題,也可以找尼恩取經, 可以省去太多的折騰,省去太多的彎路。

尼恩已經指導了大量的小夥伴上岸,前段時間指導一個40歲+被裁小夥伴上岸,拿到了一個年薪100W的offer。

技術自由的實現路徑:

實現你的 架構自由:

喫透8圖1模板,人人可以做架構

10Wqps評論中臺,如何架構?B站是這麼做的!!!

阿里二面:千萬級、億級數據,如何性能優化? 教科書級 答案來了

峯值21WQps、億級DAU,小遊戲《羊了個羊》是怎麼架構的?

100億級訂單怎麼調度,來一個大廠的極品方案

2個大廠 100億級 超大流量 紅包 架構方案

… 更多架構文章,正在添加中

實現你的 響應式 自由:

響應式聖經:10W字,實現Spring響應式編程自由

這是老版本 《Flux、Mono、Reactor 實戰(史上最全)

實現你的 spring cloud 自由:

Spring cloud Alibaba 學習聖經》 PDF

分庫分表 Sharding-JDBC 底層原理、核心實戰(史上最全)

一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之間混亂關係(史上最全)

實現你的 linux 自由:

Linux命令大全:2W多字,一次實現Linux自由

實現你的 網絡 自由:

TCP協議詳解 (史上最全)

網絡三張表:ARP表, MAC表, 路由表,實現你的網絡自由!!

實現你的 分佈式鎖 自由:

Redis分佈式鎖(圖解 - 秒懂 - 史上最全)

Zookeeper 分佈式鎖 - 圖解 - 秒懂

實現你的 王者組件 自由:

隊列之王: Disruptor 原理、架構、源碼 一文穿透

緩存之王:Caffeine 源碼、架構、原理(史上最全,10W字 超級長文)

緩存之王:Caffeine 的使用(史上最全)

Java Agent 探針、字節碼增強 ByteBuddy(史上最全)

實現你的 面試題 自由:

4800頁《尼恩Java面試寶典 》 40個專題

免費獲取11個技術聖經PDF:

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