新的阅读体验:http://www.zhouhong.icu/post/157

一、业务需求

  需要实现一个提前二十分钟通知用户去做某件事的一个业务,拿到这个业务首先想到的最简单得方法就是使用Redis监控Key值:在排计划时候计算当前时间与提前二十分钟这个时间差,然后使用一个唯一的业务Key压入Redis中并设定好过期时间,然后只需要让Redis监控这个Key值即可,当这个Key过期后就可以直接拿到这个Key的值然后实现发消息等业务。

  关于Redis实现该业务的具体实现在之前我已经记过一篇笔记,有兴趣的可以直接去瞅瞅,但是现在感觉有好多不足之处。

       Redis实现定时: http://www.zhouhong.icu/post/144

二、Redis实现定时推送等功能的不足之处

  由于Redis不止你一个使用,其他业务也会使用Redis,那么最容易想到的一个缺点就是:1、如果在提醒的那一刻有大量的其他业务的Key也过期了,那么就会很长时间都轮不到你的这个Key,就会出现消息推送延迟等缺点;2、还有一个缺点就是像阿里云他们的Redis根本就不支持对 Redis 的 Key值得监控(我也是因为公司使用阿里云的Redis没法对Key监控才从之前使用Redis监控转移到使用RocketMQ的延时消息推送的。。。)

三、阿里云RocketMQ定时/延迟消息队列实现

  其实在实现上非常简单

1、首先去阿里云控制台创建所需消息队列资源,包括消息队列 RocketMQ 的实例、Topic、Group ID (GID),以及鉴权需要的 AccessKey(AK),一般公司都有现成的可以直接使用。
2、在springboot项目pom.xml添加需要的依赖。
<!--阿里云MQ TCP-->
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>ons-client</artifactId>
<version>1.8.7.1.Final</version>
</dependency>
3、在对应环境的application.properties文件配置参数
console:
rocketmq:
tcp:
accessKey: XXXXXXXX使用自己的
secretKey: XXXXXXXXXXXXX使用自己的
nameSrvAddr: XXXXXXXXXXXXXXXX使用自己的
topic: XXXXXXX使用自己的
groupId: XXXXXXX使用自己的
tag: XXXXXXXXX使用自己的
4、封装MQ配置类
import com.aliyun.openservices.ons.api.PropertyKeyConst;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; import java.util.Properties;
/**
* @Description: MQ配置类
* @Author: zhouhong
* @Date: 2021/8/4
*/
@Configuration
@EnableConfigurationProperties({PatrolMqConfig.class})
@ConfigurationProperties(prefix = "console.rocketmq.tcp")
@Primary
public class PatrolMqConfig { private String accessKey;
private String secretKey;
private String nameSrvAddr;
private String topic;
private String groupId;
private String tag;
private String orderTopic;
private String orderGroupId;
private String orderTag; public Properties getMqPropertie() {
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.AccessKey, this.accessKey);
properties.setProperty(PropertyKeyConst.SecretKey, this.secretKey);
properties.setProperty(PropertyKeyConst.NAMESRV_ADDR, this.nameSrvAddr);
return properties;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getNameSrvAddr() {
return nameSrvAddr;
}
public void setNameSrvAddr(String nameSrvAddr) {
this.nameSrvAddr = nameSrvAddr;
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
public String getOrderTopic() {
return orderTopic;
}
public void setOrderTopic(String orderTopic) {
this.orderTopic = orderTopic;
}
public String getOrderGroupId() {
return orderGroupId;
}
public void setOrderGroupId(String orderGroupId) {
this.orderGroupId = orderGroupId;
}
public String getOrderTag() {
return orderTag;
}
public void setOrderTag(String orderTag) {
this.orderTag = orderTag;
}
}
5、配置生产者
import com.aliyun.openservices.ons.api.bean.ProducerBean;
import com.honyar.iot.ibs.smartpatrol.modular.mq.tcp.config.PatrolMqConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class PatrolProducerClient { @Autowired
private PatrolMqConfig mqConfig;
@Bean(name = "ConsoleProducer", initMethod = "start", destroyMethod = "shutdown")
public ProducerBean buildProducer() {
ProducerBean producer = new ProducerBean();
producer.setProperties(mqConfig.getMqPropertie());
return producer;
}
}
6、消费者订阅
import com.aliyun.openservices.ons.api.MessageListener;
import com.aliyun.openservices.ons.api.PropertyKeyConst;
import com.aliyun.openservices.ons.api.bean.ConsumerBean;
import com.aliyun.openservices.ons.api.bean.Subscription;
import com.honyar.iot.ibs.smartpatrol.modular.mq.tcp.config.PatrolMqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties; //项目中加上 @Configuration 注解,这样服务启动时consumer也启动了
@Configuration
@Slf4j
public class PatrolConsumerClient { @Autowired
private PatrolMqConfig mqConfig; @Autowired
private MqTimeMessageListener messageListener; @Bean(initMethod = "start", destroyMethod = "shutdown")
public ConsumerBean buildConsumer() {
ConsumerBean consumerBean = new ConsumerBean();
//配置文件
Properties properties = mqConfig.getMqPropertie();
properties.setProperty(PropertyKeyConst.GROUP_ID, mqConfig.getGroupId());
//将消费者线程数固定为20个 20为默认值
properties.setProperty(PropertyKeyConst.ConsumeThreadNums, "20");
consumerBean.setProperties(properties);
//订阅关系
Map<Subscription, MessageListener> subscriptionTable = new HashMap<Subscription, MessageListener>();
Subscription subscription = new Subscription();
subscription.setTopic(mqConfig.getTopic());
subscription.set);
subscriptionTable.put(subscription, messageListener);
//订阅多个topic如上面设置
consumerBean.setSubscriptionTable(subscriptionTable);
System.err.println("订阅成功!");
return consumerBean;
}
}
7、定时延时MQ消息监听消费
/**
* @Description: 定时/延时MQ消息监听消费
* @Author: zhouhong
* @Create: 2021-08-03 09:16
**/
@Component
public class MqTimeMessageListener implements MessageListener {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public Action consume(Message message, ConsumeContext context) {
System.err.println("收到消息啦!!");
logger.info("接收到MQ消息 -- Topic:{}, tag:{},msgId:{} , Key:{}, body:{}",
message.getTopic(),message.getTag(),message.getMsgID(),message.getKey(),new String(message.getBody()));
try {
String msgTag = message.getTag(); // 消息类型
String msgKey = message.getKey(); // 业务唯一id
switch (msgTag) {
case "XXXX":
// TODO 具体业务实现,比如发消息等操作
System.err.println("推送成功!!!!");
break;
}
return Action.CommitMessage;
} catch (Exception e) {
logger.error("消费MQ消息失败! msgId:" + message.getMsgID()+"----ExceptionMsg:"+e.getMessage());
//消费失败,告知服务器稍后再投递这条消息,继续消费其他消息
return Action.ReconsumeLater;
}
}
}
8、封装一个发延时/定时消息的工具类
/**
* @Description: MQ发送消息助手
* @Author: zhouhong
* @Create: 2021-08-03 09:06
**/
@Component
public class ProducerUtil {
private Logger logger = LoggerFactory.getLogger(ProducerUtil.class);
@Autowired
private PatrolMqConfig config;
@Resource(name = "ConsoleProducer")
ProducerBean producerBean;
public SendResult sendTimeMsg(String msgTag,byte[] messageBody,String msgKey,long delayTime) {
Message msg = new Message(config.getTopic(),msgTag,msgKey,messageBody);
msg.setStartDeliverTime(delayTime);
return this.send(msg,Boolean.FALSE);
}
/**
* 普通消息发送发放
* @param msg 消息
* @param isOneWay 是否单向发送
*/
private SendResult send(Message msg,Boolean isOneWay) {
try {
if(isOneWay) {
//由于在 oneway 方式发送消息时没有请求应答处理,一旦出现消息发送失败,则会因为没有重试而导致数据丢失。
//若数据不可丢,建议选用同步或异步发送方式。
producerBean.sendOneway(msg);
success(msg, "单向消息MsgId不返回");
return null;
}else {
//可靠同步发送
SendResult sendResult = producerBean.send(msg);
//获取发送结果,不抛异常即发送成功
if (sendResult != null) {
success(msg, sendResult.getMessageId());
return sendResult;
}else {
error(msg,null);
return null;
}
}
} catch (Exception e) {
error(msg,e);
return null;
}
}
private ExecutorService threads = Executors.newFixedThreadPool(3);
private void error(Message msg,Exception e) {
logger.error("发送MQ消息失败-- Topic:{}, Key:{}, tag:{}, body:{}"
,msg.getTopic(),msg.getKey(),msg.getTag(),new String(msg.getBody()));
logger.error("errorMsg --- {}",e.getMessage());
}
private void success(Message msg,String messageId) {
logger.info("发送MQ消息成功 -- Topic:{} ,msgId:{} , Key:{}, tag:{}, body:{}"
,msg.getTopic(),messageId,msg.getKey(),msg.getTag(),new String(msg.getBody()));
}
}
9、接口测试(10000表示延迟10秒,可以根据自己的业务计算出)
// 测试MQ延时
@Autowired
ProducerUtil producerUtil;
@PostMapping("/patrolTaskTemp/mqtest")
public void mqTime(){
producerUtil.sendTimeMsg(
"SMARTPATROL",
"你好鸭!!!".getBytes(),
"红红火火恍恍惚惚!!",
System.currentTimeMillis() + 10000
);
}
10、结果
2021-08-04 22:07:12.677  INFO 17548 --- [nio-8498-exec-2] c.h.i.i.s.m.common.util.ProducerUtil     : 发送MQ消息成功 -- Topic:TID_COMMON ,msgId:C0A80168448C2F0E140B14322CB30000 , Key:红红火火恍恍惚惚!!, tag:SMARTPATROL, body:你好鸭!!!
收到消息啦!!
推送成功!!!!
2021-08-04 22:07:22.179 INFO 17548 --- [MessageThread_1] c.h.i.i.s.m.m.t.n.MqTimeMessageListener : 接收到MQ消息 -- Topic:TID_COMMON, tag:SMARTPATROL,msgId:0b17f2e71ebd1b054c2c156f6d1d1655 , Key:红红火火恍恍惚惚!!, body:你好鸭!!!

阿里云RocketMQ定时/延迟消息队列实现的更多相关文章

  1. SpringBoot - 集成RocketMQ实现延迟消息队列

    目录 前言 环境 具体实现 前言 RocketMQ是阿里巴巴在2012年开源的分布式消息中间件,记录下SpringBoot整合RocketMQ的方式,RocketMQ的安装可以查看:Windows下安 ...

  2. [原创]阿里云RocketMQ踩过的哪些坑

    由于公司的最近开始使用RocketMQ来做支付业务处理, 便开启了学习阿里云RocketMQ的学习与实践之路, 其中踩了不少的坑, 大部份是由于没有仔细查看阿里云的技术文档而踩的坑. 但是有一个非常大 ...

  3. 阿里云RocketMQ的生产者简单实现

    // MQ的应用场景有比如 订单变更消息可以通过产生这个事件的地方(比如前端调用后端的接口post一个订单,那么就是在这个mapping方法里做一个生产者[不过最好通过aop来实现,不然n多个接口都要 ...

  4. 基于redis的延迟消息队列设计

    需求背景 用户下订单成功之后隔20分钟给用户发送上门服务通知短信 订单完成一个小时之后通知用户对上门服务进行评价 业务执行失败之后隔10分钟重试一次 类似的场景比较多 简单的处理方式就是使用定时任务 ...

  5. 基于redis的延迟消息队列设计(转)

    需求背景 用户下订单成功之后隔20分钟给用户发送上门服务通知短信 订单完成一个小时之后通知用户对上门服务进行评价 业务执行失败之后隔10分钟重试一次 类似的场景比较多 简单的处理方式就是使用定时任务 ...

  6. Delayer 基于 Redis 的延迟消息队列中间件

    Delayer 基于 Redis 的延迟消息队列中间件,采用 Golang 开发,支持 PHP.Golang 等多种语言客户端. 参考 有赞延迟队列设计 中的部分设计,优化后实现. 项目链接:http ...

  7. 滴滴出行基于RocketMQ构建企业级消息队列服务的实践

    小结: 1. https://mp.weixin.qq.com/s/v6NM3UgX-qTI7yO1QPCJrw 滴滴出行基于RocketMQ构建企业级消息队列服务的实践 原创: 江海挺 阿里巴巴中间 ...

  8. rabbitmq的延迟消息队列实现

    第一部分:延迟消息的实现原理和知识点 使用RabbitMQ来实现延迟任务必须先了解RabbitMQ的两个概念:消息的TTL和死信Exchange,通过这两者的组合来实现上述需求. 消息的TTL(Tim ...

  9. Spring boot实战项目整合阿里云RocketMQ (非开源版)消息队列实现发送普通消息,延时消息 --附代码

    一.为什么选择RocketMQ消息队列? 首先RocketMQ是阿里巴巴自研出来的,也已开源.其性能和稳定性从双11就能看出来,借用阿里的一句官方介绍:历年双 11 购物狂欢节零点千万级 TPS.万亿 ...

随机推荐

  1. SCP,SSH应用

  2. Scala语言笔记 - 第三篇(容器方法篇)

    Scala语言笔记 - 第三篇(容器方法篇) 目录 Scala语言笔记 - 第三篇(容器方法篇) map和flapMap方法: ​ 最近研究了下scala语言,这个语言最强大的就是它强大的函数式编程( ...

  3. Springboot集成Spring Security实现JWT认证

    我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 简介 Spring Security作为成熟且强大的安全框架,得到许多大厂的青睐.而作为前后端分离的SSO方案,JWT ...

  4. lvm脚本

    # 2 SWAP 20GB SWAP # 3 /usr/sap 50GB /usr/sap # 4 Shared 1 x 512 GiB /hana/shared # 5.6 Log 2 x 300 ...

  5. 12、elk的使用(1)

    12.0.架构图: 服务器名称 ip地址 controller-node1(主) 172.16.1.90 slave-node1(从) 172.16.1.91 12.1.elk介绍: (1)ELK是三 ...

  6. 一小时搞懂Mysql锁机制

    内容概述: 我们知道,数据也是一种供许多用户共享访问的资源.如何保证数据并发访问的一致性.有效性,是所有数据库必须解决的一个问题,锁的冲突也是影响数据库并发访问性能的一个重要因素.从这一角度来说,锁对 ...

  7. Java:Java控制台输出保存进文件

    前言 实现在控制台输出.并且把输出保存进文件 实现 您要在两个流中写入数据,请尝试使用OutputStream中的TeeOutputStream对象. 一.在maven的pom文件中引入jar包. & ...

  8. XCTF IgniteMe

    一.查壳 结论: 1.用vc++编译的. 2.无壳,毕竟是一分的题 二.点击运行,发现不是爆破,而是找出注册机,汇编功力还在提升中,只能拖入ida来静态调试了 具体的见注释: 二.1点击进入关键函数 ...

  9. buu crackRTF

    一.无壳,拖入ida,静态编译一下 整体逻辑还是很清晰,这里我的盲区是那个加密函数,是md5,没看出来,内存地址看错了,之前用黑盒动调一下,发现猜不出来,看某位wp发现有的老哥,直接sha1爆破的,狠 ...

  10. 干掉 Postman?测试接口直接生成API文档,这个工具贼好用

    大家好,我是小富~ 前几天粉丝群有小伙伴问,有啥好用的API文档工具推荐,无意间发现了一款工具,这里马不停蹄的来给大家分享一下. ShowDoc一个非常适合团队的在线API文档工具,也支持用docke ...