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.mzph.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…