序言
在日常开发中,有很多时候都会使用异步的方式去请求方法,以提高代码的执行效率。比如用户购买某件商品,然后需要插入购买记录,物品-1,微信公众号消息推送之类,流程会很多,也会需要很多时间,而SpringBoot为异步提供了更加简单的使用方式,通过两个注解即可实现异步调用方法。
SpringBoot为我们提供了很多使用便利,我们只需要一个@Enablexxx注解 + 功能注解,便能轻松简单的实现功能,比如异步调用和定时任务。
**假定使用场景:**用户购买完一件商品,需要短信通知 + 微信消息推送(不考虑失败场景),不需要返回值。
利用@Async开启异步化(不能在同一个类下)
但是Springboot的异步化有缺点
假如异步化里面的内容很复杂,导致线程一直占用
一般来说会集成一个线程池,超过多少的话,则还是会变成同步
这时候就需要引入我们MQ的消息队列
MQ就是消息队列,和redis一样是个中间件,需要单独安装
常用的MQ有rocketmq,kafka,rabbitmq等
MQ发送和接收 为二台服务器,可以将二个逻辑放在二台服务器上
客户端(发送方)往一个主题里面放松一个消息,服务端(接收端/消费端)可以去监听这个主题
生产者(Producer)
生产者:也称为消息发布者,负责生产并发送消息至RocketMQ。
消费者(Consumer)
消费者:也称为消息订阅者,负责从RocketMQ接收并消费消息。
消息(Message)
消息:生产或消费的数据,对于RocketMQ来说,消息就是字节数组。
主机(Broker)
RocketMQ的核心,用于暂存和传输消息。
RocketMQ的概念模型
分组(Group)
生产者:标识发送同一类消息的Producer,通常发送逻辑一致。发送普通消息的时候,仅标识使用,并无特别用处。主要作用用于事务消息,事务消息中如果某条发送某条消息的producer-A宕机,使得事务消息一直处于PREPARED状态并超时,则broker会回查同一个group的其它producer,确认这条消息应该commit还是rollback。
消费者:标识一类Consumer的集合名称,这类Consumer通常消费一类消息,且消费逻辑一致。同一个Consumer Group下的各个实例将共同消费topic的消息,起到负载均衡的作用。
消费进度以Consumer Group为粒度管理,不同Consumer Group之间消费进度彼此不受影响,即消息A被Consumer Group1消费过,也会再给Consumer Group2消费。
主题(Topic)
标识一类消息的逻辑名字,消息的逻辑管理单位。无论消息生产还是消费,都需要指定Topic。
区分消息的种类;一个发送者可以发送消息给一个或者多个Topic;一个消息的接收者可以订阅一个或者多个Topic消息。
标签(Tag)
RocketMQ支持给在发送的时候给topic打tag,同一个topic的消息虽然逻辑管理是一样的。但是消费topic的时候,如果你消费订阅的时候指定的是tagA,那么tagB的消息将不会投递。
消息队列(Message Queue)
简称Queue或Q。消息物理管理单位。一个Topic将有若干个Q。若一个Topic创建在不同的Broker,则不同的broker上都有若干Q,消息将物理地存储落在不同Broker结点上,具有水平扩展的能力。
无论生产者还是消费者,实际的生产和消费都是针对Q级别。例如Producer发送消息的时候,会预先选择(默认轮询)好该Topic下面的某一条Q发送;Consumer消费的时候也会负载均衡地分配若干个Q,只拉取对应Q的消息。
每一条message queue均对应一个文件,这个文件存储了实际消息的索引信息。并且即使文件被删除,也能通过实际纯粹的消息文件(commit log)恢复回来。
偏移量(Offset)
RocketMQ中,有很多offset的概念。一般我们只关心暴露到客户端的offset。不指定的话,就是指Message Queue下面的offset。
Message queue是无限长的数组。一条消息进来下标就会涨1,而这个数组的下标就是offset,Message queue中的max offset表示消息的最大offset。
Consumer offset可以理解为标记Consumer Group在一条逻辑Message Queue上,消息消费到哪里即消费进度。但从源码上看,这个数值是消费过的最新消费的消息offset+1,即实际上表示的是下次拉取的offset位置
一.下载并安装ROCKETMQ,并启动
二.导入依赖
RocketMQ
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
三.注入RocketMQTemplate
@Resource
private RocketMQTemplate rocketMQTemplate;
设置主题为VOTE_TOPIC
// 推送消息
Doc docDb = docMapper.selectByPrimaryKey(id);
String logId = MDC.get("LOG_ID");
// wsService.sendInfo("【" + docDb.getName() + "】被点赞!", logId);
rocketMQTemplate.convertAndSend("VOTE_TOPIC", "【" + docDb.getName() + "】被点赞!");
四.设置消息端
通过注解@RocketMQMessageListener(consumerGroup = "default", topic = "VOTE_TOPIC")
来收到主题为“VOTE_TOPIC”的消息
package com.jiawa.wiki.rocketmq;
import com.jiawa.wiki.websocket.WebSocketServer;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
@RocketMQMessageListener(consumerGroup = "default", topic = "VOTE_TOPIC")
public class VoteTopicConsumer implements RocketMQListener<MessageExt> {
private static final Logger LOG = LoggerFactory.getLogger(VoteTopicConsumer.class);
@Resource
public WebSocketServer webSocketServer;
@Override
public void onMessage(MessageExt messageExt) {
byte[] body = messageExt.getBody();
LOG.info("ROCKETMQ收到消息:{}", new String(body));
webSocketServer.sendInfo(new String(body));
}
}
成功获取消息!!