消息队列是一种传输服务。他的角色就是维护一条从Producer到Consumer的路线,
保证数据能够按照指定的方式进行传输。
消息队列中包含下面的角色,各个角色之间相互配合实现了整个消息传递的功能。
Message(消息对象)
一个Message有两个部分:payload(有效载荷)和label(标签)
名称 | 详细 |
---|---|
payload | 主要内容为传输的数据 |
label | 主要是exchange的名字和对数据的描述,此部分的内容在传递给消费者之前会被删除。RabbitMQ也是通过这个label来决定把这个Message发给哪个Consumer |
Producer-消息生产者
作为消息的生产者主要的任务为下面内容
Consumer-消息消费者
作为消费者的主要任务
Exchange-交换器
Exchange的作用是接收生产者发送的消息,根据参数将消息路由到零个(丢弃)或者多个Queue中。
Exchange存在fanout、direct、topic、headers四种类型,每种类型对应不同的路由规则。
路由名称 | 路由规则 |
---|---|
direct | 它会把消息路由到那些binding key与routing key完全匹配的Queue中 |
fanout | 它会把生产者发送到该Exchange的所有消息路由到所有与它绑定的Queue中,最终被多个消费者消费 |
topic | direct的匹配规则是精确匹配的,而topic可以进行模糊匹配 |
headers | 不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配 |
Queue-队列
RoutingKey-路由key
RoutingKey是生产者在推送消息的时候,跟随消息一起发送的路由规则标识
Connection-连接
Producer和Consumer都是通过TCP连接到RabbitMQ Server的
Channels-信道
数据流动都是在Channel中进行的。也就是说,第一步建立TCP连接,第二步建立这个Channel
整个流程可以这么描述
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
dependencies>
spring:
application:
name: rabbit
rabbitmq:
# 地址
host: 127.0.0.1
# 端口
port: 5672
# 账号信息
username: admin
password: admin
# 开启发送确认
publisher-confirms: true
# 开启发送失败退回
publisher-returns: true
listener:
# 开启ACK
direct:
# 配置 manual 表示启动消费方的手动确认
acknowledge-mode: auto
simple:
acknowledge-mode: auto
template:
retry:
enabled: true
完成了上面的配置,我们可以直接引入RabbitTemplate来进行对RabbitMQ进行操作了
@Autowired
private RabbitTemplate rabbitTemplate;
配置Exchange和Queue参数
public static final String DIRECT_QUEUE1 = "direct.queue1";
public static final String DIRECT_QUEUE2 = "direct.queue2";
public static final String DIRECT_EXCHANGE = "direct.exchange";
@Bean
public Queue getDirectQueue() {
return new Queue(DIRECT_QUEUE1);
}
@Bean
public Queue getDirectQueue2() {
return new Queue(DIRECT_QUEUE2);
}
@Bean
public DirectExchange directExchange() {
return new DirectExchange(DIRECT_EXCHANGE);
}
后续我们将队列和Exchange进行绑定
这个时候消息中的路由键(routing key)和 Binding 中的 binding key 一致消息才能被推送至队列中
@Bean
public Binding directBinding1() {
return BindingBuilder
// 设置queue
.bind(getDirectQueue())
// 绑定交换机
.to(directExchange())
// 设置routingKey
.with("direct.V1");
}
@Bean
public Binding directBinding2() {
return BindingBuilder
// 设置queue
.bind(getDirectQueue2())
// 绑定交换机
.to(directExchange())
// 设置routingKey
.with("direct.V2");
}
配置消息发送者
在消息发送者代码中,创建了两个方法,一个使用direct.V1的路由一个使用direct.V2的路由
@Component
public class DirectSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send1(User user) {
this.rabbitTemplate.convertAndSend(
DirectConfig.DIRECT_EXCHANGE,
// routingKey
"direct.V1",
user);
}
public void send2(User user) {
this.rabbitTemplate.convertAndSend(
DirectConfig.DIRECT_EXCHANGE,
// routingKey
"direct.V2",
user);
}
}
配置消息接收者
同样消息接受者我们也监听了两个队列
@Component
public class DirectReceiver {
/**
* queues是指要监听的队列的名字
* @param user
*/
@RabbitListener(queues = DirectConfig.DIRECT_QUEUE1)
@RabbitHandler
public void receiveDirect1(User user) {
System.out.println("【receiveDirect1监听到消息】" + user);
}
/**
* queues是指要监听的队列的名字
* @param user
*/
@RabbitListener(queues = DirectConfig.DIRECT_QUEUE2)
@RabbitHandler
public void receiveDirect2(User user) {
System.out.println("【receiveDirect2监听到消息】" + user);
}
}
消息发送的流程
测试内容
调用接口发送请求
现在我们调用接口:localhost:8000/direct/send1。使用direct.V1的路由key发送消息
控制台可以看到输出内容:
【receiveDirect1监听到消息】User(id=1, name=Direct, age=100)
现在我们调用接口:localhost:8000/direct/send2。使用direct.V2的路由key发送消息
控制台可以看到输出内容:
【receiveDirect2监听到消息】User(id=12, name=DirectV2, age=200)
这个时候可以看到queue消息的监听,严格遵守路由key的一一对应
配置Exchange和Queue参数
/**
* fanout
*/
public static final String FANOUT_QUEUE1 = "fanout.queue1";
public static final String FANOUT_QUEUE2 = "fanout.queue2";
public static final String FANOUT_EXCHANGE = "fanout.exchange";
@Bean
public FanoutExchange topicExchange() {
return new FanoutExchange(FANOUT_EXCHANGE);
}
@Bean
public Queue getFanoutQueue1() {
return new Queue(FANOUT_QUEUE1);
}
@Bean
public Queue getFanoutQueue2() {
return new Queue(FANOUT_QUEUE2);
}
Exchange和Queue绑定
/**
* 监听fanout.exchange 的队列fanout.queue1
* @return
*/
@Bean
public Binding topicBinding1() {
return BindingBuilder
// 设置queue
.bind(getFanoutQueue1())
// 绑定交换机
.to(topicExchange());
}
/**
* 监听fanout.exchange 的队列fanout.queue2
* @return
*/
@Bean
public Binding topicBinding2() {
return BindingBuilder
// 设置queue
.bind(getFanoutQueue2())
// 绑定交换机
.to(topicExchange());
}
配置消息发送者
发送消息的时候并没有指定routingKey而是之间发送到exchange中
@Component
public class FanoutSender {
@Autowired
private AmqpTemplate rabbitTemplate;
/**
* 消息发送到FanoutConfig.FANOUT_EXCHANGE交换机中
* @param user
*/
public void send(User user) {
this.rabbitTemplate
.convertAndSend(FanoutConfig.FANOUT_EXCHANGE, "", user);
}
}
配置消息接收者
监听的时候我们监听了不同的队列消息
@Component
public class FanoutReceiver {
/**
* queues是指要监听的队列的名字
* @param user
*/
@RabbitListener(queues = FanoutConfig.FANOUT_QUEUE1)
public void receiveTopic1(User user) {
System.out.println("【receiveFanout1监听到消息】" + user);
}
@RabbitListener(queues = FanoutConfig.FANOUT_QUEUE2)
public void receiveTopic2(User user) {
System.out.println("【receiveFanout2监听到消息】" + user);
}
}
消息发送的流程
测试内容
调用接口发送请求
现在我们调用接口:localhost:8000/fanout/send。使用direct.V1的路由key发送消息
控制台可以看到输出内容:
【receiveFanout1监听到消息】User(id=1, name=Fanout, age=100)
【receiveFanout2监听到消息】User(id=1, name=Fanout, age=100)
此时一条消息被路由下所有队列消费掉
配置Exchange和Queue参数
/**
* topic
*/
public static final String TOPIC_QUEUE1 = "topic.queue1";
public static final String TOPIC_QUEUE2 = "topic.queue2";
public static final String TOPIC_EXCHANGE = "topic.exchange";
@Bean
public Queue getTopicQueue1() {
return new Queue(TOPIC_QUEUE1);
}
@Bean
public Queue getTopicQueue2() {
return new Queue(TOPIC_QUEUE2);
}
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(TOPIC_EXCHANGE);
}
Exchange和Queue绑定
其中队列2使用的匹配模式。
其中匹配方式:
@Bean
public Binding topicBinding1() {
return BindingBuilder
// 设置queue
.bind(getTopicQueue1())
// 绑定交换机
.to(topicExchange())
// 设置routingKey
.with("dai.message");
}
@Bean
public Binding topicBinding2() {
return BindingBuilder
.bind(getTopicQueue2())
.to(topicExchange())
.with("dai.#");
}
配置消息发送者
消息发送者里面设置了两个请求,一个用来验证精确匹配,一个用来验证模糊匹配
@Component
public class TopicSender {
@Autowired
private AmqpTemplate rabbitTemplate;
/**
* 第一个参数:TopicExchange名字
* 第二个参数:Route-Key
* 第三个参数:要发送的内容
* @param user
*/
public void send(User user) {
this.rabbitTemplate.convertAndSend(
TopicConfig.TOPIC_EXCHANGE,
"dai.message",
user);
this.rabbitTemplate.convertAndSend(
TopicConfig.TOPIC_EXCHANGE,
"dai.dai",
user);
}
}
配置消息接收者
监听操作我们还是监听两个队列
@Component
public class TopicReceiver {
/**
* queues是指要监听的队列的名字
* @param user
*/
@RabbitListener(queues = TopicConfig.TOPIC_QUEUE1)
public void receiveTopic1(User user) {
System.out.println("【receiveTopic1监听到消息】" + user.toString());
}
@RabbitListener(queues = TopicConfig.TOPIC_QUEUE2)
public void receiveTopic2(User user) {
System.out.println("【receiveTopic2监听到消息】" + user.toString());
}
}
消息发送的流程
测试内容
调用接口发送请求
现在我们调用接口:localhost:8000/topic/send。发送消息
控制台可以看到输出内容:
【receiveTopic2监听到消息】User(id=5, name=Topic, age=500)
【receiveTopic1监听到消息】User(id=5, name=Topic, age=500)
【receiveTopic2监听到消息】User(id=5, name=Topic, age=500)
可以看到消息被模糊匹配然后被两个队列都消费掉了
https://docs.spring.io/spring-amqp/docs/2.0.13.BUILD-SNAPSHOT/reference/html/
参数 | 描述 | 参数末班 |
---|---|---|
spring.rabbitmq.address | 客户端连接的地址,有多个的时候使用逗号分隔,该地址可以是IP与Port的结合 | |
spring.rabbitmq.cache.channel.checkout-timeout | 当缓存已满时,获取Channel的等待时间,单位为毫秒 | |
spring.rabbitmq.cache.channel.size | 缓存中保持的Channel数量 | |
spring.rabbitmq.cache.connection.mode | 连接缓存的模式 | CHANNEL |
spring.rabbitmq.cache.connection.size | 缓存的连接数 | |
spring.rabbitmq.connnection-timeout | 连接超时参数单位为毫秒:设置为“0”代表无穷大 | |
spring.rabbitmq.dynamic | 默认创建一个AmqpAdmin的Bean | true |
spring.rabbitmq.host | RabbitMQ的主机地址 | localhost |
spring.rabbitmq.listener.acknowledge-mode | 容器的acknowledge模式 | |
spring.rabbitmq.listener.auto-startup | 启动时自动启动容器 | true |
spring.rabbitmq.listener.concurrency | 消费者的最小数量 | |
spring.rabbitmq.listener.default-requeue-rejected | 投递失败时是否重新排队 | true |
spring.rabbitmq.listener.max-concurrency | 消费者的最大数量 | |
spring.rabbitmq.listener.prefetch | 在单个请求中处理的消息个数,他应该大于等于事务数量 | |
spring.rabbitmq.listener.retry.enabled | 不论是不是重试的发布 | false |
spring.rabbitmq.listener.retry.initial-interval | 第一次与第二次投递尝试的时间间隔 | 1000 |
spring.rabbitmq.listener.retry.max-attempts | 尝试投递消息的最大数量 | 3 |
spring.rabbitmq.listener.retry.max-interval | 两次尝试的最大时间间隔 | 10000 |
spring.rabbitmq.listener.retry.multiplier | 上一次尝试时间间隔的乘数 | 1.0 |
spring.rabbitmq.listener.retry.stateless | 不论重试是有状态的还是无状态的 | true |
spring.rabbitmq.listener.transaction-size | 在一个事务中处理的消息数量。为了获得最佳效果,该值应设置为小于等于每个请求中处理的消息个数,即spring.rabbitmq.listener.prefetch的值 | |
spring.rabbitmq.password | 登录到RabbitMQ的密码 | |
spring.rabbitmq.port | RabbitMQ的端口号 | 5672 |
spring.rabbitmq.publisher-confirms | 开启Publisher Confirm机制 | false |
spring.rabbitmq.publisher-returns | 开启publisher Return机制 | false |
spring.rabbitmq.requested-heartbeat | 请求心跳超时时间,单位为秒 | |
spring.rabbitmq.ssl.enabled | 启用SSL支持 | false |
spring.rabbitmq.ssl.key-store | 保存SSL证书的地址 | |
spring.rabbitmq.ssl.key-store-password | 访问SSL证书的地址使用的密码 | |
spring.rabbitmq.ssl.trust-store | SSL的可信地址 | |
spring.rabbitmq.ssl.trust-store-password | 访问SSL的可信地址的密码 | |
spring.rabbitmq.ssl.algorithm | SSL算法,默认使用Rabbit的客户端算法库 | |
spring.rabbitmq.template.mandatory | 启用强制信息 | false |
spring.rabbitmq.template.receive-timeout | receive()方法的超时时间 | 0 |
spring.rabbitmq.template.reply-timeout | sendAndReceive()方法的超时时间 | 5000 |
spring.rabbitmq.template.retry.enabled | 设置为true的时候RabbitTemplate能够实现重试 | false |
spring.rabbitmq.template.retry.initial-interval | 第一次与第二次发布消息的时间间隔 | 1000 |
spring.rabbitmq.template.retry.max-attempts | 尝试发布消息的最大数量 | 3 |
spring.rabbitmq.template.retry.max-interval | 尝试发布消息的最大时间间隔 | 10000 |
spring.rabbitmq.template.retry.multiplier | 上一次尝试时间间隔的乘数 | 1.0 |
spring.rabbitmq.username | 登录到RabbitMQ的用户名 | |
spring.rabbitmq.virtual-host | 连接到RabbitMQ的虚拟主机 |