利用redis+AOP简单处理MQ幂等问题

思路:

1、利用redis内部的串行执行特性,使用getandset()处理分布式+并发问题;

2、注解提供入参选择,通过数据抽取后计算MD5值,实现业务性值的幂等;

代码区:

1、注解

 1 /**
 2  * 功能描述:MQ简单幂等性处理
 3  * 作者:唐泽齐
 4  */
 5 @Documented
 6 @Target({
 7         ElementType.METHOD
 8 })
 9 @Retention(RetentionPolicy.RUNTIME)
10 public @interface MqPitfall {
11 
12     // 过期时长 默认30天  单位/秒(s)
13     long timeOut() default 30*24*60*60l;
14 
15     // 幂等效验 参数 必须是能从onMessage()方法的入参中取出的属性
16     String[] args() default {};
17 }

2、AOP

 1 /**
 2  * 功能描述:MQ信息过滤
 3  * 作者:唐泽齐
 4  */
 5 @Aspect
 6 @Component
 7 public class MqPitfallInterceptor {
 8 
 9     static final String mqPitfallKey = "MqPitfall:";
10     static final Logger logger = LoggerFactory.getLogger(com.lechuang.common.redis.intercaptor.MqPitfallInterceptor.class);
11 
12     @Resource
13     RedisService redisService;
14 
15     @Around("@annotation(MqPitfall)")
16     public void around(ProceedingJoinPoint point) throws Throwable {
17         MqPitfall mqPitfall = ((MethodSignature) point.getSignature()).getMethod().getAnnotation(MqPitfall.class);
18         String className = ((MethodSignature) point.getSignature()).getMethod().getDeclaringClass().getName();
19         Map<String,Object> map = new HashMap<>();
20         try {
21             for(Object arg: point.getArgs()) {
22                 JSONObject json = null;
23                 if(arg instanceof String) {
24                     json = JSON.parseObject(arg.toString());
25                 } else {
26                     json = JSON.parseObject(JSON.toJSONString(arg));
27                 }
28                 for(String key:mqPitfall.args()) {
29                     map.put(key,json.get(key));
30                 }
31             }
32             if(map.isEmpty()) {
33                 for(Object arg: point.getArgs()) {
34                     JSONObject json = null;
35                     if(arg instanceof String) {
36                         json = JSON.parseObject(arg.toString());
37                     } else {
38                         json = JSON.parseObject(JSON.toJSONString(arg));
39                     }
40                     for(String key: json.keySet()) {
41                         map.put(key,json.get(key));
42                     }
43                 }
44             }
45         } catch (Exception e) {
46             map.put("Args",Arrays.deepToString(point.getArgs()));
47         }
48         map.put("Aspect",className);
49         String thisMd5 = MD5.create().digestHex(map.toString());
50         String key = mqPitfallKey + thisMd5;
51 
52         //简单的占位锁机制
53         Object value = redisService.getAndSet(key, -1l);
54         if(ObjectUtils.isEmpty(value)) {
55             redisService.set(key,1,mqPitfall.timeOut());
56             point.proceed();
57         } else {
58             logger.warn("MQ信息重复消费 摘要["+thisMd5+"] ==》" + Arrays.deepToString(point.getArgs()));
59         }
60     }
61 }

3、使用

1 /**
2  * @Method 引入切面注解
3  */
4 @Configuration
5 @Import({MqPitfallInterceptor.class})
6 public class WebAppConfig implements WebMvcConfigurer {
7 
8 }
 1 /**
 2  * 作者:唐泽齐
 3  */
 4 @Slf4j
 5 @Service
 6 @RequiredArgsConstructor
 7 @RocketMQMessageListener(consumerGroup = GuildTopic.GUILD_ANCHOR_ATTEST+"_guildAnchorAttestListener", consumeMode = ConsumeMode.ORDERLY, topic = GuildTopic.GUILD_ANCHOR_ATTEST)
 8 public class GuildAnchorAttestListener implements RocketMQListener {
 9 
10     private final GuildAnchorAttestService guildAnchorAttestService;
11 
12     @Override
13     @MqPitfall(args = {"userId","guildId"})
14     public void onMessage(Object message) {
15         log.info("xxxxxx 开始 ==》" + message);
16         long millis = System.currentTimeMillis();
17         try {
18             GuildTopicEnum guildTopicEnum = GuildTopic.find(GuildTopic.GUILD_ANCHOR_ATTEST);
19             if(!guildTopicEnum.valid(message)) {
20                 log.error("xxxxxx  异常 ==> 信息效验不合格 : "+message);
21                 return;
22             }
23             GuildAnchorAttest attest = guildTopicEnum.getData().toJavaObject(GuildAnchorAttest.class);
24             guildAnchorAttestService.save(attest);
25             log.info("xxxxxx  成功 ==》" + message);
26         } catch (Exception e) {
27             log.error("xxxxxx  失败 ==》 "+ message,e);
28         } finally {
29             log.info("xxxxxx  耗时 "+(System.currentTimeMillis()-millis)+"ms ==》" + message);
30         }
31 
32     }
33 }

 

posted on 2022-02-11 17:14  instr  阅读(324)  评论(2编辑  收藏  举报

导航