利用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 }
利用redis+AOP简单处理MQ冥等问题的更多相关文章
- 手把手教你用redis实现一个简单的mq消息队列(java)
众所周知,消息队列是应用系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构.目前使用较多的消息队列有 ActiveMQ,RabbitMQ,Zero ...
- 利用Redis cache优化app查询速度实践
注意:本篇文章译自speeding up existing app with a redis cache,如需要转载请注明出处. 发现问题 在应用解决方法之前,我们需要对我们面对的问题有一个清晰的认识 ...
- 利用redis写webshell
redis和mongodb我之所见 最近自己在做一些个人的小创作.小项目,其中用到了mongodb和redis,最初可能对这二者没有深入的认识.都是所谓的“非关系型数据库”,有什么区别么? 实际上,在 ...
- 利用redis实现分布式锁
分布式锁一般有三种实现方式: 1. 数据库乐观锁: 2. 基于ZooKeeper的分布式锁: 3. 基于Redis的分布式锁: 这里大概说一下三种方式的优缺点,数据库乐观锁优点是实现简单,只需要for ...
- redis 的简单命令
以下实例讲解了如何启动 redis 客户端: 启动 redis 客户端,打开终端并输入命令 redis-cli.该命令会连接本地的 redis 服务. $redis-cli redis > re ...
- Watchdogs利用Redis实施大规模挖矿,常见数据库蠕虫如何破?
背景 2月20日17时许,阿里云安全监测到一起大规模挖矿事件,判断为Watchdogs蠕虫导致,并在第一时间进行了应急处置. 该蠕虫短时间内即造成大量Linux主机沦陷,一方面是利用Redis未授权访 ...
- 后端利用Redis队列及哈希实现定时推送提醒的三个思路
周煦辰 2016年8月31日 本文介绍了一下本人在开发过程中遇到"定时推送提醒"的需求的时候所思考的三种解决方案. 明确问题 首先明确一下这个需求可能包含的几个"坑&qu ...
- Tomcat8利用Redis配置Session共享
同一个应用在运行多个tomcat实例的时候,经常需要共享Session.tomcat配置共享session有多种方式 1.利用tomcat自身集群特性进行配置: 2.利用Memcache第三方缓存进行 ...
- 如何更好的利用redis
原文地址http://oldblog.antirez.com/post/take-advantage-of-redis-adding-it-to-your-stack.html @(syoka)[re ...
随机推荐
- shell2-if判断
1.条件测试类型(判断类型): 将测试结果做为判断依据. 测试类型有以下三种 [ 命令 ] :命令测试法(最常用的) [[ 命令 ]] : 关键字测试 test 命令 以上是三种都可以,注意单词 ...
- Linux登录时,下游回显非常慢
问题现象 登录linux时,远程连接正常,[root@...]回显非常慢,在执行脚本时,很容易导致命令下发错乱 原因分析 家目录下的.bash_history文件太大,导致每次登陆时读取这个文件耗时太 ...
- xshell 所选的用户密钥未在远程主机上注册;无法加载密钥
他山之石 https://zhuanlan.zhihu.com/p/92528287 安全起见,服务器最近的安全策略准备进行更改,逐渐由原来的密码登录更换为密钥登录认证. 于是今天把服务器上的id_r ...
- nuxt 相关
https://github.com/xuqiang521/nuxt-ssr-demo https://www.cnblogs.com/laozhang-is-phi/p/10249248.html ...
- vue注册全局组件
在项目开发中能不能自己写一个组件可以像iview或者element那样可以不必引用就可以直接用呢?答案是可以的. 首先,写一个组件mainHeader. 接着在vue中注册这个组件,代码如下: Vue ...
- 关于APP设计规范和一些图层命名
首先,本人大学计算机专业出身,学过编程,工作的时候做过 产品经理,设计师,前端工程师,对工作的流程都有一些见解. 现在主攻前端工程师,做Web APP.今天收到设计师的设计稿,一看图层分类,这让我感觉 ...
- fis学习
http://fis.baidu.com/docs/beginning/getting-started.html#md5 还是喜欢时间戳?没问题,FIS也可以满足你的需求,点击这里
- 【爬虫】从零开始使用 Scrapy
一. 概述 最近有一个爬虫相关的需求,需要使用 scrapy 框架来爬取数据,所以学习了一下这个非常强大的爬虫框架,这里将自己的学习过程记录下来,希望对有同样需求的小伙伴提供一些帮助. 本文主要从下面 ...
- 利用栈实现括号匹配(python语言)
原理: 右括号总是与最近的左括号匹配 --- 栈的后进先出 从左往右遍历字符串,遇到左括号就入栈,遇到右括号时,就出栈一个元素与其配对 当栈为空时,遇到右括号,则此右括号无与之匹配的左括号 当最终右括 ...
- 使用Rainbond打包业务模块,实现业务积木式拼装
背景 每个程序员在学习开发的过程中,都知道解耦和模块化的重要性,也希望自己设计和开发的程序支持模块化,开发好的模块其他人就能快速复用,为了达成这个效果,我们学习各种模块化和解耦的技术,从面向对象的设计 ...