思路:
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 }