文章很長,且持續更新,建議收藏起來,慢慢讀!瘋狂創客圈總目錄 博客園版 爲您奉上珍貴的學習資源 :
免費贈送 :《尼恩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事務屬性。
當然,這裏僅僅聚焦 冪等。
爲什麼需要冪等性?
如果客戶端重複調用,服務端會遇到如下的很多問題:
- 創建訂單時,重複調用是否產生兩筆訂單?
- 扣減庫存時,重複調是否會多扣一次?
這就是出現了冪等性問題。
按照冪等性要求,需要保證一次請求和多次請求同一個資源產生相同的副作用。
所以:創建訂單時,重複調用是否產生兩筆訂單? 當然不能。
所以:扣減庫存時,重複調是否會多扣一次? 當然不能。
這些,都是需要冪等性機制去保障。如果不支持冪等操作,那將會出現以下情況:
- 電商超賣現象
- 重複轉賬、扣款或付款
- 重複增加金幣、積分或優惠券
等等,非常慘的。
什麼樣的原因導致冪等性問題?
原因之一:底層網絡阻塞和延遲的問題
在系統高併發的環境下,很有可能因爲網絡阻塞等等問題,導致客戶端不能及時的收到服務端響應,甚至是調用超時。 這時候用戶會重複點擊,重複請求。
在消息隊列組件中,客戶端也有重試機制,如果投遞失敗/投遞超時,則會重新投遞。 對於服務端來說,可能會收到重複投遞的一份消息。
在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,不一定具備冪等性(需要根據實際情況進行分析判斷)
- 基於主鍵的計算式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的活動中,對於冪等性問題,支付寶團隊摸索出來了一個綜合性的解決方案:一鎖二判三更新。這個方案,可以作爲一個比較通用的綜合性的冪等解決方案。
何爲“一鎖二判三更新”?
簡單來說就是當任何一個併發請求過來的時候
-
- 先鎖定單據
-
- 然後判斷單據狀態,是否之前已經更新過對應狀態了
-
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。
技術自由的實現路徑:
實現你的 架構自由:
《阿里二面:千萬級、億級數據,如何性能優化? 教科書級 答案來了》
《峯值21WQps、億級DAU,小遊戲《羊了個羊》是怎麼架構的?》
… 更多架構文章,正在添加中
實現你的 響應式 自由:
這是老版本 《Flux、Mono、Reactor 實戰(史上最全)》
實現你的 spring cloud 自由:
《Spring cloud Alibaba 學習聖經》 PDF
《分庫分表 Sharding-JDBC 底層原理、核心實戰(史上最全)》
《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之間混亂關係(史上最全)》
實現你的 linux 自由:
實現你的 網絡 自由:
《網絡三張表:ARP表, MAC表, 路由表,實現你的網絡自由!!》
實現你的 分佈式鎖 自由:
實現你的 王者組件 自由:
《隊列之王: Disruptor 原理、架構、源碼 一文穿透》
《緩存之王:Caffeine 源碼、架構、原理(史上最全,10W字 超級長文)》
《Java Agent 探針、字節碼增強 ByteBuddy(史上最全)》