偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

業(yè)務(wù)冪等性設(shè)計(jì)的六種方案

開發(fā) 前端
分布式鎖實(shí)現(xiàn)冪等性的邏輯就是,請求過來時(shí),先去嘗試獲得分布式鎖,如果獲得成功,就執(zhí)行業(yè)務(wù)邏輯,反之獲取失敗的話,就舍棄請求直接返回成功。

現(xiàn)如今很多系統(tǒng)都會(huì)基于分布式或微服務(wù)思想完成對系統(tǒng)的架構(gòu)設(shè)計(jì)。那么在這一個(gè)系統(tǒng)中,就會(huì)存在若干個(gè)微服務(wù),而且服務(wù)間也會(huì)產(chǎn)生相互通信調(diào)用。

那么既然產(chǎn)生了服務(wù)調(diào)用,就必然會(huì)存在服務(wù)調(diào)用延遲或失敗的問題。當(dāng)出現(xiàn)這種問題,服務(wù)端會(huì)進(jìn)行重試等操作或客戶端有可能會(huì)進(jìn)行多次點(diǎn)擊提交。在存在重復(fù)請求的場景中(如支付交易),為確保系統(tǒng)最終處理結(jié)果的一致性并避免資損風(fēng)險(xiǎn),必須通過業(yè)務(wù)冪等性設(shè)計(jì)保障數(shù)據(jù)操作的唯一性。

什么叫冪等

冪等(Idempotence) 是計(jì)算機(jī)科學(xué)和分布式系統(tǒng)中的核心概念,指在特定上下文中,對同一操作進(jìn)行多次執(zhí)行所產(chǎn)生的影響,與僅執(zhí)行一次該操作的影響完全相同。無論該操作被調(diào)用一次還是多次,系統(tǒng)的最終狀態(tài)始終保持一致,資源狀態(tài)或業(yè)務(wù)結(jié)果不會(huì)因?yàn)橹貜?fù)調(diào)用而發(fā)生額外改變。

冪等用數(shù)學(xué)語言表達(dá)就是:f(f(x))=f(x)

圖片圖片

在分布式系統(tǒng)和網(wǎng)絡(luò)通信中,冪等性尤為重要,尤其是轉(zhuǎn)賬、支付等涉及金額交易的場景,如果出現(xiàn)冪等性的問題,造成的后果是非常嚴(yán)重的。

事故:轉(zhuǎn)賬無冪等、交易無冪等、發(fā)優(yōu)惠券無冪等,都會(huì)造成不小的事故。

冪等性設(shè)計(jì)主要從兩個(gè)維度進(jìn)行考慮:空間、時(shí)間。

  • 空間:定義了冪等的范圍,如生成訂單的話,不允許出現(xiàn)重復(fù)下單。
  • 時(shí)間:定義冪等的有效期。有些業(yè)務(wù)需要永久性保證冪等,如下單、支付等。而部分業(yè)務(wù)只要保證一段時(shí)間冪等即可。

業(yè)務(wù)問題拋出

在業(yè)務(wù)開發(fā)與分布式系統(tǒng)設(shè)計(jì)中,有非常多的場景需要考慮冪等性的問題,如:

  • 當(dāng)用戶購物進(jìn)行下單操作,用戶操作多次,但訂單系統(tǒng)對于本次操作只能產(chǎn)生一個(gè)訂單。
  • 當(dāng)用戶對訂單進(jìn)行付款,支付系統(tǒng)不管出現(xiàn)什么問題,應(yīng)該只對用戶扣一次款。
  • 當(dāng)支付成功對庫存扣減時(shí),庫存系統(tǒng)對訂單中商品的庫存數(shù)量也只能扣減一次。
  • 當(dāng)對商品進(jìn)行發(fā)貨時(shí),也需保證物流系統(tǒng)有且只能發(fā)一次貨。

但是一旦考慮冪等后,服務(wù)邏輯務(wù)必會(huì)變的更加復(fù)雜。因此是否要考慮冪等,需要根據(jù)具體業(yè)務(wù)場景具體分析。

此處以下單減庫存為例,當(dāng)用戶生成訂單成功后,會(huì)對訂單中商品進(jìn)行扣減庫存。 訂單服務(wù)會(huì)調(diào)用庫存服務(wù)進(jìn)行庫存扣減。庫存服務(wù)會(huì)完成具體扣減實(shí)現(xiàn):

圖片圖片

如果出現(xiàn)調(diào)用超時(shí),如網(wǎng)絡(luò)抖動(dòng),雖然庫存服務(wù)執(zhí)行成功了,但結(jié)果并沒有在指定時(shí)間內(nèi)返回,則訂單服務(wù)會(huì)進(jìn)行重試。那就會(huì)出現(xiàn)問題,此時(shí)出現(xiàn)庫存扣減兩次的問題。 對于這種問題,就需要考慮冪等性設(shè)計(jì)。

冪等設(shè)計(jì)實(shí)現(xiàn)

方案一:數(shù)據(jù)庫唯一索引 

在保存數(shù)據(jù)前,可以先 select 一下數(shù)據(jù)是否存在。如果數(shù)據(jù)已存在,說明是重復(fù)數(shù)據(jù),則不再寫入數(shù)據(jù),如果數(shù)據(jù)不存在,則執(zhí)行 insert 操作。如果 insert 成功,則直接返回成功,如果 insert 產(chǎn)生主鍵沖突異常,則捕獲異常進(jìn)行處理。

但在高并發(fā)的場景下,可能會(huì)出現(xiàn)兩個(gè)請求 select 的時(shí)候,都沒有查到數(shù)據(jù),然后都執(zhí)行了 insert 操作,所以此時(shí)會(huì)有重復(fù)數(shù)據(jù)產(chǎn)生,因此在數(shù)據(jù)庫中,我們需要添加唯一索引來保證冪等,唯一索引是不會(huì)引起重復(fù)數(shù)據(jù)的兜底策略。

方案二:防重表機(jī)制 

防重表機(jī)制與唯一索引機(jī)制是相同的原理,只不過是單獨(dú)建一個(gè)防重表,防重表也必須引入唯一索引,而且防重表與業(yè)務(wù)表必須在同一數(shù)據(jù)庫,并且操作要在同一個(gè)事務(wù)中。

防重表機(jī)制的主要流程:把唯一主鍵插入防重表,再進(jìn)行業(yè)務(wù)操作,且它們處于同一個(gè)事務(wù)中。當(dāng)重復(fù)請求時(shí),因?yàn)榉乐乇碛形ㄒ患s束,導(dǎo)致請求失敗,可以避免冪等問題。

注意防重表和業(yè)務(wù)表應(yīng)該在同一個(gè)庫中,這樣就保證處在一個(gè)事務(wù)中,即使業(yè)務(wù)操作失敗,也會(huì)把防重表的數(shù)據(jù)回滾。保證了數(shù)據(jù)的一致性。

該方案也是比較常用的,防重表跟業(yè)務(wù)無關(guān),很多業(yè)務(wù)可以共用同一個(gè)防重表,只要規(guī)劃好唯一主鍵即可。

圖片圖片

方案三:數(shù)據(jù)庫樂觀鎖 

樂觀鎖實(shí)現(xiàn)的方式有兩種:基于版本號(hào)、基于條件。但是實(shí)現(xiàn)思想都是基于行鎖來實(shí)現(xiàn)的。

基于版本號(hào)實(shí)現(xiàn)

通過為表增加一個(gè) “version” 字段來實(shí)現(xiàn)。讀取出數(shù)據(jù)時(shí),將此版本號(hào)一同讀出,之后更新時(shí),對此版本號(hào)加一。此時(shí),將提交數(shù)據(jù)的版本號(hào)與對應(yīng)記錄的當(dāng)前版本號(hào)進(jìn)行比對,如果提交的版本號(hào)等于當(dāng)前版本號(hào),則予以更新,否則認(rèn)為是過期數(shù)據(jù)。

圖片圖片

基于條件實(shí)現(xiàn)

版本號(hào)控制在并發(fā)場景中雖然能保證數(shù)據(jù)一致性,但在高并發(fā)庫存扣減的場景下存在體驗(yàn)問題:當(dāng)多個(gè)用戶同時(shí)查詢到可售庫存后,只有基于版本號(hào)的最新請求能扣減成功,這會(huì)導(dǎo)致一些用戶看似有庫存卻最終下單失敗。

從業(yè)務(wù)角度而言,只要確保庫存實(shí)際不發(fā)生超賣即可,此時(shí)更推薦直接通過數(shù)據(jù)庫條件控制:

update tb_stock set amount=amount-#{num} 
where goods_id=#{goodsId} and amount-#{num}>=0"

總結(jié):在競爭不激烈,出現(xiàn)并發(fā)沖突幾率較小時(shí),推薦使用樂觀鎖。但是,樂觀鎖的每次沖突檢測都需要與數(shù)據(jù)庫交互,頻繁的更新操作仍會(huì)對數(shù)據(jù)庫產(chǎn)生一定壓力。此外,在高并發(fā)場景下,大量事務(wù)競爭可能導(dǎo)致數(shù)據(jù)庫連接池耗盡或成為性能瓶頸。

方案四:悲觀鎖 

悲觀鎖的實(shí)現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機(jī)制,具有強(qiáng)烈的獨(dú)占和排他性。

通過 for update 可以實(shí)現(xiàn)排它鎖;

select * from account where id = 123 for update;

悲觀鎖在同一事務(wù)操作過程中,鎖住了一行數(shù)據(jù)。別的請求過來只能等待,如果當(dāng)前事務(wù)耗時(shí)比較長,就很影響接口性能。所以一般不建議用悲觀鎖做這個(gè)事情。

方案五:防重 Token 令牌 

采用 Token 機(jī)制確保冪等性是一種廣泛應(yīng)用的解決方案,能夠覆蓋絕大多數(shù)業(yè)務(wù)場景。該方案通過前后端協(xié)作實(shí)現(xiàn)。此方案包含兩個(gè)請求階段:

  1. 客戶端請求服務(wù)端申請獲取 token。
  2. 客戶端攜帶 token 再次請求,服務(wù)端校驗(yàn) token 后進(jìn)行操作。

圖片圖片

整體流程如下:

  1. 服務(wù)端提供獲取 token 接口,供客戶端進(jìn)行使用。服務(wù)端生成 token 后,如果當(dāng)前為分布式架構(gòu),將 token 存放于 redis 中(一般會(huì)設(shè)置一個(gè)過期時(shí)間),如果是單體架構(gòu),可以保存在本地緩存。
  2. 當(dāng)客戶端獲取到 token 后,會(huì)攜帶著 token 發(fā)起請求。
  3. 服務(wù)端接收到客戶端請求后,首先會(huì)判斷該 token 在 redis 中是否存在。如果存在,則完成進(jìn)行業(yè)務(wù)處理,業(yè)務(wù)處理完成后,再刪除 token。如果不存在,代表當(dāng)前請求是重復(fù)請求,直接向客戶端返回對應(yīng)標(biāo)識(shí)。

存在問題

但是現(xiàn)在有一個(gè)問題,當(dāng)前是先執(zhí)行業(yè)務(wù)再刪除 token。在高并發(fā)下,很有可能出現(xiàn)第一次訪問時(shí) token 存在,完成具體業(yè)務(wù)操作。但在還沒有刪除 token 時(shí),客戶端又?jǐn)y帶 token發(fā)起請求,此時(shí),因?yàn)?token 還存在,第二次請求也會(huì)驗(yàn)證通過,執(zhí)行具體業(yè)務(wù)操作。

針對該問題,我們提出兩種解決方案進(jìn)行探討:

第一種方案:對于業(yè)務(wù)代碼執(zhí)行和刪除 token 整體加線程鎖。 當(dāng)后續(xù)線程再來訪問時(shí),則阻塞排隊(duì)。

第二種方案:借助 redis 單線程和 incr 是原子性的特點(diǎn)。當(dāng)?shù)谝淮潍@取 token 時(shí),以 token 作為 key,對其進(jìn)行自增。然后將 token 進(jìn)行返回,當(dāng)客戶端攜帶 token 訪問執(zhí)行業(yè)務(wù)代碼時(shí),對于判斷 token 是否存在不用刪除,而是對其繼續(xù) incr。 如果 incr 后的返回值為 2。則是一個(gè)合法請求允許執(zhí)行,如果是其他值,則代表是非法請求,直接返回。

圖片圖片

前面提到的都是先執(zhí)行業(yè)務(wù)再刪除 token,那如果先刪除 token 再執(zhí)行業(yè)務(wù)呢?其實(shí)也會(huì)存在問題,假設(shè)具體業(yè)務(wù)代碼執(zhí)行超時(shí)或失敗,沒有向客戶端返回明確結(jié)果,那客戶端就很有可能會(huì)進(jìn)行重試,但此時(shí)之前的 token 已經(jīng)被刪除了,則會(huì)被認(rèn)為是重復(fù)請求,不再進(jìn)行業(yè)務(wù)處理。

圖片圖片

這種方案無需進(jìn)行額外處理,一個(gè) token 只能代表一次請求。 一旦業(yè)務(wù)執(zhí)行出現(xiàn)異常,則讓客戶端重新獲取令牌,重新發(fā)起一次訪問即可。推薦使用先刪除 token 方案

但是無論先刪 token 還是后刪 token,都會(huì)有一個(gè)相同的問題。每次業(yè)務(wù)請求都會(huì)產(chǎn)生一個(gè)額外的請求去獲 token。但是,業(yè)務(wù)失敗或超時(shí),在生產(chǎn)環(huán)境下,一萬個(gè)里最多也就十個(gè)左右會(huì)失敗,那為了這十來個(gè)請求,讓其他九千九百多個(gè)請求都產(chǎn)生額外請求,就有一些得不償失了。雖然 redis 性能好,但是這也是一種資源的浪費(fèi)。

方案六:分布式鎖 

分布式鎖實(shí)現(xiàn)冪等性的邏輯就是,請求過來時(shí),先去嘗試獲得分布式鎖,如果獲得成功,就執(zhí)行業(yè)務(wù)邏輯,反之獲取失敗的話,就舍棄請求直接返回成功。

分布式鎖可以使用 Redis,也可以使用 ZooKeeper,Redis 相對來說會(huì)更加輕量級。

Redis 分布式鎖,可以使用命令SETNX  + 唯一流水號(hào) 實(shí)現(xiàn),分布式鎖的 key 必須為業(yè)務(wù)的唯一標(biāo)識(shí)。

Redis 執(zhí)行設(shè)置 key 的動(dòng)作時(shí),要設(shè)置過期時(shí)間,這個(gè)過期時(shí)間不能太短,太短攔截不了重復(fù)請求,也不能設(shè)置太長,會(huì)占存儲(chǔ)空間。

責(zé)任編輯:武曉燕 來源: Java隨想錄
相關(guān)推薦

2023-08-29 13:53:00

前端攔截HashMap

2024-11-01 09:28:02

2025-04-27 03:22:00

系統(tǒng)接口冪等性

2024-06-24 01:00:00

2022-05-23 11:35:16

jiekou冪等性

2024-08-29 09:01:39

2025-05-06 00:00:05

MySQLES協(xié)同

2025-05-19 00:02:00

數(shù)據(jù)脫敏加密算法數(shù)據(jù)庫

2019-01-17 10:58:52

JS異步編程前端

2022-05-24 10:43:02

延時(shí)消息分布式MQ

2024-11-07 11:17:50

2022-05-05 07:49:54

業(yè)務(wù)冪MySQL索引

2021-04-14 17:18:27

冪等性數(shù)據(jù)源MySQL

2010-03-15 17:12:52

Python字典

2016-01-15 17:36:29

云計(jì)算云應(yīng)用

2011-06-07 09:36:18

2012-10-15 13:26:31

云計(jì)算架構(gòu)

2025-02-27 00:00:30

SpringJava方式

2020-10-14 09:00:00

SAST漏洞攻擊

2022-08-01 12:04:22

SaaSSaaS業(yè)務(wù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)