什么是MQ:
MQ全称为Message Queue,即消息队列.是一种提供消息队列服务的中间件,也称为消息中间件,是一套提供了消息生 产、存储、消费全过程的软件系统,遵循FIFO(先进先出,后进后出)原则。
MQ作用:
在一些特点的场景中,我们需要执行两个步骤,比如地铁,我们要先刷码出站,过几分钟之后才会扣款,上下班高峰期使用天府通刷码的人非常多,意味并发量很高,一个出站请求到后台需要做费用计算,费用结算。由于并发很高,并且费用结算和积分等业务本来就耗时,况且支付服务也不一定能承担那么大的请求量.如果我们等到扣款之后才能通行,这会使用户等待一段时间,后续的人也要刷码出站,会导致地铁站堆集大量人员.
当服务器线程耗尽,后续请求会等待变慢,再加上高并发请求就会导致后续请求越来越慢,请求长时间等待,导致大量请求超时。并发太高,可能会导致服务器的内存上升,CPU使用率急速上升,甚至导致服务器宕掉。
我们可以使用MQ消峰,用户刷码直接返回结果出站,MQ记录数据,稍后再通过MQ计算费用,结算费用,这样使得用户体验度大幅提高
MQ的使用场景(重要)
-
限流削峰
MQ可以将系统的超量请求暂存其中,以便系统后期可以慢慢进行处理,从而避免了请求的丢失或系统 被压垮。
-
异步&解耦
上游系统对下游系统的调用若为同步调用,则会大大降低系统的吞吐量与并发度,且系统耦合度太高。 而异步调用则会解决这些问题。所以两层之间若要实现由同步到异步的转化,一般性做法就是,在这两层间添加一个MQ层。 即使消费者挂掉也不影响生产者工作,只要把消息放入队列即可,消费者重启后自己消费即可。
-
数据收集
分布式系统会产生海量级数据流,如:业务日志、监控数据、用户行为等。针对这些数据流进行实时或 批量采集汇总,然后对这些数据流进行大数据分析,这是当前互联网平台的必备技术。通过MQ完成此 类数据收集是最好的选择。
-
大数据处理
比如我们的平台向“三方平台”获取数据,一次请求了大量数据回来要进行处理,由于数据较多处理不过来,那么就可以放入MQ,再创建一些消费者进行数据处理即可。
如下情况不太适合MQ
-
小项目,体量不大,并发量低的使用MQ会太过笨重 - 你可以考虑使用Redis做一个消息队列。
-
对数据的一致性有要求(强一致性)的的场景不适合使用MQ,因为MQ是异步的。
MQ的优点:
1.提高系统响应速度
因为MQ是采用异步处理,不需要像同步一样等待线程处理完之后才能返回,降低了数据的响应时间
2.提高系统稳定性
降低了峰值,减少了高并发的风险,提高了系统的稳定性.即便是系统挂掉了也没问题,操作内容放到消息队列不丢失,后续重新消费者一样能消费做业务处理
3.采用FIFO
先进先出,能够保证消息按照添加的数据被消费。
MQ常见协议
-
AMQP协议 AMQP是一套公开的消息队列协议,最早在2003年被提出,它旨在从协议层定义消息通信数据的标准格式, 为的就是解决MQ市场上协议不统一的问题。
基于此协议的客户端与消息中间件可传递 消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制
,RabbitMQ就是遵循AMQP标准协议开发的MQ服务。 官方:Home | AMQP -
JMS协议 JMS是Java消息服务,
是java提供的一套消息服务API标准
,其目的是为所有的java应用程序提供统一的消息通信的标准,类似java的 jdbc,只要遵循jms标准的应用程序之间都可以进行消息通信。它和AMQP有什么 不同,jms是java语言专属的消 息服务标准,它是在api层定义标准,并且只能用于java应用;而AMQP是在协议层定义的标准,是跨语言的 。 -
MQTT MQTT,Message Queuing Telemetry Transport(消息队列遥测传输),是IBM开发的一个即时通讯协 议,是一种二进制协议,主要用于服务器和低功耗IoT(
物联网
)设备间的通信。该协议支持所有平 台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和致动器的通信协议。 RabbitMQ通 过插件可以支持该协议。
RocketMQ介绍
RocketMQ是什么
RocketMQ是一个统一消息引擎、轻量级数据处理平台。
RocketMQ是⼀款阿⾥巴巴开源的消息中间件,双十一承载了万亿级消息的流转,2016年11⽉,阿⾥巴巴向 Apache 软件基⾦会捐赠 RocketMQ,成为 Apache 孵化项⽬,2017 年 9 ⽉ ,Apache 宣布 RocketMQ孵化成为 Apache 顶级项⽬(TLP )成为国内⾸个互联⽹中间件在 Apache 上的顶级项⽬。
RocketMQ特征(了解)
-
支持集群模型、负载均衡、水平扩展能力
-
亿级别消息堆积能力
-
采用零拷贝的原理,顺序写盘,随机读
-
底层通信框架采用Netty NIO
-
NameServer代替Zookeeper,实现服务寻址和服务协调
-
消息失败重试机制、消息可查询
-
强调集群无单点,可扩展,任意一点高可用,水平可扩展
-
经过多次双十一的考验
RocketMQ的集群架构如下
RocketMQ主要由 Producer、Broker、Consumer、NameServer 四部分组成,其中Producer 负责生产消息,Consumer 负责消费消息,Broker 负责存储消息,NameServer 负责管理Broker的地址。Broker 在实际部署过程中对应一台服务器。
RocketMQ工作原理
如何使用RocketMQ:
第一步:下载RocketMQ,配置环境变量
下载地址:http://rocketmq.apache.org/release_notes/release-notes-4.2.0/
第二步:导入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.8.0</version>
</dependency>
第三步:创建生产者,消费者
public class Producer {
public static void main(String[] args) throws Exception {
//1.创建生产者组
DefaultMQProducer producer = new DefaultMQProducer("producer-hello");
//2.设置NameServer地址
producer.setNamesrvAddr("127.0.0.1:9876");
//3.启动producer实例
producer.start();
//4.创建消息
Message message = new Message("log-topic", "info-tag", "这是一个info信息".getBytes(RemotingHelper.DEFAULT_CHARSET));
//5.发送消息
SendResult result = producer.send(message);
//6.关闭producer实例
System.out.println("发送完毕,结果: "+result);
}
}
-
DefaultMQProducer : MQ生产者 , 可以指定组名 producerGroupName
-
producer.setNamesrvAddr : 指定Name Server地址,用作Brocker发现。注意IP和启动name server服务时指定的IP保持一致。
-
producer.start() : 启动生产者
-
new Message("topic_log","tags_error",("我是消息"+i).getBytes()) :消息,参数为:topic,tags,内容
-
producer.send(message) : 发送消息
-
SendResult :发送结果,其中包含
-
sendStatus=SEND_OK :发送状态
-
msgId :producer 创建的消息ID
-
offsetMsgId :Brocker创建的消息ID
-
messageQueue :消息存储的队列
-
producer.shutdown():关闭生产者
-
public class Consumer {
public static void main(String[] args) throws MQClientException {
//1.创建消费者组
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer-hello");
//2.设置NameServer地址
consumer.setNamesrvAddr("127.0.0.1:9876");
//3.订阅topic,指定tag标签
consumer.subscribe("log-topic","info-tag");
//4.注册消息监听器
consumer.registerMessageListener(new MessageListenerConcurrently(){
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
System.out.printf("%s 接收到新的消息: %n", Thread.currentThread().getName());
msgs.stream().forEach(messageExt -> {
String body = null;
try {
body = new String(messageExt.getBody(), RemotingHelper.DEFAULT_CHARSET);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println(body);
});
//失败消费,稍后尝试消费,会进行多次重试
//return ConsumeConcurrentlyStatus.RECONSUME_LATER;
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//5.启动消费者
consumer.start();
System.out.println("消费者启动...");
}
}
-
DefaultMQPushConsumer :消费者 , 可以指定 consumerGroupName
-
consumer.setNamesrvAddr : 设置name server 地址
-
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET) :从什么位置开始消费
-
consumer.subscribe("topic_log", "tags_error") :订阅某个topic下的某个tags的消息
-
consumer.registerMessageListener :注册消息监听器,拿到消息后,进行消息处理。
-
ConsumeConcurrentlyStatus :消费者消费结果状态,ConsumeConcurrentlyStatus.CONSUME_SUCCESS代表成功,ConsumeConcurrentlyStatus.RECONSUME_LATER代表消费失败,稍后重试,会进行多次重试
RocketMQ 核心概念
Producer 生产者
RocketMQ提供多种发送方式,同步发送、异步发送、顺序发送、单向发送
。同步和异步方式均需要Broker返回确认信息,单向发送不需要。 Producer会使用一定的算法选择把消息发送到哪个master的某个queue中.
Consumer 消费者
Consumer 支持两种消费形式:拉取式消费、推动式消费
。(主动,被动),RocketMQ中的消息消费者都是以消费者组(Consumer Group)的形式出现的。消费者组是同一类消费者的集合,这类Consumer消费的是同一个Topic类型的消息,不同的 Consumer Group可以消费同一个Topic。 一个Consumer Group内的Consumer可以消费多个Topic的消息。
Topic 消息主题
Topic表示一类消息的集合,每个topic主题包含若干条message消息,
每条message消息只能属于一个topic主题,Topic是RocketMQ进行消息订阅的基本单位。
Message 消息
消息是指,消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。
Tag 标签
为消息设置的标志,用于同一主题下区分不同类型的消息
.Topic是消息的一级分类,Tag是消息的二级分类
MessageQueue队列
一个Topic中可以包含多个Queue
,一 个Topic的Queue也被称为一个Topic中消息的分区(Partition)。 在一个Consumer Group内,一个Queue最多只能分配给一个Consumer
,一个Cosumer可以分配得到多个Queue。这样的分配规则,每个Queue只有一个消费者,可以避免消费过程中的多线程处理和资源锁定,有效提高各Consumer消费的并行度和处理效率。
SpringBoot整合RocketMQ
1.导入依赖
<parent>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<!-- <version>2.0.4</version> -->
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
2.配置启动类
@SpringBootApplication
public class ApplicationStart {
public static void main(String[] args) {
SpringApplication.run(ApplicationStart.class);
}
}
3.配置文件
rocketmq:
name-server: 127.0.0.1:9876
#生产者配置
producer:
#生产者组名字
group: "service-producer"
# 消息最大长度 默认 1024 * 1024 * 4 (4M)
max-message-size: 4194304
# 发送消息超时时间,默认 3000
send-message-timeout: 3000
# 发送消息失败重试次数,默认2
retry-times-when-send-failed: 2
# 异步消息发送失败重试次数
retry-times-when-send-async-failed: 2
#达到 4096 ,进行消息压缩
compress-message-body-threshold: 4096
consumer:
#消费者名字
group: "service-consumer"
#批量拉取消息数量
pull-batch-size: 10
message-model: CLUSTERING
selector-expression: "*"
同步发送消息
@Autowired
private RocketMQTemplate rocketMQTemplate;
// 同步发送信息
@RequestMapping("/order/{msg}")
public String senMessage(@PathVariable("msg") String msg) {
// 构建消息对象
Message<String> message = MessageBuilder.withPayload(msg).build();
rocketMQTemplate.send("topic-order:tags-order-pay", message);
//发送同步消息,2s发送不成功就超时
// SendResult sendResult = rocketMQTemplate.syncSend("topic-order:tags-order-pay", message, 2000);
return "消息发送成功";
}
异步发送消息(包含延迟消息)
@Autowired
private RocketMQTemplate rocketMQTemplate;
// 异步发送信息
@RequestMapping("/order/async/{msg}")
public String sendAsyncMessage(@PathVariable("msg") String msg) {
// 创建消息对象,发送消息需要消息对象
Message<String> message = MessageBuilder.withPayload(msg).build();
// 发送延迟消息
SendResult sendResult = rocketMQTemplate.syncSend("topic-order:tags-order-pay", message, 2000, 3);//延迟消息
// 异步发送消息
// rocketMQTemplate.asyncSend("topic-order:tags-order-pay", message, new SendCallback() {
// @Override
// public void onSuccess(SendResult sendResult) {
// System.out.println(sendResult);
// System.out.println("发送成功");
// }
//
// @Override
// public void onException(Throwable e) {
// System.out.println("发送失败");
// e.printStackTrace();
// }
// });
return "发出去了";
}
我这里 指定了发送的主题(topic)和标签(tags),固定语法("topic:tags")
2000是指等待时间超过2000毫秒是超时等待
3是消息的延迟等级也就是10秒
发送延迟消息是有一个等级来定义延迟时间的,一共有19个等级,分别对于以下时间
messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h 2d 这个时候总共就有19个level
发送事务消息
如果业务只涉及到一个数据库的写操作,我们只需要保证这一个事物的提交和回滚,这种事务管理叫传统事物或本地事务,如果业务涉及到多个数据库(多个服务)的写操作,我们需要保证多个数据库同时提交或回滚,这种跨多个数据库的事务操作叫分布式事务。
使用RocketMQ发送事务消息需要用到分布式事
-
编写本地事务检查监听TransactionListener ,一是执行本地事务逻辑,二是返回本地事务执行状态
-
发消息时生产者需要设置producer.setTransactionListener 事务监听
创建事务监听器
// 发送消息事务监听器,监听本地事务是否执行成功,如果成功就表示消息可以被消费,如果失败,则删除MQ中的信息,确保事务消息一致性
@RocketMQTransactionListener(txProducerGroup = "order-tx-listener") // 声明当前类是事务监听器类,txProducerGroup是该事务监听器的名字
@Slf4j
public class CourserOrderTransactionListener implements RocketMQLocalTransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(org.springframework.messaging.Message message, Object o) {
byte[] messageBody = (byte[])message.getPayload();
String msg = new String(messageBody);
log.info("打印一下表示持久化成功{}",msg); // 这里应该做持久化操作,如果持久化成功就返回COMMIT,失败就返回ROLLBACK
return RocketMQLocalTransactionState.COMMIT;
}
// 这里表示不知道消息返回是否成功或者失败,走检查路线
@Override
public RocketMQLocalTransactionState checkLocalTransaction(org.springframework.messaging.Message message) {
byte[] messageExtBody = (byte[])message.getPayload();
String msg = new String(messageExtBody);
log.info("打印一下表示持久化成功{}",msg);
return RocketMQLocalTransactionState.COMMIT;
}
}
发送事务消息
// 事务消息
@RequestMapping("/order/tx/{msg}")
public String sendTransactionMessage(@PathVariable("msg") String msg){
// 创建消息对象,发送消息需要消息对象
Message<String> message = MessageBuilder.withPayload(msg).build();
TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(
"order-tx-listener", // 事务监听器的名字
"topic-order:tags-order-pay", // 目的地,发送到哪个主题下的哪个标签
message, // 发送的信息
"arg");//扩展参数 Object; 该参数会传递给事务监听器的arg
LocalTransactionState localTransactionState = transactionSendResult.getLocalTransactionState();
SendStatus sendStatus = transactionSendResult.getSendStatus();
log.info("本地事务状态:{}, 消息发送状态:{}",localTransactionState,sendStatus);
return "OK";
}