MessageQueue
MQ全称是Message Queue(消息队列),是保存消息在传输过程中的一种容器,既是存储消息的一种中间件。多是应用在分布式系统中进行通信的第三方中间件,发送方称为生产者,接收方称为消费者。
MQ的几个主要特性
MQ的优点
解耦: 一个业务需要多个模块共同实现,或一条消息有多个系统对应处理,只需要在主业务完成以后,发送一条MQ,其余模块消费MQ消息,即可实现业务,降低模块之间的耦合。
异步: 主业务执行结束后,从属业务通过MQ异步处理,减少业务的响应时间,提高用户体验。
削峰:高并发情况下,业务异步处理,提供高峰期业务外理能力,避免系统瘫癌。
MQ的缺点
- 系统可用性降低。依赖服务越多,服务越容易挂掉。
- 需要考虑MQ瘫痪的情况系统的复杂性提高。
- 需要考虑消息丢失、消息重复消费、消息传递的顺序性。
- 业务一致性。主业务和从属业务一致性的处理
常用的MQ框架
名称 | 特点 |
---|---|
RabbitMQ | 灵活的消息路由:RabbitMQ支持多种消息路由模式。 多语言客户端支持:RabbitMQ提供了丰富的客户端库。 高可用性和可靠性:RabbitMQ支持主从复制和镜像队列,在节点故障时能够保证消息的可靠传递。 消息确认机制:RabbitMQ支持消息的确认机制,,确保消息不会丢失。 |
Apache Kafka | 高吞吐量和低延迟:Kafka被设计为高效地处理大量消息的系统,能够实现非常高的吞吐量和低延迟。 分布式架构:Kafka采用分布式架构,可以在多个节点之间进行水平扩展,实现高可用性和容错性。 消息持久化:Kafka将消息持久化到磁盘中,保证数据的可靠性和持久性。 发布-订阅模型:通过主题(topics)来组织和分类消息,消费者可以按照自己的需要订阅感兴趣的主题。 流处理能力:Kafka提供了流处理功能,在流处理应用中可以方便地处理和转换实时数据流。 |
RocketMQ | 高吞吐量和低延迟、分布式架构、消息持久化、发布-订阅模型、消息顺序性、消息过滤、提供轻量级的事务支持 |
RabbitMQ中的概念
- channel:操作mq的工具
- exchange:路由
- queue:缓存消息
- virtual host:虚拟主机,是对queue 、exchange等资源的逻辑分组
一些概念
-
P: 生产者,要发送消息的程序,不是直接发送到队列中,而是发送到交换机中。
-
C: 消费者,接收消息的程序,会一直等待消息到来。
-
Queue(红色部分): 消息队列,接收消息,缓存消息。
-
X: 交换机,即Exchange。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。具体是如何操作的,取决于Exchange的类型。Exchange有常见以下3种类型。
常见的exchange类型:
- Fanout:广播:将消息交给所有绑定到交换机的队列,
- Direct:路由:把消息交给符合指定routing key的队列
- Topic:话题:与路由模式的区别是用通配符匹配,把消息交给符合routing pattern的队列
注意:exchange负责消息路由,而不是存储,路由失败则消息丢失。
安装MQ
-
获取镜像
-
查询
- 查询docker镜像
docker search rabbitmq
- 查询docker镜像
-
拉取
-
docker pull rabbitmq:3.7.7-management #docker pull rabbitmq: 需要版本号-management
-
-
-
初始化
-
执行命令初始化MQ容器
-
docker run \ - e RABBITMQ_DEFAILT_USER = username \ # -e 是设置环境变量 - e RABBITMQ_DEFAILT_PASS = password \ #user 和 pwd --name mq \ #容器名 --hostname mq1 \ #主机名,主要用于集群部署 -p 15672:15672 \ # -p 是端口映射 -p 5672:5672 \ #15672是管理平台的端口,5672是消息通信的端口 -d \ # -d 是后台运行 rabbitmq:3-management #镜像名称
-
-
输入上述指令,安装完成
-
进入容器并运行
-
docker exec -it 容器id /bin/bash
-
rabbitmq-plugins enable rabbitmq_management
-
-
查看管理平台界面 :15672
Spring Boot使用Rabbit MQ及API-AMQP
基本消息队列的消息发送流程
- 建立connection
- 创建channel
- 利用channel声明队列
- 利用channel向队列发送消息
基本消息队列的消息接收流程
- 建立connection
- 创建channel
- 利用channel声明队列
- 定义consumer的消费行为handleDelevery()
- 利用channel将消费者与队列绑定
简化消息发送与接收的API–SpringAMQP
使用:
-
添加依赖
-
#Gradle-kotlin implementation("org.springframework.boot:spring-boot-starter-amqp:3.1.0") #maven <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> <version>3.1.0</version> </dependency>
-
写测试,发消息
-
在发送服务,写配置文件
-
spring: rabbitmq: host: xxxx port: 5672 username: xxx password: xxx virtual-host: xxx
-
-
写发送测试
-
-
写测试,收消息
- 在接收服务,写配置文件,内容同上
- 写接收测试
- @RabbitListener 指定接收的消息队列
-
注意:消息一旦消费就会从队列中删除,Rabbit MQ没有回溯功能
消费预取限制
修改application.yml 设施preFetch值。
spring:
rabbitmq:
listener:
simple:
prefetch: 1 #每次只能获取一条消息,处理完才能获取下一个消息;效果:效率高的服务多处理,慢的少处理
RabbitMQ的工作模式
简单模式
- 简单模式:一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)即可。
Queue队列配置
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Configuration
public class QueueAndExchangeConfig {
@Bean("myFirstQueue")
public Queue getFirstQueue(){
return new Queue("my-first-queue");
}
}
生产者配置
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("producer")
public class ProducerController{
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/producerSendFirstQueue")
public String sendMsg(@RequestParam(value = "msg") String msg){
rabbitTemplate.convertAndSend("my-first-queue",msg);
return msg;
}
}
消费者配置
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class ConsumerHandler {
@RabbitListener(queues = "my-first-queue")
public void getFirstQueue(String msg){
System.out.println("消费者1:"+msg);
}
}
工作队列模式
- 工作队列模式 WorkQueue:一个生产者、多个消费者,对同一个消息是竞争关系,不需要设置交换机(使用默认的交换机)
两个消费者的配置,轮训发送消息到消费者
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class ConsumerHandler {
@RabbitListener(queues = "my-first-queue")
public void getFirstQueue(String msg){
System.out.println("消费者1:"+msg);
}
@RabbitListener(queues = "my-first-queue")
public void getFirstQueueTwo(String msg){
System.out.println("消费者2:"+msg);
}
}
发布订阅模式
-
发布订阅模式 Publish/subscribe:发布订阅模式允许将同一条消息发送给多个消费者。
实现方式就是加入了exchange(交换机)。
将两个队列与交换器绑定
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QueueAndExchangeConfig {
//第一个队列:my-first-queue
@Bean("myFirstQueue")
public Queue getFirstQueue(){
return new Queue("my-first-queue");
}
//第二个队列:my-second-queue
@Bean("mySecondQueue")
public Queue getSecondQueue(){
return new Queue("my-second-queue");
}
//第一个交换机:my-first-exchange
@Bean("myFirstExchange")
FanoutExchange getMyFirstExchange(){
return new FanoutExchange("my-first-exchange");
}
//将my-first-queue队列绑定到my-first-exchange交换机上
@Bean
Binding bindingFirstQueueToFanoutExchange(){
return BindingBuilder.bind(getFirstQueue()).to(getMyFirstExchange());
}
//将my-second-queue队列绑定到my-first-exchange交换机上
@Bean
Binding bindingSecondQueueToFanoutExchange(){
return BindingBuilder.bind(getSecondQueue()).to(getMyFirstExchange());
}
}
两个消费者配置
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class ConsumerHandler {
@RabbitListener(queues = "my-first-queue")
public void getFirstQueue(String msg){
System.out.println("消费者1:"+msg);
}
@RabbitListener(queues = "my-second-queue")
public void getSecondQueue(String msg){
System.out.println("消费者2:"+msg);
}
}
路由队列(routing)
-
路由队列是以direct routing = key方式发送消息给消费者的
queue 可以通过一个或多个routing key绑定到Exchange上,不同queue相同routing key绑定到Exchange也是完全可以的
queue 、 exchange 配置
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QueueAndExchangeConfig {
//省略......
//将以my-first-queue队列绑定到my-first-exchange交换机上
@Bean
Binding bindingFirstQueueToFanoutExchange(){
return BindingBuilder.bind(getFirstQueue()).to(getMyFirstExchange()).with("orange");
}
//将以my-second-queue队列绑定到my-first-exchange交换机上
@Bean
Binding bindingSecondQueueToFanoutExchange(){
return BindingBuilder.bind(getSecondQueue()).to(getMyFirstExchange()).with("green");
}
}
生产者在发送消息前会绑定路由,只有路由符合规定的消费者才会接收到消息。
生产者配置
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("producer")
public class ProducerController{
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/producerSendFirstQueue")
public String sendMsg(@RequestParam(value = "msg") String msg){
rabbitTemplate.convertAndSend("my-first-exchange","green",msg);
return msg;
}
}
消费者配置
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class ConsumerHandler {
@RabbitListener(queues = "my-first-queue")
public void getFirstQueue(String msg){
System.out.println("消费者1:"+msg);
}
@RabbitListener(queues = "my-second-queue")
public void getSecondQueue(String msg){
System.out.println("消费者2:"+msg);
}
}
通配符队列 (Topics)
通配符的匹配规则:
符号 | 作用 | 示例 |
---|---|---|
* | 匹配任意一个单词 | rabbit.* :匹配rabbit.first |
# | 匹配任意一个或多个单词 | rabbit.# :匹配rabbit.first 或者 rabbit.first.two |
注意 | 如果没有匹配的路由到队列,那么该消息会丢失 |
简述:在上述的路由队列的路由路径的基础上进行通配。
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QueueAndExchangeConfig {
//省略.....
//将以my-first-queue队列绑定到my-first-exchange交换机上
@Bean
Binding bindingFirstQueueToFanoutExchange(){
return BindingBuilder.bind(getFirstQueue()).to(getMyFirstExchange()).with("green.*");
}
//将以my-second-queue队列绑定到my-first-exchange交换机上
@Bean
Binding bindingSecondQueueToFanoutExchange(){
return BindingBuilder.bind(getSecondQueue()).to(getMyFirstExchange()).with("green.#");
}
}
生产者的消息路由上也有所改变,更加灵活
@GetMapping("/producerSendFirstQueue")
public String sendMsg(@RequestParam(value = "msg") String msg){
rabbitTemplate.convertAndSend("my-first-exchange","green.red",msg);
return msg;
}
远程通讯队列
Remote Procedure Call:远程过程调用,一次远程过程调用的流程即客户端发送一个请求到服务,服务端根据请求信息进行处理后返回响应信息,客户端收到响应信息后结束
连接:RPC通信队列参考
高级应用
qos合理分发
消费预取限制
修改application.yml 设置 preFetch值。
spring:
rabbitmq:
listener:
simple:
prefetch: 1 #每次只能获取一条消息,处理完才能获取下一个消息;效果:效率高的服务多处理,慢的少处理
qos 主要应用于work queue工作队列的实现合理分发,将prefetch设置为1之后,rabbitmq会根据每个消费者的消费时间,合理分发,处理越快的消费者,处理的消息越多
ttl消息过期时间
ttl可以定义队列和消息的过期时间,如果同时设置了队列和消息的过期时间,按最小的时间删除消息,超过指定时间消息还没有被消费则会自动丢失
queue队列过期时间
@Bean("myFirstQueue")
public Queue getFirstQueue(){
return QueueBuilder.durable("my-first-queue").ttl(30000).build();
}