一:MQ
1.简介
MQ全称为Message Queue,即消息队列 ,是一种提供消息队列服务的中间件,也称为消息中间件,是一套提供了消息生 产、存储、消费全过程的软件系统,遵循FIFO原则。
2:MQ的使用场景
1.限流削峰
2.异步&解耦
3.数据收集 (日志,监控 )
4.大数据处理
好处:提高系统响应速度 提高系统稳定性 排序保证 FIFO
不适合:
-
小项目,体量不大,并发量低的使用MQ会太过笨重 - 你可以考虑使用Redis做一个消息队列。
-
对数据的一致性有要求(强一致性)的的场景不适合使用MQ,因为MQ是异步的。
3:常见MQ产品
- ActiveMQ
- RabbitMQ
- Kafka
- RocketMQ (Spring Cloud Alibaba 提倡)
- Redis
- ZeroMQ
二:RocketMQ
1.下载RocketMQ
2. 解压
3.配置环境变量
ROCKETMQ_HOME E:\software\rocketmq-all-4.2.0-bin-release
4. 启动MQ 的NameServer
bin目录下进入cmd输入 start mqnamesrv.cmd
,启动NameServer。
5. 启动Broker
进入至MQ文件夹\bin
下,修改Bean目录下的 runbroker.cmd
中JVM占用内存大小
bin目录下进入cmd输入 start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
,启动Broker。
6. 下载安装启动 RocketMQ可视化管理插件
6.1 下载
Releases · apache/rocketmq-externals · GitHub
6.2修改配置
解压后,修改配置:src/main/resource/application.properties ,这里需要指向Name Server 的地址和端口 rocketmq.config.namesrvAddr=localhost:9876
6.3打包插件
回到安装目录(pom.xml所在目录),执行: mvn clean package -Dmaven.test.skip=true
,然后会在target目录生成打包后的jar文件
6.4启动插件
进入 target 目录,执行 java -jar rocketmq-console-ng-1.0.0.jar
, 访问 http://localhost:8080
三:RocketMQ 原生jar入门
1. 导入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.8.0</version>
</dependency>
2.生产者
public class Producer {
public static void main(String[] args) throws Exception {
//1.创建生产者
DefaultMQProducer producer = new DefaultMQProducer("hello-producer");
//2. 设置nameserver地址
producer.setNamesrvAddr("localhost:9876");
// 启动生产者
producer.start();
// 创建消息
Message message = new Message("order-topic","order-flow-tags","物流消息".getBytes("utf-8"));
// 1.同步发送消息
SendResult sendResult = producer.send(message);
// 2.异步
// producer.send(message, new SendCallback() {
// @Override
// public void onSuccess(SendResult sendResult) {
// System.out.println("发送成功");
// }
//
// @Override
// public void onException(Throwable throwable) {
// System.out.println("发送失败");
// }
// });
// 3.单向
// producer.sendOneway(message);
System.out.println("发送状态: " + sendResult);
}
}
3. 消费者
public class Consumer {
public static void main(String[] args) throws Exception {
// 创建消费着
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("hello-consumer");
// 设置nameServer地址
consumer.setNamesrvAddr("localhost:9876");
// 订阅
consumer.subscribe("order-topic","order-flow-tags");
// 注册监听器
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, // 队列中可能有多个消息
ConsumeConcurrentlyContext context) {
list.forEach(messageExt ->{
// 拿到消息
byte[] body = messageExt.getBody();
String msg = new String(body, Charset.defaultCharset());
System.out.println("拿到:" + msg);
});
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; // 消费成功
// return ConsumeConcurrentlyStatus.RECONSUME_LATER; // 消费 稍后重试
}
});
// 启动消费者
consumer.start();
}
}
4. 先执行生产者main方法 将消息存到rocket,再启动消费者
四: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.启动类
3.application.yaml
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: "*"
server:
port: 8888
4. 生产者controller
@RestController
public class ProducerController {
@Autowired
private RocketMQTemplate rocketMQTemplate;
// 同步发送
@GetMapping("/send/{message}")
public String send(@PathVariable("message") String data){
Message<String> message = MessageBuilder.withPayload(data).build();
rocketMQTemplate.send("order-topic:order-flow-tags", message);
return "发送成功";
}
// 同步发送
@GetMapping("/syncSend/{message}")
public String syncSend(@PathVariable("message") String data){
Message<String> message = MessageBuilder.withPayload(data).build();
// 有返回值,有超时时间
SendResult sendResult = rocketMQTemplate.syncSend("order-topic:order-flow-tags", message,2000);
if (sendResult.getSendStatus() == SendStatus.SEND_OK){
return "发送成功";
}
return "发送失败";
}
// 异步发送
@GetMapping("/asyncSend/{message}")
public void asyncSend(@PathVariable("message") String data){
Message<String> message = MessageBuilder.withPayload(data).build();
// asyncSend 异步发送
rocketMQTemplate.asyncSend("order-topic:order-flow-tags", message, new SendCallback() { // 回调
@Override
public void onSuccess(SendResult sendResult) {
System.out.println(sendResult);
System.out.println("发送成功");
}
@Override
public void onException(Throwable throwable) {
System.out.println("发送失败");
}
});
}
// 单向发送
@GetMapping("/sendOneWay/{message}")
public void sendOneWay(@PathVariable("message") String data) {
Message<String> message = MessageBuilder.withPayload(data).build();
rocketMQTemplate.sendOneWay("order-topic:order-flow-tags",message);
System.out.println("发送成功");
}
// 同步延时发送
@GetMapping("/syncSendTime/{message}")
public String syncSendTime(@PathVariable("message") String data){
Message<String> message = MessageBuilder.withPayload(data).build();
// 有返回值,有超时时间
SendResult sendResult = rocketMQTemplate.syncSend("order-topic:order-flow-tags", message,2000,3);
if (sendResult.getSendStatus() == SendStatus.SEND_OK){
return "发送成功";
}
return "发送失败";
}
}
5.消费者 交给spring管理
@Component
@RocketMQMessageListener(
//消费者的名字
consumerGroup = "service-consumer",
//主题
topic = "order-topic",
//tags
selectorExpression = "order-flow-tags",
//消息消费模式:默认是CLUSTERING集群,还支持BROADCASTING广播
messageModel = MessageModel.CLUSTERING)
public class TestConsumer implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
if (messageExt == null || messageExt.getBody().length == 0){
return;
}
System.out.println(new String(messageExt.getBody(), Charset.defaultCharset()));
}
}
五:面试题
MQ有哪些使用场景
-
异步任务处理:使用MQ可以将任务放入消息队列中,然后由消费者异步处理这些任务。这在需要高吞吐量和低延迟的场景中非常有用,例如大规模数据处理、后台任务处理和异步消息通知。
-
解耦系统组件:通过引入MQ,系统中的不同组件可以通过消息的方式进行通信,实现解耦。这使得系统更加灵活可扩展,并允许组件在不影响其他组件的情况下独立地进行升级和修改。
-
广播和订阅模式:MQ可以用来实现发布/订阅模式,其中生产者将消息发布到某个主题,而订阅者可以选择订阅感兴趣的主题并接收相关消息。这在事件驱动的系统中非常有用,例如实时数据分析、日志收集和系统监控。
-
负载均衡:使用MQ可以实现负载均衡,将请求分发到多个消费者之间。生产者将请求发布到队列,然后消费者按照一定的规则从队列中获取任务并处理。这样可以有效地处理高并发的请求,提高系统的处理能力。
-
消息持久化:MQ通常提供消息持久化功能,即将消息存储在持久化存储介质(如磁盘)上,以防止消息丢失。这对于一些重要的消息和业务场景非常关键,例如订单处理和交易记录。
说下一下RokcetMQ的架构
RocketMQ是一个分布式消息中间件,以下是它的基本架构:
-
Nameserver(命名服务器):Nameserver是RocketMQ的元数据管理组件。它保存了分布式系统中Topic(主题)的路由信息,包括生产者和消费者的地址。Nameserver还负责提供轻量级的服务发现和动态路由功能。
-
Broker(代理服务器):Broker是消息的存储和传输引擎。每个Broker负责管理一部分的Topic或消息队列。它负责接收来自生产者的消息,存储在磁盘上,并向消费者传输消息。如果一个Topic具有多个分区,那么每个分区可以由不同的Broker管理。
-
Producer(生产者):Producer是消息的发送者。它将消息发送到Topic,然后由Broker负责存储和传输。生产者可以按照指定的方式发送消息,如同步发送、异步发送或者定时发送。
-
Consumer(消费者):Consumer是消息的接收者。它可以订阅一个或多个Topic,并从Broker接收消息。消费者可以按照不同的方式消费消息,如顺序消费、并发消费或者定时消费。
-
Message Queue(消息队列):每个Topic由多个消息队列组成,用于存储消息的队列。每个消息队列按照顺序存储消息,并且具有一个全局唯一的偏移量,用于标识消息在队列中的位置。
-
Message(消息):消息是RocketMQ传输的基本单元。它由主题(Topic)、标签(Tag)和消息体(Body)组成。消息还可以包含一些扩展属性,如键值对或者用户自定义的属性。
RocketMQ的架构支持高吞吐量、低延迟和高可靠性的消息传输。它具有水平扩展和容错性强的特点,适用于大规模分布式系统和高并发的消息通信场景。另外,RocketMQ还提供了丰富的监控和管理工具,便于运维人员对系统进行监控和调优。
RocketMQ的数据是存储到内存还是硬盘,如果是硬盘那么怎么保证速度
RocketMQ的数据是存储在硬盘上的,而不是存储在内存中。之所以选择硬盘存储,是因为硬盘可以提供更大的存储容量,适用于处理大量消息的场景。
为了提高RocketMQ在硬盘存储模式下的速度,它采用了以下优化策略:
-
零拷贝技术:RocketMQ使用零拷贝技术将消息从磁盘直接发送到网络,避免了数据在内存之间的复制操作,提高了传输效率和速度。
-
文件刷盘策略:RocketMQ采用了MAPPED_FILES的设计,将消息存储在内存映射文件中,可以通过强制刷盘和定时刷盘两种方式将数据从内存刷写到磁盘。强制刷盘会在消息发送时将数据直接刷写到磁盘,而定时刷盘会按照一定的时间间隔进行批量刷盘。这样可以在保证一定的持久化能力的同时,减少了频繁的磁盘写入操作,提高了性能。
-
顺序写入:RocketMQ对消息进行有序写入,即按照消息的顺序将其写入到磁盘上。这样可以减少磁盘寻址的时间,提高了写入速度。
-
索引文件的优化:RocketMQ使用了索引文件来加速消息的查找和检索。它将消息的偏移量和物理位置存储在索引文件中,当消费者需要拉取消息时,可以根据索引文件快速地找到消息在磁盘上的位置,提高了消息的读取速度。
这些优化策略结合起来,使得RocketMQ在硬盘存储模式下能够兼顾数据的持久性和传输速度,适用于高吞吐量的场景。同时,RocketMQ还支持将部分热点数据存储在内存中,以进一步提高读取的速度
发送消息有几种方式
在RocketMQ中,发送消息有以下几种方式:
1.1同步发送(Sync):这是最常用的发送方式。调用发送消息的API后,消息发送线程会阻塞等待消息发送完成,并返回发送结果,确保消息发送成功后再继续执行后续代码。同步发送适用于对消息可靠性要求较高的场景。
SendResult sendResult = producer.send(message);
1.2 有返回值,有超时时间
SendResult sendResult = rocketMQTemplate.syncSend("order-topic:order-flow-tags", message,2000);
1.3 同步延时发送
// 有返回值,有超时时间 等级是 3,对应的是 10S,也就是10s之后,消费者就可以收到该消息了。
SendResult sendResult = rocketMQTemplate.syncSend("order-topic:order-flow-tags", message,2000,3);
2.异步发送(Async):异步发送是指在发送消息后,发送线程不会等待消息发送结果,而是立即返回。发送者通过回调函数或Future对象来处理发送结果。这种方式适用于对响应时间敏感的场景。
producer.send(message, new SendCallback() {
public void onSuccess(SendResult sendResult) {
// 处理成功回调逻辑
}
public void onException(Throwable throwable) {
// 处理异常回调逻辑
}
});
3.单向发送(Oneway):单向发送不关心发送结果,发送后立即返回,没有回调函数。这种方式适用于不关心消息发送结果且对响应时间要求很高的场景。
producer.sendOneway(message);
通过选择不同的发送方式,您可以根据具体的业务需求来平衡消息的可靠性和发送性能。同步发送提供了最高的可靠性,异步发送和单向发送可以在一定程度上提高发送的吞吐量和响应时间。
Queue的分配算法
在消息队列系统中,存在一些常见的分配算法来决定消息队列(Queue)如何分配给消费者(Consumer)。以下是几种常见的分配算法:
-
轮询(Round-robin):这是最简单的分配算法之一,它按照循环顺序依次将消息队列分配给每个消费者。每个消费者依次接收一个消息队列,然后循环到下一个消费者,实现了简单的负载均衡。
-
平均分配(Average distribution):在平均分配算法中,将可用的消息队列平均分配给所有的消费者。每个消费者接收相同数量的消息队列,以实现相对均匀的负载分布。
-
哈希分配(Hash distribution):哈希分配算法使用消息的某些属性(如消息的键或消息体)计算哈希值,并根据哈希值将消息分配给相应的消息队列和消费者。这样相同属性的消息总是被分配到相同的消息队列上,确保了具有相同属性的消息被顺序地处理。
-
优先级分配(Priority distribution):在优先级分配算法中,为每个消息队列和消费者分配不同的优先级。消息队列会根据消息的重要性或优先级进行排序,高优先级的消息将首先被消费者接收和处理。
-
负载均衡(Load balancing):负载均衡算法根据消费者的处理能力和负载情况动态地分配消息队列。通过监控消费者的处理速度和负载情况,将消息队列重新分配给负载较低的消费者,以实现负载均衡和系统性能的优化。
具体选择哪种分配算法取决于应用程序的需求和特点。有些消息队列系统还允许用户自定义和扩展分配算法来满足特定的业务需求。
Consumer消息的拉取模式有哪两种
在消息队列系统中,消费者(Consumer)可以通过两种方式来主动拉取消息,这两种方式分别是:
-
批量拉取(Pull Mode):在批量拉取模式中,消费者主动向消息队列服务器发送请求,请求获取一批消息。消费者可以控制每次拉取的消息数量,并根据自身的处理能力进行批量处理。这种方式适用于需要精确控制消息获取数量和处理批次的场景。
-
流式拉取(Streaming Mode):在流式拉取模式中,消费者以流的方式持续拉取消息,实现了实时处理。消费者会周期性地向消息队列服务器发送请求,获取新的消息。这种方式适用于需要实时处理消息、保持海量消息处理和持续消费的场景。
-
push推送
如何保证消息的顺序
要保证消息的顺序性,可以采取以下几种方法:
-
单一队列:将所有相关的消息发送到同一个队列中,确保消息在同一个队列中按顺序处理。这样消费者只需要从该队列中按顺序消费消息即可。
-
分区顺序:如果有多个消费者并行消费消息,可以将消息按照某个属性值(例如某个字段或关键字)进行分区,使具有相同属性值的消息发送到同一个队列中。每个队列中的消息按顺序被消费,从而保证分区内的顺序。
-
顺序消息处理器:使用顺序消息处理器(Orderly Message Processor)来处理消息。该处理器在消费端维护一个消息队列,按顺序将消息放入该队列中。然后使用单个线程按顺序从队列中取出消息,确保消息按照发送顺序进行处理。
-
并发控制:通过并发控制机制来确保消息的有序处理。例如,在消费端使用互斥锁(Mutex)或分布式锁(Distributed Lock)来保证同一时间只有一个消费者在处理消息,避免并发操作导致消息顺序错乱。
需要注意的是,以上方法中的一些方式可能会引入一定的性能开销或限制并行处理能力。因此,在设计系统时需要根据业务需求和性能要求进行权衡和选择。对于特别注重消息顺序的场景,可以选择单一队列或顺序消息处理器等方法来保证消息的顺序性。
延迟消息有用过吗?怎么用?可以用在什么业务场景
延迟消息在某些业务场景中非常有用。延迟消息是指将消息发送到消息队列中,但要求消息在一定的延迟时间后才能被消费者接收和处理。延迟消息可以用于以下一些业务场景:
-
订单超时处理:在电子商务中,当用户下单后,可以发送一个延迟消息来处理订单超时逻辑。如果在一定时间内未支付,延迟消息将被消费者接收并触发相应的订单取消操作。
-
定时任务调度:延迟消息可用于实现简单的定时任务调度。将任务相关的信息发送为延迟消息,并设置延迟时间,以便在指定时间后消费者接收消息并执行任务。
-
提醒与通知:例如,在应用中设置了提醒功能,可以使用延迟消息来实现提醒的发送。将提醒内容发送为延迟消息,并设置延迟时间,以便在指定的时间点提醒用户。
-
积分过期处理:对于一些应用中的积分系统,可以使用延迟消息来处理积分的过期。在用户获取积分后,发送一个延迟消息,指定积分的有效期。在延迟时间到达后,消费者会接收到消息并自动执行相应的积分过期处理逻辑。
实现延迟消息的方式有多种,可以使用消息队列系统自带的延迟消息特性(如果支持),也可以通过额外的定时任务、定时器或者延迟队列来实现。具体的实现方式和工具库会根据使用的消息队列系统和编程语言而有所不同。无论使用何种方式,延迟消息都可以帮助在特定的时间做出相应的处理,提升系统的灵活性和用户体验。
事务消息的使用场景,举例说明
事务消息是指在消息队列系统中支持事务性操作的消息传递机制。它可以将多个操作封装在一个消息事务中,并要么全部执行成功,要么全部回滚。
以下是一些使用场景的例子:
-
订单支付:在电子商务应用中,当用户下单并进行支付操作时,可以将订单信息和支付操作封装为一个事务消息。消息事务中包含了订单创建、库存扣减和支付处理等操作。只有当所有操作都成功执行,事务消息才被提交,否则会回滚并重新处理。
-
资金转移:在金融系统中,资金转移涉及到多个账户的扣款和入账操作。通过使用事务消息,可以将这些操作封装在一个消息事务中,以确保原子性和一致性。只有当所有账户的扣款和入账都成功时,事务消息才被提交。
-
消息推送和持久化:在消息推送和持久化的场景中,事务消息可用于确保消息的可靠传输和持久化。例如,在发送通知或短信时,将消息发送到事务消息中,待消息成功持久化后再进行真正的发送,如果发送失败则进行回滚,确保消息的可靠性。
-
数据库操作:在分布式系统中,可能需要跨多个数据库进行操作。通过使用事务消息,可以将多个数据库操作封装在一个消息事务中,以确保数据的一致性。只有当所有数据库操作都成功执行,事务消息才被提交。
事务消息的主要优势在于保证多个操作的原子性和一致性,使得分布式系统的数据处理更加可靠和可靠。在具体应用中,需要根据业务需求和系统架构选择使用事务消息的场景,并结合相应的消息队列系统提供的事务性特性进行使用和配置。