MQ防重復消費----去重表結合 Spring AOP 切面編程,抽象封裝成通用冪等注解

以下內容包含針對 NoMQDuplicateConsumeAspect 的深度面試問答、消息隊列重投遞觸發場景、AOP 切面編程擴展,以及基于已有實現的關鍵要點與步驟總結。文中所有論斷均引用多源資料,以助于您在面試與實戰中全面展示對冪等消費切面及消息重投的理解。


一、深度面試官提問與解答

1. 接口與 AOP 解耦機制

:請解釋 NoMQDuplicateConsumeAspect 中,如何在不依賴具體業務類的前提下,通過 AOP 與 Spring 容器自動裝配實現冪等性攔截?

  • 切面僅依賴于統一注解 @NoMQDuplicateConsume 和切點定義,不直接持有業務 Bean,引入環繞通知實現攔截 。

  • Spring 在啟動時掃描所有被 @Component 標注的切面與 Handler Bean,將它們納入 AOP 代理與上下文管理,實現業務與切面完全解耦 (Home)。

2. 冪等鍵設計與全局唯一性

:使用 SpEL 表達式生成的冪等鍵如何保證全局唯一?當方法參數為復雜對象時,應如何優化?

  • 把關鍵字段(如消息 ID、業務流水號)拼接到 keyPrefix 后,形成 key = prefix + ":" + id,即可保證同一消息唯一緩存鍵 (Stack Overflow)。

  • 對于嵌套對象,可使用 Jackson 將其序列化成 JSON 字符串或僅提取必要字段哈希值,避免過長或重復性不足 (Medium)。

3. Lua 腳本原子性與命令語義

:為什么要在 Lua 腳本中同時使用 NXPXGET?能否改為多條 Redis 命令?有什么風險?

  • NX 確保只有當鍵不存在時才寫入;PX 指定過期毫秒;GET 返回舊值,實現原子 “讀-寫” 操作 (Redis)。

  • 分開執行 GETSET 會遭遇并發競態:兩個消費者都可能先 GET 得到空,再都 SET,失去冪等性保障 (Stack Overflow)。

4. 消息重投遞觸發條件

:MQ 在什么情況下會觸發消息重投遞?當消費者不 ACK 或超時時,容器如何處理?

  • 使用 JMS 事務模式時,若消息消費拋出異常導致事務回滾,消息未 ACK,會被立即或延遲重投 (InfoWorld)。

  • RabbitMQ 的 delivery?acknowledgement?timeout 機制:消費者在配置超時時間內未 ACK,則會重投或轉入死信隊列 (RabbitMQ) (Stack Overflow)。

  • 顯式 basicNack(..., requeue=true) 也可觸發重投;Quorum 隊列的 delivery-limit 達到閾值后則死信化 (RabbitMQ) (CloudAMQP)。

5. 異常與補償策略

:當鏈路中途拋出異常,Aspect 應如何確保 Redis 鍵被清理?在分布式事務下如何做補償?

  • 在環繞通知的 finally 塊中調用 redisTemplate.delete(key),保證無論業務成功與否都可清理過期或失敗標志 (Home)。

  • 對于跨服務分布式事務,可結合 Seata 等框架,在全局事務回滾時觸發消息補償或二次冪等刪除 (Ted Kaminski)。

6. 切面優先級與性能評估

:若系統中有多種切面(如日志、限流、冪等),如何定義執行順序?如何測量切面帶來的 TPS 開銷?

  • 切面實現 Ordered 接口或使用 @Order 明確優先級,數值越小越先執行;Advice 類型也影響“入點/出點”順序 (Home) 。

  • 可在切面中埋點 System.nanoTime() 前后差值,上報至 Micrometer/Prometheus 觀察延遲分布,從而量化每個切面對吞吐的影響 。

7. 動態配置與熱更新

:如何在不重啟服務的前提下動態調整 keyTimeout 或開啟/關閉冪等校驗?

  • 將配置托管于 Spring Cloud Config,并在切面 Bean 上加 @RefreshScope,通過 /actuator/refresh 拉取最新配置 (Medium)。

  • 或者實現自定義管理接口,在運行時通過調用 ChainContext 提供的更新方法,動態修改超時或開關狀態。

8. 跨場景復用與副作用隔離

:當需要在另一個消費場景中復用同一切面,僅改 mark() 標識,如何確保不會引入副作用?

  • 切面 mark() 返回值可基于方法注解或 SpEL 動態解析,不可硬編碼單一場景;并在 ChainContext 注冊時隔離不同 mark 的鍵空間 (Medium)。

  • 復用時,單元測試應覆蓋多場景同時并行消費,確保不同 mark 間 Redis 鍵互不干擾。

9. 監控與告警埋點

:在冪等校驗失敗或超時場景,如何上報監控?可結合哪些工具?

  • 在切面中調用 Micrometer 的 CounterTimer 指標記錄冪等跳過次數和處理時長,Prometheus/Grafana 可實時報警 。

  • 異常場景下可額外向 ELK(Elasticsearch + Logstash + Kibana)發送結構化日志,結合 Alertmanager 觸發告警 。

10. 測試覆蓋策略

:如何編寫單元與集成測試,模擬 Redis 鍵已存在、Lua 腳本報錯、MQ 重投遞等場景?

  • 單元測試:Mock StringRedisTemplate.execute(...) 返回不同值,驗證切面邏輯分支。

  • 集成測試:借助 Testcontainers 啟動真實 Redis、RabbitMQ 實例,發送測試消息并斷言消費結果與重投次數 (Home) (Nejc Korasa)。


二、MQ 重投遞觸發場景詳解

  1. 事務回滾重投

    • JMS 事務單元失敗時,Broker 保留消息并在事務結束后重新投遞 (InfoWorld)。

  2. ACK 超時重投

    • RabbitMQ 消費者若超出 consumer_timeout 時間未 ACK,Broker 會將消息重投或 DLQ (RabbitMQ) (Stack Overflow)。

  3. 顯式 NACK

    • 通過 channel.basicNack(..., requeue=true) 明確請求重投,或 Camel 的 redeliveryPolicy 控制重試次數 (RabbitMQ)。

  4. 背書閾值與死信

    • IBM MQ 在重投次數超 BOTHRESH 后移至背書隊列;RabbitMQ Quorum 隊列 delivery-limit 達到閾值后 DLX 處理 (Oracle Docs) (CloudAMQP)。

  5. Prefetch 與并發假重投

    • 過大 prefetch 造成處理緩慢,導致 ACK 超時,產生“假重試”現象 (Medium)。


三、AOP 切面編程擴展

  1. 切點與通知類型

    • 使用 @Pointcut 定義注解匹配,@Around 環繞通知可完全掌控方法執行前后與異常 (Medium)。

  2. Advice 順序

    • 實現 Ordered@Order,結合 AspectJ 語義控制“入點優先級”與“出點順序” (Home)。

  3. 代理模式與限制

    • Spring AOP 基于代理,無法攔截 privatestaticfinal 方法;對性能影響可通過窄切點與精確匹配減到最低 。

  4. 性能監控

    • 采用 Micrometer Observation API,在切面中記錄 Timer,結合 Spring Boot 3 Observability 提供可視化分析 (Home)。

  5. 動態切面生效

    • 利用 @Profile@ConditionalOnProperty 控制切面 Bean 是否加載;或配合 @RefreshScope 實時切換冪等校驗開關 (Stack Overflow) (Medium)。


四、關鍵要點與實現步驟總結

  1. 注解識別

    • 環繞通知通過 ProceedingJoinPoint 與反射 MethodSignature 獲取 @NoMQDuplicateConsume 實例。

  2. SpEL 解析冪等鍵

    • 調用 SpELUtil.parseKey(...) 結合方法參數動態生成全局唯一的 Redis key (prefix:業務ID)。

  3. 原子腳本執行

    • 單條 Lua 腳本 SET key value NX GET PX expire 保證讀寫原子性,避免并發競態。

  4. 結果判斷

    • 腳本返回 nil → 首次消費,執行業務;否則檢查返回值看是否為錯誤狀態,拋異常或直接跳過。

  5. 后置標記與清理

    • 業務成功后 SET key consumed PX expire;失敗或異常則在 finally/catchDEL key,支持 MQ 重投。

  6. 異常補償

    • 結合分布式事務框架或補償消息,確保跨服務調用時的一致性。

  7. 監控埋點

    • 利用 Micrometer/Grafana 跟蹤冪等跳過率、處理延遲與失敗數,確保實時報警與運維可視化。

附完整實現:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoMQDuplicateConsume {/*** 設置防重令牌 Key 前綴*/String keyPrefix() default "";/*** 通過 SpEL 表達式生成的唯一 Key*/String key();/*** 設置防重令牌 Key 過期時間,單位秒,默認 1 小時*/long keyTimeout() default 3600L;
}@Slf4j
@Aspect
@RequiredArgsConstructor
public final class NoMQDuplicateConsumeAspect {private final StringRedisTemplate stringRedisTemplate; // Redis 操作字符串模板// LUA 腳本,使用 Redis 的 SETNX 命令實現分布式鎖,并設置過期時間private static final String LUA_SCRIPT = """local key = KEYS[1]local value = ARGV[1]local expire_time_ms = ARGV[2]return redis.call('SET', key, value, 'NX', 'GET', 'PX', expire_time_ms)""";// 嘗試用 NX(不存在才設置) + PX(指定毫秒級過期時間)設置Key//如果設置成功,返回 nil//如果Key已經存在,返回舊的Value/*** 增強方法標記 {@link NoMQDuplicateConsume} 注解邏輯*/@Around("@annotation(com.nageoffer.onecoupon.framework.idempotent.NoMQDuplicateConsume)") // 創建 NoMQDuplicateConsumeAspect 切面控制器public Object noMQRepeatConsume(ProceedingJoinPoint joinPoint) throws Throwable {// 獲取自定義防重復消費注解NoMQDuplicateConsume noMQDuplicateConsume = getNoMQDuplicateConsumeAnnotation(joinPoint);// 獲取防重復消費注解 Key 的唯一標識String uniqueKey = noMQDuplicateConsume.keyPrefix() + // 防重令牌key前綴SpELUtil.parseKey(noMQDuplicateConsume.key(), // SpEL表達式動態生成唯一Key((MethodSignature) joinPoint.getSignature()).getMethod(), // 防重令牌key SpEL 表達式joinPoint.getArgs()); // 防重令牌key SpEL 表達式參數// Redis執行Lua腳本嘗試加防重復鎖// 如果Key不存在,成功設置,繼續執行業務。// 如果Key存在,說明這個消息之前消費過或正在消費。String absentAndGet = stringRedisTemplate.execute(RedisScript.of(LUA_SCRIPT, String.class),List.of(uniqueKey),IdempotentMQConsumeStatusEnum.CONSUMING.getCode(),String.valueOf(TimeUnit.SECONDS.toMillis(noMQDuplicateConsume.keyTimeout())));// 如果Key存在(重復消費了)if (Objects.nonNull(absentAndGet)) {// 判斷是否為錯誤狀態boolean errorFlag = IdempotentMQConsumeStatusEnum.isError(absentAndGet);log.warn("[{}] MQ repeated consumption, {}.", uniqueKey, errorFlag ? "Wait for the client to delay consumption" : "Status is completed");if (errorFlag) {throw new ServiceException(String.format("消息消費者冪等異常,冪等標識:%s", uniqueKey));}return null;}// 執行標記了消息隊列防重復消費注解的方法原邏輯Object result;try {result = joinPoint.proceed();// 設置防重令牌 Key 過期時間,單位秒stringRedisTemplate.opsForValue().set(uniqueKey, IdempotentMQConsumeStatusEnum.CONSUMED.getCode(), noMQDuplicateConsume.keyTimeout(), TimeUnit.SECONDS);} catch (Throwable ex) {// 刪除冪等 Key,讓消息隊列消費者重試邏輯進行重新消費stringRedisTemplate.delete(uniqueKey);throw ex;}return result;}/*** @return 返回自定義防重復消費注解*/public static NoMQDuplicateConsume getNoMQDuplicateConsumeAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {// getSignature() 拿到的是一個 Signature,一般是方法簽名信息。MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();// 獲取目標方法實例Method targetMethod = joinPoint.getTarget().getClass().getDeclaredMethod(methodSignature.getName(), methodSignature.getMethod().getParameterTypes());// 獲取方法上的注解return targetMethod.getAnnotation(NoMQDuplicateConsume.class);}
}

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

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

相關文章

[:, :, 1]和[:, :, 0] 的區別; `prompt_vector` 和 `embedding_matrix`的作用

prompt_vector = torch.sum(prompt_embedding * attention_weights.unsqueeze(-1), dim=1) # [1, hidden_dim] prompt_vector = torch.sum(prompt_embedding * attention_weights.unsqueeze(-1), dim=1) 主要作用是通過將 prompt_embedding 與 attention_weights 相乘后再按指…

Dinky 安裝部署并配置提交 Flink Yarn 任務

官方文檔 https://www.dinky.org.cn/docs/1.1/deploy_guide/normal_deploy 版本 dinky 1.1.0、1.2.3 當前最新發布版本為 1.2.3 ,但是官方文檔最新穩定版為 1.1 ,所以先選擇 1.1.0,驗證通過后,再嘗試 1.2.3 ,發現 1…

java連數據庫

一、準備工作 ??安裝MySQL數據庫?? 確保已安裝MySQL服務器并啟動服務 ??下載JDBC驅動?? 官方驅動&#xff1a;MySQL Connector/JMaven依賴&#xff1a; <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactI…

【生態信息】開源軟件全方位解析

開源軟件(0pen Source Software&#xff0c;0ss)是指其源代碼可以公開發布、查看、使用和修改的軟件。這一概念的核心在于開放性和共享性&#xff0c;允許開發者自由地使用、修改、分發以及改進軟件。開源軟件通常遵循特定的開源許可證&#xff0c;這些許可證確保了軟件的自由使…

探秘 DeerFlow:字節跳動開源的科研創作魔法盒!

1.前言 字節跳動于2025年5月9日開源了名為DeerFlow的全新Deep Research項目&#xff0c;該項目基于LangStack框架&#xff0c;旨在通過人工智能技術簡化科研和內容創作流程。DeerFlow整合了語言模型、網絡搜索、爬蟲和Python代碼執行等多種工具&#xff0c;支持深度研究、MCP集…

機器學習第十一講:標準化 → 把厘米和公斤單位統一成標準值

機器學習第十一講&#xff1a;標準化 → 把厘米和公斤單位統一成標準值 資料取自《零基礎學機器學習》。 查看總目錄&#xff1a;學習大綱 關于DeepSeek本地部署指南可以看下我之前寫的文章&#xff1a;DeepSeek R1本地與線上滿血版部署&#xff1a;超詳細手把手指南 一、買菜…

less中使用 @supports

在Less中使用supports supports 是CSS的條件規則&#xff0c;用于檢測瀏覽器是否支持特定的CSS屬性或值。在Less中&#xff0c;你可以像在普通CSS中一樣使用supports&#xff0c;同時還能利用Less的特性來增強它。 基本用法 /* 檢測瀏覽器是否支持display: flex */ supports …

LeetCode Hot100 (1/100)

目錄 一、有關數組和動態數組的排序&#xff08;sort函數&#xff09; 1.普通數組的排序 基本用法 降序排序 2.vector的排序 基本用法 降序排序 二、數組長度和一些vector的基本語法 1. 靜態數組長度計算? 2. 安全獲取數組長度&#xff08;C17 起&#xff09;? 3.vecto…

通過MCP讓LLM調用系統接口

場景 MCP的出現大大豐富了LLM的功能&#xff0c;對于存量系統&#xff0c;我們希望能讓模型調用已有的接口&#xff0c;以最小的成本讓AI能夠獲取系統內部數據。因此我們開發了一個名為http-api-call的MCP Server&#xff0c;來支持模型到內部API的調用 實現方案 使用用標準…

基于Transformer的多資產收益預測模型實戰(附PyTorch實現與避坑指南)

基于Transformer的多資產收益預測模型實戰(附PyTorch模型訓練及可視化完整代碼) 一、項目背景與目標 在量化投資領域,利用時間序列數據預測資產收益是核心任務之一。傳統方法如LSTM難以捕捉資產間的復雜依賴關系,而Transformer架構通過自注意力機制能有效建模多資產間的聯…

養生:打造健康生活的全方位策略

在生活節奏不斷加快的當下&#xff0c;養生已成為提升生活質量、維護身心平衡的重要方式。從飲食、運動到睡眠&#xff0c;再到心態調節&#xff0c;各個方面的養生之道共同構建起健康生活的堅實基礎。以下為您詳細介紹養生的關鍵要點&#xff0c;助您擁抱健康生活。 飲食養生…

輕型汽車鼓式液壓制動器系統設計

一、設計基礎參數 1.1 整車匹配參數 參數項數值范圍整備質量1200-1500kg最大設計車速160km/h輪胎規格195/65 R15制動法規要求GB 12676-2014 1.2 制動性能指標 制動減速度&#xff1a;≥6.2m/s&#xff08;0型試驗&#xff09; 熱衰退率&#xff1a;≤30%&#xff08;連續10…

無法更新Google Chrome的解決問題

解決問題&#xff1a;原文鏈接&#xff1a;【百分百成功】Window 10 Google Chrome無法啟動更新檢查&#xff08;錯誤代碼為1&#xff1a;0x80004005&#xff09; google谷歌chrome瀏覽器無法更新Chrome無法更新至最新版本&#xff1f; 下載了 就是更新Google Chrome了

【AAAI 2025】 Local Conditional Controlling for Text-to-Image Diffusion Models

Local Conditional Controlling for Text-to-Image Diffusion Models&#xff08;文本到圖像擴散模型的局部條件控制&#xff09; 文章目錄 內容摘要關鍵詞作者及研究團隊項目主頁01 研究領域待解決問題02 論文解決的核心問題03 關鍵解決方案04 主要貢獻05 相關研究工作06 解決…

Kuka AI音樂AI音樂開發「人聲伴奏分離」 —— 「Kuka Api系列|中文咬字清晰|AI音樂API」第6篇

導讀 今天我們來了解一下 Kuka API 的人聲與伴奏分離功能。 所謂“人聲伴奏分離”&#xff0c;顧名思義&#xff0c;就是將一段完整的音頻拆分為兩個獨立的軌道&#xff1a;一個是人聲部分&#xff0c;另一個是伴奏&#xff08;樂器&#xff09;部分。 這個功能在音樂創作和…

Idea 設置編碼UTF-8 Idea中 .properties 配置文件中文亂碼

Idea 設置編碼UTF-8 Idea中 .properties 配置文件中文亂碼 一、設置編碼 1、步驟&#xff1a; File -> Setting -> Editor -> File encodings --> 設置編碼二、配置文件中文亂碼 1、步驟&#xff1a; File -> Setting -> Editor -> File encodings ->…

Xilinx FPGA PCIe | XDMA IP 核 / 應用 / 測試 / 實踐

注&#xff1a;本文為 “Xilinx FPGA 中 PCIe 技術與 XDMA IP 核的應用” 相關文章合輯。 圖片清晰度受引文原圖所限。 略作重排&#xff0c;未整理去重。 如有內容異常&#xff0c;請看原文。 FPGA&#xff08;基于 Xilinx&#xff09;中 PCIe 介紹以及 IP 核 XDMA 的使用 N…

sqli—labs第六關——雙引號報錯注入

一&#xff1a;判斷輸入類型 首先測試 ?id1&#xff0c;?id1&#xff0c;?id1"&#xff0c;頁面回顯均無變化 所以我們采用簡單的布爾測試&#xff0c;分別測試數字型&#xff0c;單引號&#xff0c;雙引號 然后發現&#xff0c;只有在測試到雙引號注入的時候符合關鍵…

【TroubleShoot】禁用Unity Render Graph API 兼容模式

使用Unity 6時新建了項目&#xff0c;有一個警告提示&#xff1a; The project currently uses the compatibility mode where the Render Graph API is disabled. Support for this mode will be removed in future Unity versions. Migrate existing ScriptableRenderPasses…

圖形學、人機交互、VR/AR、可視化等領域文獻速讀【持續更新中...】

&#xff08;1&#xff09;筆者在時間有限的情況下&#xff0c;想要多積累一些自身課題之外的新文獻、新知識&#xff0c;所以開了這一篇文章。 &#xff08;2&#xff09;想通過將文獻喂給大模型&#xff0c;并向大模型提問的方式來快速理解文獻的重要信息&#xff08;如基礎i…