java-冪等性

冪等性

1.1冪等性定義:

在計算機領域中,冪等(Idempotence)是指任意一個操作的多次執行總是能獲得相同的結果,不會對系統狀態產生額外影響。在Java后端開發中,冪等性的實現通常通過確保方法或服務調用的結果具有確定性,無論調用次數如何,結果都是可預期的。

1.2注意:

在實際的互聯網服務開發中,冪等性的理論定義與業務邏輯間的沖突是常見的。
例如,考慮查詢操作,當A系統調用B系統的查詢接口時,如果首次調用由于B系統中的程序錯誤而導致業務邏輯失敗,即使在程序修復后系統A重新使用相同參數進行重試,B系統可能仍然返回相同的失敗響應。盡管這符合冪等性的定義,卻與實際業務邏輯不符。同樣,以訂單支付為例,首次調用由于賬戶余額不足而返回“余額不足”提示,用戶充值后再次使用相同參數發起支付請求,服務仍然返回“余額不足”響應,也符合冪等性的定義,但同樣不符合業務邏輯。
因此,在實現冪等性方案時,應該遵循冪等性方案的目標,而不僅僅是嚴格遵循冪等性的定義。尤其是涉及寫操作的服務,應當更關注防止重復請求帶來的不良副作用,例如重復扣款或退款。

1.3 什么情況下會出現冪等?

在微服務和分布式架構中,一個請求可能需要多個服務協作才能完成。在這個過程中,網絡抖動、系統運行異常等不確定因素使得請求的成功率不可能達到100%,一旦發生失敗或未知異常,最常見的處理方式就是重試,而重試必然會導致重復請求問題。

冪等設計主要是為了處理重復請求而生的,好的冪等方案可以保證重復請求獲得預期結果,而不產生副作用。

用戶不可靠: 用戶通過客戶端發起請求,由于手抖或有意重復點擊,很容易造成導致極短時間內發起多次重復請求。
網絡不可靠:網絡抖動、網關內部抖動有可能觸發重試機制,這個在使用消息隊列投遞消息時經常會遇到。MQ 消息中間件,消息重復消費;

服務不可靠: 在需要保證數據一致性的場景中,如果調用下游服務超時,在無法確認執行結果的情況下,常用的處理方法是重試。比如:前端調用后端接口發起支付超時,然后再次發起重試,可能會導致多次支付。

1.3 冪等與并發的關系

在具有并發寫操作的場景下,通常需要考慮冪等問題。例如,當用戶在極短時間內多次提交表單或者使用特殊手段同時提交多個表單時,這就是典型的并發場景,需要進行冪等性處理。為了防止重復請求被執行,服務端需要實施冪等性控制,以避免產生不符合預期的結果。

雖然并發場景大都存在冪等問題,但冪等問題卻并非并發場景所特有。冪等設計是為了識別并處理重復請求,而并發僅僅是重復請求的一種特殊情況。 事實上,只要重復請求涉及寫操作,無論是否并發,都需要做好冪等處理。舉個例子,用戶在pc端同時開了兩個窗口,間隔10分鐘分別提交表單,所有參數完全相同,這顯然不屬于并發,但仍需要進行冪等處理。


二、冪等性解決方案

這些方案的技術路線可以總結成三條:唯一索引、唯一數據、狀態機約束。

  • 唯一索引是指數據庫主鍵、唯一索引,唯一索引大部分是基于業務流水表建立,也可單獨建表實現;
  • 唯一數據是指悲觀鎖、樂觀鎖、分布式鎖等機制;
  • 狀態機約束,對于存在狀態流轉的業務,通過狀態機的流轉約束,可以實現有限狀態機的冪等。

在實際開發中,單獨使用這些方法往往效果有限,需要根據具體的業務場景靈活選擇、合理的運用上述實現方法。

  1. 數據庫:樂觀鎖、悲觀鎖、唯一主鍵、唯一索引
  2. 業務層:分布式鎖、下游傳遞唯一序列號
  3. Token令牌
  4. 狀態機

2.1 方案一:數據庫唯一主鍵實現冪等性

在這里插入圖片描述
缺點:無法使用change buffer,InnoDB為了進行唯一性檢查,必須有一次磁盤IO讀頁

2.1.1 方案一延伸:唯一索引方案機制

唯一索引方案依賴于數據庫表中不允許存在具有相同索引值的重復行。這種策略在關系型數據庫中廣泛支持,并且能有效利用唯一性約束來確保冪等性。 在高并發場景中,唯一索引能保證當多個線程嘗試同時插入相同記錄時,只有一個線程能成功執行,而其他線程將會因違反唯一性約束而拋出異常。

通常,業務流水表的建立是基于以下核心字段:
id(bigint 類型):作為主鍵,唯一標識每條記錄。
gmt_create(datetime 類型):記錄的創建時間。
gmt_modified(datetime 類型):記錄的最后修改時間。
user_id(varchar(32) 類型):用戶ID,這個字段也可以作為分表的依據。
out_biz_no(varchar(64) 類型):外部業務流水號,即調用方的冪等號。
biz_no(varchar(64) 類型):內部業務流水號,用于系統內部追蹤。
status(char(1) 類型):記錄執行狀態。

在這種設計中,user_id和out_biz_no通常會組合成一個聯合索引,這樣做能有效避免在并發情況下的數據重復插入問題,從而保障了業務操作的冪等性。

2.2 方案二:數據庫樂觀鎖實現冪等性

樂觀鎖主要依靠帶條件更新 來確保多次外部請求的一致性。在系統設計中,可以在數據表中添加版本號字段,用于標識當前數據的版本。每次對該數據表的記錄進行更新時,都需要提供上一次更新的版本號,示例操作如下:

//1. 取出要更新的對象,帶有版本versoin
select * from tablename where id = xxx//2. 更新數據
update tableName set sq = sq-#{quantity},version = #{version}+1 where id = xxx and version=#{version}

在這里插入圖片描述
特點:樂觀鎖主要適用于更新場景,確保多次更新不會影響結果的一致性。
缺點:操作業務前,需要先查詢出當前的version版本。會增加操作

2.3方案三:數據庫悲觀鎖機制

悲觀鎖依賴數據庫提供的鎖機制來實現,整個數據處理過程中,數據處于鎖定狀態,并與事務機制配合,能夠有效實現業務冪等性。操作示例如下:

// 1. 開啟事務
begin;
// 2. 基于冪等號查詢
record = select * from tbl_xxx where out_biz_no = 'xxx' for update;
// 3. 根據狀態進行決策
if(record.getStatus() != 預期狀態){return;
}
// 4. 更新記錄
update tbl_xxx set status = '目標狀態' where out_biz_no = 'xxx';
// 5. 提交事務
commit;

特點:
select for update,整個執行過程中鎖定該條記錄
缺點:
在DB讀大于寫的情況下盡量少用。悲觀鎖主要適用于更新場景,通過串行化請求處理來確保冪等性,但需要小心使用,因為在并發場景下,重復請求可能會導致線程長時間處于等待狀態,浪費資源且降低性能。

2.4 方案四:業務層采用分布式鎖機制

分布式鎖與悲觀鎖本質上相似,都通過串行化請求處理來實現冪等性。與悲觀鎖不同的是,分布式鎖更輕量。在系統接收請求后,首先嘗試獲取分布式鎖。如果成功獲取鎖,則執行業務邏輯;如果獲取失敗,則立即拒絕請求。
在這里插入圖片描述
分布式鎖的核心是識別重復請求,實現串行化處理。但要注意,獲取鎖成功后,業務邏輯的執行并沒有可靠保證。因此,在實際應用中,分布式鎖需要結合事務機制和重試機制,以形成完整的冪等性解決方案。

2.5方案五:防重 Token 令牌實現冪等性

2.5.1 流程:

1)當用戶訪問表單頁面時,客戶端請求服務端接口以獲取唯一的Token(可以是UUID或全局ID),服務端生成的Token會被存儲在Redis或數據庫中。
2)用戶首次提交表單時,將Token與表單一起發送至服務端,服務端會驗證Token的存在性,如果Token存在,則執行業務邏輯,并在完成后銷毀Token。
3)用戶再次提交表單時,同樣攜帶Token一起發送至服務端。但由于Token已被銷毀,服務端無法找到對應的Token,從而拒絕重復提交請求。
在這里插入圖片描述
在這里插入圖片描述

2.5.2實現:

(1)集群環境:token+redis
(2)單jvm環境:token+redis 或者token+jvm內存

2.5.3 Token特點

== 要申請,一次有效性,可以限流 ==

2.5.4缺點:

(1)產生過多額外請求
(2)先刪除token,如果業務處理出現異常但token已經刪除掉了,再來請求會被認定為重復請求
后刪除token,如果刪除redis中的token失敗了,再來請求不會攔截,發生了重復請求
無論是先刪除token還是后刪除token,都會導致每次業務請求都產生一個額外的請求去獲取token。然而,在生產環境中,業務失敗或超時的情況并不多見,大多數請求都能成功完成。因此,為了處理這少數失敗的請求,讓絕大多數請求都產生額外的請求也算是一種資源的浪費。

2.5.5 存在問題:刪除token時,是先完成業務操作后刪除token,還是先刪除token后執行業務操作呢?

答案:要先刪除 token ,再執行業務代碼 。『后刪除 token』的缺陷太致命
(1)先執行業務操作再刪除token
情況:在高并發下,可能出現第一次訪問時token存在,完成具體業務操作,但在還沒有刪除token時,客戶端又攜帶token發起請求。此時,因為token還存在,第二次請求也會驗證通過,執行具體業務操作。
對于這個問題有如下兩種解決方案:
第一種方案: 對于業務代碼執行和刪除token整體加線程鎖,使得后續線程阻塞排隊,但可能造成一定性能損耗與吞吐量降低。
第二種方案: 借助Redis單線程和INCR原子性特性,在獲取token時對其進行自增操作。當客戶端攜帶token訪問執行業務代碼時,繼續對其進行自增,如果自增后的返回值為2,則是一個合法請求允許執行,否則認為是非法請求,直接返回。
在這里插入圖片描述

(2)先刪除token再執行業務
如果業務執行超時或失敗,沒有向客戶端返回明確結果,客戶端就會進行重試,但此時之前的token已經被刪除,導致被認為是重復請求,不再進行業務處理。
在這里插入圖片描述

這種方案無需額外處理,一個token只能代表一次請求。一旦業務執行出現異常,則讓客戶端重新獲取令牌,重新發起一次訪問即可。
先刪除token,再執行業務邏輯,中間如果出現宕機,可能會導致業務調用失敗,對于這種情況,大不了就重新獲取token再次請求

2.6 方案六:狀態機機制

在許多業務單據中,存在有限數量的狀態,并且這些狀態之間的流轉順序是固定的。如果狀態已經處于下一個狀態,那么再次應用上一個狀態的變更邏輯是不會產生任何效果的,這就確保了有限狀態機的冪等性。
例如,庫存狀態通常包括"預扣中"、“扣減中”、“占用中"和"已釋放"等狀態。如果系統重復調用扣減接口,而庫存狀態已經是"扣減中”,則可以直接返回結果。
狀態機可以與樂觀鎖機制結合使用,示例操作如下:

update tableName set sq=sq-#{quantity},status=#{udpate_status} where id =#{id} and status=#{status}

特點:和任務、狀態相關的業務,肯定會涉及狀態機,業務的一個屬性狀態,可以作為冪等的一個根據

2.7方案七:下游傳遞唯一序列號實現冪等性

在這里插入圖片描述
缺點:無法控制下游唯一序列號的生成規則,如果序列號由時間戳生成,那么無法攔截類似重復點擊這種情況下的重復請求

3.1 補充 redis冪等性

3.1.1 redis冪等性

在Redis中,冪等性是指相同的操作可以被多次執行而不會產生額外的影響或副作用。簡而言之,就是無論執行多少次相同的操作,結果都是一樣的。 在Redis中,可以通過以下幾種方式來實現redis的冪等性:
(1)使用Redis的原子性操作:Redis提供了一些原子性操作,如SETNX、INCR、SADD等。 這些操作在執行時是原子性的,即是一個操作的結果要么成功執行,要么沒有執行。通過使用這些原子性操作,可以保證相同的操作在執行時只會生效一次。
(2)使用Redis的事務:Redis的事務可以將一系列的操作包裝在一個事務中,然后一起執行。在事務執行期間,其他客戶端的請求不會干擾到事務的執行。通過將冪等操作放在一個事務中執行,可以保證這些操作只會被執行一次。
(3)使用Redis的分布式鎖:通過使用Redis的分布式鎖,可以保證同一時間只有一個客戶端可以執行特定的操作。當一個客戶端獲取到鎖后,其他客戶端嘗試獲取鎖的操作會被阻塞,直到鎖被釋放。通過使用分布式鎖,可以保證相同的操作只會被執行一次。

總結起來,Redis中可以通過原子性操作、事務和分布式鎖等方式來實現redis的冪等性。 這樣可以保證相同的操作在執行時不會產生額外的影響或副作用。

3.1.2 redis SETNX分布鎖詳解

1.SETNX:向Redis中添加一個key,只用當key不存在的時候才添加并返回1,存在則不添加返回0。并且這個命令是原子性的。
2. 使用SETNX作為分布式鎖時,添加成功表示獲取到鎖,添加失敗表示未獲取到鎖。至于添加的value值無所謂可以是任意值(根據業務需求),只要保證多個線程使用的是同一個key,所以多個線程添加時只會有一個線程添加成功,就只會有一個線程能夠獲取到鎖。而釋放鎖鎖只需要將鎖刪除即可。
3.設置過期時間防止死鎖
在添加時存在則添加,不存在則不添加。同時設置過期時間,單位秒
示例:

/*** 計算結果寫入到kafka冪等性的實現** @param json* @return*/public boolean idempotent(String json) {try {RedisPool redisPool = RedisPool.instance(properties);Jedis jedis = redisPool.getResource();String value = "1";if (jedis.setnx(json, value) > 0) {jedis.expire(json, 24 * 3600);redisPool.returnResource(jedis);return true;}redisPool.returnResource(jedis);} catch (Exception e) {System.err.println("redis pool get redis failed: " + json);}return false;
}  

2.防止死鎖

SET key value NX EX time//通過java代碼實現SETNX同時設置過期時間 //key--鍵 value--值 time--過期時間 TimeUnit--時間單位枚舉
stringRedisTemplate.opsForValue().setIfAbsent(key, value , time, TimeUnit); 

優點:
1.程序可以分組,可以分布式,亦可以用于數據恢復程序
2.減少了對redis操作頻度,提高了程序的并發性.

3.2 參考文章:

https://mp.weixin.qq.com/s/7YDtl8EfYvre49Al9yVZIw
https://blog.csdn.net/sinat_32023305/article/details/119610885
https://blog.csdn.net/q7w8e9r4/article/details/132533849

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/718118.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/718118.shtml
英文地址,請注明出處:http://en.pswp.cn/news/718118.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

設計模式(十四)中介者模式

請直接看原文: 原文鏈接:設計模式(十四)中介者模式_設計模式之中介模式-CSDN博客 -------------------------------------------------------------------------------------------------------------------------------- 前言 寫了很多篇設計模式的…

Ribbon實現Cloud負載均衡

安裝Zookeeper要先安裝JDK環境 解壓 tar -zxvf /usr/local/develop/jdk-8u191-linux-x64.tar.gz -C /usr/local/develop 配置JAVA_HOME vim /etc/profile export JAVA_HOME/usr/local/develop/jdk1.8.0_191 export PATH$JAVA_HOME/bin:$PATH export CLASSPATH.:$JAVA_HOM…

npm install 一直在等待sill idealTree buildDeps

最近在整vue的前端項目,npm install的時候一直在等待,記錄下處理過程。 1.檢查當前npm鏡像的地址 輸入命令:"npm config get registry",如果結果是"https://registry.npmjs.org/"。 更換下地址&#xff0c…

Windows Server 各版本搭建文件服務器實現共享文件(03~19)

一、Windows Server 2003 打開服務器,點擊左下角開始?管理工具?管理您的服務器?添加或刪除角色 點擊下一步等待測試 勾選自定義配置,點擊下一步 選擇文件服務器,點擊下一步 勾選設置默認磁盤空間,數據自己更改,最…

【JavaEE】_Spring MVC 項目傳參問題

目錄 1. 傳遞單個參數 1.1 關于參數名的問題 2. 傳遞多個參數 2.1 關于參數順序的問題 2.2 關于基本類型與包裝類的問題 3. 使用對象傳參 4. 后端參數重命名問題 4.1 關于RequestPara注解 1. 傳遞單個參數 現創建Spring MVC項目,.java文件內容如下&#xff…

2024年3月計劃

根據規劃,為了要考慮把產品代碼吃透。先對于計算幾何,圖像處理,測量學基礎進行 當然,也要把ue繼續進行著。ue的rpg和底層渲染。收集下虛幻商城的免費資源,萬一以后做獨立游戲用得到。其他的可以暫時不進行。因為ue的工…

Apache Flink連載(三十七):Flink基于Kubernetes部署(7)-Kubernetes 集群搭建-3

?? 個人主頁:IT貧道-CSDN博客 ?? 私聊博主:私聊博主加WX好友,獲取更多資料哦~ ?? 博主個人B棧地址:豹哥教你學編程的個人空間-豹哥教你學編程個人主頁-嗶哩嗶哩視頻 目錄

AI-數學-高中-32-概率-樣本空間與隨機事件

原作者視頻:【概率】【一數辭典】1樣本空間與隨機事件_嗶哩嗶哩_bilibili 1.隨機試驗: 2.樣本點、樣本空間、有限樣本空間: 示例1 示例2 3.事件: 示例:

PAT基礎練習

本題要求你寫個程序把給定的符號打印成沙漏的形狀。例如給定17個“*”,要求按下列格式打印 ************ *****所謂“沙漏形狀”,是指每行輸出奇數個符號;各行符號中心對齊;相鄰兩行符號數差2;符號數先從大到小順序遞減…

自己本地模擬內存數據庫增刪改查

目錄 學習初衷準備代碼實現結果感謝閱讀 學習初衷 用于滿足自己的測試要求,不連接數據庫,也不在意數據丟失 準備 maven依賴 org.springframework.boot spring-boot-starter-test test 代碼實現 內存數據庫(InMemoryDatabase&#xff0…

[AutoSar]BSW_Com08 CAN driver 模塊介紹及參數配置說明 (二)

目錄 關鍵詞平臺說明一、CanControllers二、CanTxProcessing三、CanFilterMask四、CanHardwareObjects五、CanGeneral 關鍵詞 嵌入式、C語言、autosar、OS、BSW 平臺說明 項目ValueOSautosar OSautosar廠商vector ,芯片廠商TI 英飛凌編程語言C,C編譯器…

游戲引擎分層簡介

游戲引擎分層架構(自上而下) 工具層(Tool Layer) 在一個現代游戲引擎中,我們最先看到的可能不是復雜的代碼,而是各種各樣的編輯器,利用這些編輯器,我們可以制作設計關卡、角色、動畫…

數據類型和變量

1.數據類型 在Java中數據類型主要分為兩類:基本數據類型和引用數據類型。 基本數據類型有四類八種: 1. 四類:整型、浮點型、字符型以及布爾型 2.八種: 整形是分為如上四種 byte short int long 浮點型分為 float 和double …

Docker Compose系列--搭建halo與mysql

原文網址:使用Docker Compose系列--搭建halo與mysql_IT利刃出鞘的博客-CSDN博客 簡介 說明 本文介紹如何使用Docker Compose搭建halo與mysql。 官網網址 使用 Docker 部署 Halo 和 MySQL | Halo Documents Docker Compose搭建mysql 見:Docker Com…

【大廠AI課學習筆記NO.64】機器學習開發框架

機器學習開發框架本質上是一種編程庫或工具,目的是能夠讓開發人員更容易、更快速地構建機器學習模型。 機器學習開發框架封裝了大量的可重用代碼,可以直接調用,目的是避免“重復造輪子’大幅降低開發人員的開發難度,提高開發效率…

軟考高級:候選碼、主碼、全碼、外碼、主屬性、主鍵、主關鍵字、非主屬性概念和例題

一、AI 講解 候選碼、主碼、全碼、外碼、主屬性、主鍵、主關鍵字、非主屬性是數據庫設計和數據建模中的基本概念,特別是在關系數據庫模型中。下面將對這些概念進行簡單講解,并給出相應的例子。 概念講解 候選碼(Candidate Key)…

Spring框架精髓:帶你手寫IoC

個人名片: 🐼作者簡介:一名大三在校生,喜歡AI編程🎋 🐻???個人主頁🥇:落798. 🐼個人WeChat:hmmwx53 🕊?系列專欄:🖼?…

足球青訓俱樂部|基于Springboot的足球青訓俱樂部管理系統設計與實現(源碼+數據庫+文檔)

足球青訓俱樂部管理系統目錄 目錄 基于Springboot的足球青訓俱樂部管理系統設計與實現 一、前言 二、系統設計 1、系統架構設計 三、系統功能設計 1、管理員登錄界面 2、公告信息管理界面 3、學員管理界面 4、商品信息管理界面 5、課程安排管理界面 四、數據庫設計…

ArcGIS Runtime For Android開發之符號化和圖層渲染

一、用Symbol對要素進行符號化 首先我們看一下Symbol 接口關系: 1、SimpleFillSymbol 他是用來進行簡單的Graphic面要素填充符號化的,它可以設置要素的填充顏色,邊線顏色、線寬,其用法如下: Polygon polygonnew Po…

常用的電阻、電容的種類和應用場合?

電阻的 a.按阻值特性:固定電阻、可調電阻、特種電阻(敏感電阻),不能調節的,我們稱之為固定電阻,而可以調節的,我們稱之為可調電阻.常見的例如收音機音量調節的,主要應用于電壓分配的,我們稱之為電位器. b.按制造材料:碳膜電阻、金屬膜電阻、線繞電阻,捷…