分布式項目保證消息冪等性的常見策略

Hello,大家好,我是灰小猿! 在分布式系統中,由于各個服務之間獨立部署,各個服務之間依靠遠程調用完成通信,再加上面對用戶重復點擊時的重復請求等情況,所以如何保證消息消費的冪等性是在分布式或微服務項目中必須要考慮的問題。

常見的保證消息冪等性的策略有以下幾種,根據具體的使用場景選用不同的冪等策略。

1、數據庫唯一索引

這種策略主要通過數據庫的唯一索引約束來保證消息的冪等性主要是用于數據插入的場景,防止數據重復插入,

適用場景:數據強一致性的場景,訂單創建防重,用戶注冊時手機號、郵箱號的唯一性校驗等,如防止用戶重復提交訂單,在訂單表中設置一個唯一標識的字段,如order表的Business_Key(業務ID)字段,當用戶提交重復的訂單時,這些重復的訂單所對應的Business_Key是相同的,此時插入數據數據庫會報索引重復DataIntegrityViolationException 異常,從而避免數據的重復插入。

對于這個Business_Key,可以使用用戶ID+商品ID+下單時間來生成,這個下單時間可能由于用戶點擊的先后順序有所不同,所以可以對時間進行處理,如五分鐘之內使用同一個時間標識,則可以使用下單時間戳除以300000(即5分鐘=300000毫秒),這樣可以有效保證同一業務訂單在一段時間內只會下單一次。

我們以一個商品訂單表為例,舉例數據表結構如下:

CREATE TABLE payment_order (order_id VARCHAR(32) PRIMARY KEY,business_key VARCHAR(64) UNIQUE NOT NULL,user_id BIGINT NOT NULL,amount DECIMAL(10,2),status VARCHAR(20),create_time DATETIME
);

給訂單表的business_key建立唯一索引

ALTER TABLE payment_order ADD UNIQUE INDEX uniq_business_key (business_key);

之后具體的業務實現流程大概如下:

1、用戶發起支付請求

2、生成訂單業務ID(business_key)

3、判斷存在業務ID相同的訂單

  • 返回已有訂單

  • 生成新的訂單

4、拉取第三方支付接口

生成唯一業務標識的方法如下:

public class BusinessKeyGenerator {// 時間窗口:5分鐘(300000毫秒)private static final long TIME_WINDOW = 300000;public static String generateKey(Long userId, String packageId) {long timeSlot = System.currentTimeMillis() / TIME_WINDOW;String rawKey = userId + ":" + packageId + ":" + timeSlot;return DigestUtils.md5Hex(rawKey);}
}

防止訂單重復創建的冪等性設計

1、通過先查詢訂單是否存在的方式插入

@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Transactionalpublic Order createOrder(Long userId, String packageId) {String businessKey = BusinessKeyGenerator.generateKey(userId, packageId);// 檢查是否存在未支付的相同業務訂單Order existingOrder = orderRepository.findPendingByBusinessKey(businessKey);if (existingOrder != null) {return existingOrder;}try {// 創建新訂單Order newOrder = new Order();newOrder.setOrderId(generateOrderId()); // 生成唯一訂單號newOrder.setBusinessKey(businessKey);newOrder.setUserId(userId);newOrder.setPackageId(packageId);newOrder.setStatus(OrderStatus.PENDING);return orderRepository.save(newOrder);} catch (DataIntegrityViolationException ex) {// 處理唯一鍵沖突(高并發場景)return orderRepository.findPendingByBusinessKey(businessKey);}}
}

2、通過捕獲唯一索引異常的方式插入

上面這種策略在創建新訂單之前是先通過業務ID的方式去查詢了數據庫中是否已經存在了這個業務ID對應的訂單,還有一種方式是直接生成訂單信息并且執行insert插入,之后通過捕獲唯一索引異常(DuplicateKeyException)的方式來返回已經創建的訂單信息。

具體的實現代碼如下:

@Transactional
public void createOrder(Order order) {try {orderDao.insert(order); // 觸發唯一約束} catch (DataIntegrityViolationException ex) {// 抓取重復提交異常Order existingOrder = orderRepository.findPendingByBusinessKey(businessKey);throw new DuplicateOrderException(existingOrder.getOrderId());}
}

2、樂觀鎖

通過數據庫樂觀鎖的方式保證冪等性,同樣也是基于數據庫的一種實現方式,

首先介紹一下樂觀鎖的概念:

樂觀鎖:即認為死鎖的發生是極小概率的事件,所以在修改數據之前不會對數據進行加鎖,只有在修改數據時通過判斷本次修改的版本和上一次的版本是否相同,相同則表示數據未被修改,不相同則表示數據已經被修改,此時的數據修改失敗。

適用場景:樂觀鎖機制適用于存在版本屬性的更新,這種方式的使用通常需要在數據庫表中增加int類型的versionId字段,每次修改數據時versionId=versionId+1,以此來保證每次更新的版本都是新的。

我們同樣以商品訂單表為例,其中加入version_id字段,用來記錄當前的數據版本。

CREATE TABLE payment_order (order_id VARCHAR(32) PRIMARY KEY,version_id int NOT NULL,user_id BIGINT NOT NULL,amount DECIMAL(10,2),status VARCHAR(20),create_time DATETIME
);

當執行更新時,需要判斷當前查詢到的version和將要更新的version是否相同

#查詢數據
SELECT version_id FROM payment_order WHERE order_id = #{order_id}#更新數據,要求數據當前版本號和已知版本號相同,并且每次更新版本號遞增
UPDATE payment_order SET status=PAID, version_id = version_id+1 
WHERE order_id = #{order_id} AND version_id = #{version_id}

3、悲觀鎖

介紹一下悲觀鎖的概念

悲觀鎖:即認為死鎖總是會發生的,所以在每次更新數據時都會對數據進行加鎖,當其他線程想要修改數據時會處于一個阻塞的狀態

這種處理方式一般需要我們在更新數據庫時使用行級鎖的更新方法,即開啟事務并先查詢出數據,同時對數據進行加鎖,更新完成數據之后,再提交事務,從而釋放鎖。

以獲取商品信息并生成訂單,之后進行庫存扣減為例,具體的sql操作如下:

//0.開始事務
begin//1.查詢出商品信息
select number from payment where id=#{payment_id} for update;//2.根據商品信息生成訂單
insert into payment_order (id,其他字段...) values (?,?,?,...);//3.修改商品庫存
update payment set number=#{number} where id=#{payment_id}//4.提交事務
commit

4、狀態機

狀態機的原理是通過狀態機的流轉控制,確保操作只會被執行一次

適用場景:這種機制適用于訂單或工單流程類系統,如訂單狀態變更,狀態機來保證消息冪等性的策略可以說是依據嚴格的業務執行流程來的,換句話來說就是一條數據的狀態只能由一個狀態變為指定的另外一種或多種狀態,

以訂單數據為例,狀態可以分為:待支付、已支付、已超時、已取消這幾種狀態,那么訂單的狀態流向就是固定的一個狀態機制,

以下是一個訂單狀態的狀態機

public enum OrderStateTypeEnum {PENDING,    // 待支付PAID,       // 已支付EXPIRED,    // 已超時CANCELED;   // 已取消/*** 狀態機*/private static final Map<OrderStateTypeEnum, Set<OrderStateTypeEnum>> transitions = new HashMap<>();static {//待支付狀態可以轉換為其他三種transitions.put(PENDING, EnumSet.of(PAID, EXPIRED, CANCELED));//已支付狀態不能轉換為其他狀態transitions.put(PAID, EnumSet.noneOf(OrderStateTypeEnum.class));}public boolean canTransitionTo(OrderStateTypeEnum orderStateType) {return transitions.get(this).contains(orderStateType);}
}

通過狀態機的方式去更新數據時,會先查詢訂單當前的狀態,并且判斷當前狀態是否可以轉化為將要更新后的狀態,如果可以再執行數據的更新,否則則認為當前的狀態轉變是不合理的。

5、Redis

基于Redis的原子操作來實現分布式鎖,通過SETNX設置key來標識是否處理過,并且設置過期時間,如果成功則處理,否則則忽略。

適用場景:高并發情況下的快速檢查,比如秒殺活動等,這種方式具有高性能低延遲的特點,但是在使用過程中要注意Redis的高可用問題。

以下是一個通過Redis實現分布式鎖,來避免訂單重復處理的代碼邏輯,

// Redis SETNX 實現分布式鎖
public class RedisLockService {public boolean tryLock(String key, String value, long expireSeconds) {return redis.opsForValue().setIfAbsent(key, value, expireSeconds, TimeUnit.SECONDS);}
}public class OrderService {public void addOrder(String orderId) {String lockKey = "lock:order:" + orderId;String requestId = UUID.randomUUID().toString();try {if (tryLock(lockKey, requestId, 30)) {// 業務處理processOrder(orderId);}} finally {// Lua腳本保證原子刪除String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";redis.execute(script, Collections.singletonList(lockKey), requestId);}}
}

6、token機制

token機制的實現是:客戶端先從服務器上獲取一個token,提交請求時攜帶上這個token,服務器端會驗證這個token是否已經存在,如果已經存在則刪除(因為在客戶端獲取這個Token時,服務器端已經存起來了)并且繼續后面的操作,這樣可以防止重復提交的發生,

適用場景:防止請求重復提交,API接口的短時效防重等,如在用戶下單時生成token,提交時服務器進行驗證,在代碼實現中可以用Redis存儲token,以此可以防止用戶重復提交多個訂單,

基于Token的實現的關鍵代碼處理如下:

// Redis + Token 機制
public class TokenService {@Autowiredprivate RedisTemplate<String, String> redis;/*** 生成Token*/public String generateToken(String userId) {String token = UUID.randomUUID().toString();//存入Redisredis.opsForValue().set(userId + ":" + token, "1", 5, TimeUnit.MINUTES);return token;}/*** 校驗Token的有效性*/public boolean validateToken(String userId, String token) {String key = userId + ":" + token;Long deleted = redis.delete(key); // 原子性刪除return deleted != null && deleted > 0;}
}

具體選用哪種冪等策略,還需要根據具體的業務功能來確定,在一個項目中,可能同時使用了多種冪等策略,這些都需要結合他們不同的特點和業務需求來分析,原則就是以實現功能的前提下以最小代碼成本實現功能。

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

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

相關文章

微信小程序(uniapp)對接騰訊云IM

UniApp 對接騰訊云 IM&#xff08;即時通訊&#xff09;完整指南 一、項目背景與需求分析 隨著社交場景的普及&#xff0c;即時通訊功能已成為移動應用的標配。騰訊云 IM&#xff08;Tencent IM&#xff0c;即 TIM&#xff09;提供穩定可靠的即時通訊服務&#xff0c;支持單聊…

Portainer安裝指南:多節點監控的docker管理面板-家庭云計算專家

背景 Portainer 是一個輕量級且功能強大的容器管理面板&#xff0c;專為 Docker 和 Kubernetes 環境設計。它通過直觀的 Web 界面簡化了容器的部署、管理和監控&#xff0c;即使是非技術用戶也能輕松上手。Portainer 支持多節點管理&#xff0c;允許用戶從一個中央控制臺管理多…

[Redis] Redis命令在Pycharm中的使用

初次學習&#xff0c;如有錯誤還請指正 目錄 String命令 Hash命令 List命令 set命令 SortedSet命令 連接pycharm的過程見&#xff1a;[Redis] 在Linux中安裝Redis并連接桌面客戶端或Pycharm-CSDN博客 redis命令的使用見&#xff1a;[Redis] Redis命令&#xff08;1&#xf…

計算機網絡:物理層

目錄 一、物理層的基本概念 二、物理層下面的傳輸媒體 2.1 導引型傳輸媒體 2.1.1 同軸電纜 2.1.2 雙絞線 2.1.3 光纖 2.1.4 電力線 2.2 非導引型傳輸媒體 2.2.1 無線電波 2.2.2 微波 2.2.3 紅外線 2.2.4 可見光 三、傳輸方式 3.1 串行與并行 3.2 同步與異步 3.…

構建系統maven

1 前言 說真的&#xff0c;我是真的不想看構建了&#xff0c;因為真的太多了。又多又亂。Maven、Gradle、Make、CMake、Meson、Ninja&#xff0c;Android BP。。。感覺學不完&#xff0c;根本學不完。。。 但是沒辦法最近又要用一下Maven&#xff0c;所以咬著牙再簡單整理一下…

UE5藍圖暴露變量,在游戲運行時修改變量實時變化、看向目標跟隨目標Find Look at Rotation、修改玩家自身彈簧臂

UE5藍圖中暴露變量&#xff0c;類似Unity中public一個變量&#xff0c;在游戲運行時修改變量實時變化 1&#xff0c;添加變量 2&#xff0c;設置變量的值 3&#xff0c;點開小眼睛&#xff0c;此變量顯示在編輯器中&#xff0c;可以運行時修改 看向目標跟隨目標Find Look at R…

proteus美觀與偏好設置

本文主要講&#xff1a; 1 快捷鍵修改&#xff08;復制&#xff0c;粘貼&#xff0c;原件旋轉&#xff09; 2 背景顏色替換 3 模塊分區 一 快捷鍵的設置 設置復制粘貼和旋轉三個 這里只是強調一下要分配 二 背景顏色 原來的背景顏色&#xff1a; 之后的背景顏色&#xff1a;…

Arm處理器調試采用jlink硬件調試器的命令使用大全

arm處理器分為cortex-a&#xff0c;cortex-r&#xff0c;cortex-m等3個內核系列&#xff0c;其中m系列一般是單片機&#xff0c;例如stm32等&#xff0c;工控用得挺多。a系列一般是消費娛樂產品等使用較多&#xff0c;例如手機處理器。r系列是高端實時類型處理器&#xff0c;價…

如何將圖像插入 PDF:最佳工具比較

無論您是編輯營銷材料、寫報告還是改寫原來的PDF文件&#xff0c;將圖像插入 PDF 都至關重要。幸運的是&#xff0c;有多種在線和離線工具可以簡化此任務。在本文中&#xff0c;我們將比較一些常用的 PDF 添加圖像工具&#xff0c;并根據您的使用場景推薦最佳解決方案&#xff…

4、獲取樹莓派溫度

打開終端&#xff0c;使用指令查看CPU溫度&#xff0c;依次輸入以下指令&#xff1a; 1.進入操作目錄 cd /sys/class/thermal/thermal_zone0 2.查看溫度 cat temp 樹莓派的返回值 51540 返回值除以1000為當前CPU溫度值。即當前溫度為51攝氏度。

Leetcode 269. 火星詞典

1.題目基本信息 1.1.題目描述 現有一種使用英語字母的外星文語言&#xff0c;這門語言的字母順序與英語順序不同。 給定一個字符串列表 words &#xff0c;作為這門語言的詞典&#xff0c;words 中的字符串已經 按這門新語言的字母順序進行了排序 。 請你根據該詞典還原出此…

使用vscode進行c/c++開發的時候,輸出報錯亂碼、cpp文件本身亂碼的問題解決

使用vscode進行c/c開發的時候&#xff0c;輸出報錯亂碼、cpp文件本身亂碼的問題解決 問題描述解決方案問題1的解決方案問題2解決方案 問題描述 本篇文章解決兩個問題&#xff1a; 1.當cpp文件出現錯誤的時候&#xff0c;編譯時報錯&#xff0c;但是報錯內容缺是亂碼&#xff0…

現代數據湖架構全景解析:存儲、表格式、計算引擎與元數據服務的協同生態

本文全面剖析現代數據湖架構的核心組件,深入探討對象存儲(OSS/S3)、表格式(Iceberg/Hudi/Delta Lake)、計算引擎(Spark/Flink/Presto)及元數據服務(HMS/Amoro)的協作關系,并提供企業級選型指南。 一、數據湖架構演進與核心價值 數據湖架構演進歷程 現代數據湖核心價…

主數據編碼體系全景解析:從基礎到高級的編碼策略全指南

在數字化轉型的浪潮中&#xff0c;主數據管理&#xff08;MDM&#xff09;已成為企業數字化轉型的基石。而主數據編碼作為MDM的核心環節&#xff0c;其設計質量直接關系到數據管理的效率、系統的可擴展性以及業務決策的準確性。本文將系統性地探討主數據編碼的七大核心策略&…

Mac電腦上本地安裝 MySQL并配置開啟自啟完整流程

文章目錄 一、mysql安裝1.1 使用 Homebrew 安裝&#xff08;推薦&#xff09;1.2 手動下載 MySQL 社區版1.3 常見問題1.4 圖形化管理工具&#xff08;可選&#xff09; 二、Mac 上配置 MySQL 開機自動啟動2.1 使用 launchd 系統服務&#xff08;原生支持&#xff09;2.2 通過 H…

SQL Server 事務詳解:概念、特性、隔離級別與實踐

一、事務的基本概念 事務&#xff08;Transaction&#xff09;是數據庫操作的基本單位&#xff0c;它是由一組SQL語句組成的邏輯工作單元。事務具有以下關鍵特性&#xff0c;通常被稱為ACID特性&#xff1a; ??原子性&#xff08;Atomicity&#xff09;??&#xff1a;事務…

【C語言極簡自學筆記】項目開發——掃雷游戲

一、項目概述 1.項目背景 掃雷是一款經典的益智游戲&#xff0c;由于它簡單而富有挑戰性的玩法深受人們喜愛。在 C 語言學習過程中&#xff0c;開發掃雷游戲是一個非常合適的實踐項目&#xff0c;它能夠綜合運用 C 語言的多種基礎知識&#xff0c;如數組、函數、循環、條件判…

unix/linux source 命令,其發展歷程詳細時間線、由來、歷史背景

追本溯源,探究技術的歷史背景和發展脈絡,能夠幫助我們更深刻地理解其設計哲學和存在的意義。source 命令(或者說它的前身和等效形式)的歷史,與 Unix Shell 本身的發展緊密相連。 讓我們一起踏上這段追溯之旅,探索 source 命令的由來和發展歷程。 早期 Unix Shell 與命令…

720全景展示:VR全景的技術原理及應用

VR720全景展示&#xff1a;技術原理及應用探索 720全景技術&#xff0c;作為當前全球范圍內迅速崛起流行的視覺新技術&#xff0c;為用戶帶來了全新的真實現場感和交互式的體驗。憑借全方位、無死角的視覺展示特性&#xff0c;在VR&#xff08;虛擬現實&#xff09;領域中得到…

Python爬蟲實戰:研究Requests-HTML庫相關技術

1. 引言 1.1 研究背景與意義 隨著互聯網數據量的爆炸式增長,網絡爬蟲已成為數據獲取的重要工具,廣泛應用于市場調研、輿情分析、學術研究等領域。傳統爬蟲技術在面對現代 JavaScript 動態渲染網頁時面臨挑戰,而 Requests-HTML 庫通過集成瀏覽器渲染引擎,為解決這一問題提…