Redisson延遲隊列實現(xiàn)訂單關(guān)閉的操作方法
Redisson的RDelayedQueue是實現(xiàn)訂單到期關(guān)閉的利器,它比定時任務(wù)更精準、比Redis過期監(jiān)聽更可靠。其核心原理是利用Redis的**有序集合(Sorted Set)和發(fā)布/訂閱(Pub/Sub)**功能,在分布式環(huán)境下可靠地執(zhí)行延遲任務(wù)。
接下來,我們結(jié)合Java代碼,一步步來看如何實現(xiàn)。
?? 生產(chǎn)端:訂單創(chuàng)建時,埋下“定時炸彈”
在用戶下單成功后,我們需要做的不是立即啟動一個計時器,而是將這筆訂單的ID作為一條“延遲消息”發(fā)送出去。
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class OrderService {
@Resource
private RedissonClient redissonClient;
// 隊列的名稱,可以理解為用于存放待關(guān)閉訂單的“信箱”
private static final String ORDER_QUEUE_KEY = "order-close-queue";
public void createOrder(Order order) {
// 1. 保存訂單到數(shù)據(jù)庫 (省略具體邏輯)
saveToDB(order);
log.info("訂單 [{}] 創(chuàng)建成功,等待支付...", order.getId());
// 2. 將訂單ID放入延遲隊列,設(shè)置30分鐘后“爆炸”
try {
// 獲取一個阻塞隊列,這是消費者最終要監(jiān)聽的目標隊列
RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(ORDER_QUEUE_KEY);
// 基于阻塞隊列,創(chuàng)建一個延遲隊列
RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
// 將訂單ID放入延遲隊列,延遲30分鐘
delayedQueue.offer(order.getId().toString(), 30, TimeUnit.MINUTES);
log.info("訂單 [{}] 已放入延遲隊列,30分鐘后將自動關(guān)閉", order.getId());
} catch (Exception e) {
log.error("放入延遲隊列失敗", e);
// 這里可以考慮補償機制,比如記錄日志后由定時任務(wù)兜底
}
}
}核心邏輯:生產(chǎn)者調(diào)用delayedQueue.offer(),將任務(wù)(訂單ID)和延遲時間(30分鐘)告訴Redisson。Redisson客戶端會把這個任務(wù)連同計算好的執(zhí)行時間戳,一起存到Redis的一個**有序集合(ZSet)**中,并以時間戳作為排序的分數(shù)(score)。
?? 消費端:時刻待命,準時“拆彈”
我們需要一個后臺任務(wù)一直監(jiān)聽,一旦有訂單到期,立刻執(zhí)行關(guān)閉操作。
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RedissonClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class OrderCloseListener implements CommandLineRunner {
@Resource
private RedissonClient redissonClient;
@Resource
private OrderService orderService; // 注入你的訂單Service
private static final String ORDER_QUEUE_KEY = "order-close-queue";
@Override
public void run(String... args) throws Exception {
// 程序啟動后,在一個獨立的線程中監(jiān)聽隊列
new Thread(() -> {
log.info("訂單關(guān)閉監(jiān)聽線程已啟動,等待到期訂單...");
// 獲取與生產(chǎn)者相同的阻塞隊列
RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(ORDER_QUEUE_KEY);
// 注意:這里必須調(diào)用一次getDelayedQueue,目的是在消費端也初始化相關(guān)的監(jiān)聽器
// 雖然不調(diào)用也能工作,但官方推薦調(diào)用以保證可靠性
redissonClient.getDelayedQueue(blockingQueue);
while (true) {
try {
// 阻塞等待,直到有訂單到期。take()方法會一直阻塞直到拿到數(shù)據(jù)
String orderId = blockingQueue.take();
log.info("收到到期訂單ID:{},開始執(zhí)行關(guān)閉操作", orderId);
// 執(zhí)行真正的關(guān)單業(yè)務(wù)邏輯
orderService.closeExpiredOrder(Long.parseLong(orderId));
} catch (InterruptedException e) {
log.error("監(jiān)聽線程被中斷", e);
Thread.currentThread().interrupt();
break; // 線程中斷時退出循環(huán)
} catch (Exception e) {
log.error("處理到期訂單時發(fā)生錯誤", e);
// 防止單個消息處理失敗導(dǎo)致循環(huán)中斷,繼續(xù)監(jiān)聽下一個
}
}
}, "OrderClose-Listener").start();
}
}核心邏輯:消費者通過blockingQueue.take()阻塞地從Redis的**目標隊列(List)**中獲取消息。當一個任務(wù)在ZSet中的時間戳小于當前時間,Redisson的后臺線程就會自動把它從ZSet移動到目標List中。這時,take()方法就會立即返回,拿到訂單ID,執(zhí)行關(guān)閉邏輯。
?? 探秘Redisson的“內(nèi)部時鐘”機制
你可能會好奇,Redisson是如何精準地將到期任務(wù)從ZSet移到List的?這背后有一個巧妙的設(shè)計:
Redisson為每個延遲隊列啟動了一個后臺輪詢線程(基于Netty的時間輪實現(xiàn))。這個線程會:
- 定期查詢ZSet中分數(shù)最?。醋钤绲狡冢┑娜蝿?wù)。
- 如果該任務(wù)的到期時間戳小于當前時間,就把這個任務(wù)以及所有其他到期的任務(wù),從ZSet和另一個輔助的List中移除,并推入到消費者正在監(jiān)聽的目標List中。
- 為了提高效率,當有新的、更早到期的任務(wù)加入時,Redisson會通過**發(fā)布/訂閱(Pub/Sub)**功能發(fā)送一個通知,喚醒輪詢線程立即工作,而不是等到下一個輪詢周期。
所以,整個過程就像有一個精準的“鬧鐘”在幫你管理這些任務(wù)。
?? 實戰(zhàn)要點與進階思考
- 消息可靠性:所有任務(wù)數(shù)據(jù)都存儲在Redis中,即使你的應(yīng)用服務(wù)重啟,已經(jīng)提交但未到期的任務(wù)也不會丟失。Redis的持久化(RDB/AOF)機制為數(shù)據(jù)提供了最終保障。
- 分布式支持:生產(chǎn)者和消費者可以是完全不同的應(yīng)用實例,只要它們連接同一個Redis、并使用相同的隊列名稱(如
order-close-queue)即可。多個消費者實例可以同時take同一個隊列,實現(xiàn)任務(wù)的負載均衡。 - 時間精度:Redisson的默認輪詢間隔約為5秒,這意味著任務(wù)的觸發(fā)時間可能會有最多5秒的誤差。如果業(yè)務(wù)對時間精度要求極高,可以通過配置
config.setScanInterval(2000)來縮短輪詢間隔(單位為毫秒),但會略微增加Redis的壓力。 - 業(yè)務(wù)兜底:作為一種最佳實踐,即使有了延遲隊列,也建議配合一個低頻的定時任務(wù)(如每小時執(zhí)行一次)作為最后的檢查,去掃描那些極少數(shù)可能因為各種意外(如Redis故障)而未被關(guān)閉的訂單,確保業(yè)務(wù)邏輯的最終一致性。
到此這篇關(guān)于Redisson延遲隊列實現(xiàn)訂單關(guān)閉的操作方法的文章就介紹到這了,更多相關(guān)redisson延遲隊列訂單關(guān)閉內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea下如何設(shè)置項目啟動的JVM運行內(nèi)存大小
這篇文章主要介紹了idea下如何設(shè)置項目啟動的JVM運行內(nèi)存大小問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
使用Java實現(xiàn)自動調(diào)整Excel行高和列寬
在處理 Excel 文件時,自動調(diào)整行高和列寬是一項常見的需求,本文將介紹如何使用 Java 來自動調(diào)整 Excel 文件中的行高和列寬,文中的示例代碼講解詳細,感興趣的小伙伴可以了解下2026-01-01
IDEA通過git回滾到某個提交節(jié)點或某個版本的操作方法
這篇文章主要介紹了IDEA通過git回滾到某個提交節(jié)點或某個版本的方法,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07
Mybatis?Plus?中的LambdaQueryWrapper示例詳解
這篇文章主要介紹了Mybatis?Plus?中的LambdaQueryWrapper,本文通過示例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03
使用mybatisPlus的queryWrapper做左聯(lián)接,內(nèi)聯(lián)接方式
本文介紹了如何使用Mybatis-Plus的QueryWrapper進行SQL查詢,包括左連接、內(nèi)連接等操作,通過示例代碼展示了如何構(gòu)建復(fù)雜的SQL查詢,并將結(jié)果存儲在List對象中返回,希望給讀者提供參考2025-03-03

