一、交换器
1.Direct
direct类型的交换器会把消息路由到那些BindingKey和RoutingKey完全匹配的队列中。
当生产者(P)发送消息时Rotuing key=booking时,这时候将消息传送给Exchange,Exchange获取到生产者发送过来消息后,会根据自身的规则进行与匹配相应的Queue,这时发现Queue1和Queue2都符合,就会将消息传送给这两个队列,如果我们以Rotuing key=create和Rotuing key=confirm发送消息时,这时消息只会被推送到Queue2队列中,其他Routing Key的消息将会被丢弃。
1.1 Direct案例
DirectExchange 路由策略是将消息队列绑定到 DirectExchange 上,当 一条消息到达DirectExchange 时会被转发到与该条消息routing key 相同的 Queue 上,例如消息队列名为“hello-queue ”,则 routingkey 为“hello-queue ”的消息会被该消息队列接收。
1.1 创建项目,并添加依赖
直接创建SpringBoot的项目
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</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>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
1.2 配置文件
spring.application.name=rabbitmq-demo02
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
spring.rabbitmq.port=5672
spring.rabbitmq.host=192.168.134.129
# 设置交换器名称
mq.config.exchange=log.direct
# info 队列名称
mq.config.queue.info=log.info
# info 路由键
mq.config.queue.info.routing.key=log.info.routing.key
# error 队列名称
mq.config.queue.error=log.error
# error 路由键
mq.config.queue.error.routing.key=log.error.routing.key
1.3 消费者
package com.biao.consumer;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "${mq.config.queue.info}",autoDelete = "true")
,exchange = @Exchange(value = "${mq.config.exchange}"
,type = ExchangeTypes.DIRECT)
,key = "${mq.config.queue.info.routing.key}"
)
)
public class InfoReceiver {
@RabbitHandler
public void process(String msg){
System.out.println("info....receiver");
}
}
package com.biao.consumer;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "${mq.config.queue.error}",autoDelete = "true")
,exchange = @Exchange(value = "${mq.config.exchange}"
,type = ExchangeTypes.DIRECT)
,key = "${mq.config.queue.error.routing.key}"
)
)
public class ErrorReceiver {
@RabbitHandler
public void process(String msg){
System.out.println("Error....receiver");
}
}
1.4 创建生产者
创建一个SpringBoot项目,添加和上面一样的依赖
配置文件有区别,不需要添加队列的配置信息
spring.application.name=rabbitmq-demo02
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
spring.rabbitmq.port=5672
spring.rabbitmq.host=192.168.134.129
# 设置交换器名称
mq.config.exchange=log.direct
# info 路由键
mq.config.queue.info.routing.key=log.info.routing.key
# error 路由键
mq.config.queue.error.routing.key=log.error.routing.key
package com.biao.provider;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Sender {
@Autowired
private AmqpTemplate template;
@Value("${mq.config.exchange}")
private String exchange;
@Value("${mq.config.queue.error.routing.key}")
private String routingKey;
public void send(String msg){
// 发送信息
template.convertAndSend(exchange,routingKey,msg);
}
}
1.5 测试效果
2.Topic
direct类型的交换器路由规则是完全匹配BindingKey和RoutingKey,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。topic交换器采用模糊匹配的方式,可以通过通配符满足一部分规则就可以传送:
- routing key为一个句点号“. ”分隔的字符串(我们将被句点号“.
”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit” - binding key与routing key一样也是句点号“. ”分隔的字符串
- binding key中可以存在两种特殊字符“”与“#”,用于做模糊匹配,其中“”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
当生产者发送消息Routing Key=F.C.E的时候,这时候只满足Queue1,所以会被路由到Queue中,如果Routing Key=A.C.E这时候会被同是路由到Queue1和Queue2中,如果Routing Key=A.F.B时,这里只会发送一条消息到Queue2中。
应用举例:
- 分发有关于特定地理位置的数据,例如销售点
- 由多个工作者(workers)完成的后台任务,每个工作者负责处理某些特定的任务
- 股票价格更新(以及其他类型的金融数据更新)
- 涉及到分类或者标签的新闻更新(例如,针对特定的运动项目或者队伍)
- 云端的不同种类服务的协调
- 分布式架构 / 基于系统的软件封装,其中每个构建者仅能处理一个特定的架构或者系统
2.1 Topic案例
TopicExchange 是比较复杂也比较灵活的 种路由策略,在TopicExchange 中,Queue 通过routingkey 绑定到 TopicExchange 上,当消息到达 TopicExchange 后,TopicExchange 根据消息的routingkey 消息路由到一个或者多 Queue上,相比direct模式topic会更加的灵活些。
2.1.1 创建项目导入依赖
2.1.1 配置文件
spring.application.name=rabbitmq-demo06
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
spring.rabbitmq.port=5672
spring.rabbitmq.host=192.168.134.129
# 设置交换器名称
mq.config.exchange=log.topic
# info 队列名称
mq.config.queue.info=log.info
# error 队列名称
mq.config.queue.error=log.error
# error 队列名称
mq.config.queue.logs=log.all
2.1.2 消费者
package com.biao.consumer;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(
bindings = @QueueBinding(value = @Queue(value = "${mq.config.queue.error}"
,autoDelete = "true"),
exchange = @Exchange(value = "${mq.config.exchange}",
type = ExchangeTypes.TOPIC)
,key = "*.log.error"
)
)
public class ErrorRecevicer {
@RabbitHandler
public void process(String msg){
System.out.println("error....recevier:"+msg);
}
}
package com.biao.consumer;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(
bindings = @QueueBinding(value = @Queue(value = "${mq.config.queue.info}"
,autoDelete = "true"),
exchange = @Exchange(value = "${mq.config.exchange}",
type = ExchangeTypes.TOPIC)
,key = "*.log.info"
)
)
public class InfoRecevicer {
@RabbitHandler
public void process(String msg){
System.out.println("info....recevier:"+msg);
}
}
package com.biao.consumer;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(
bindings = @QueueBinding(value = @Queue(value = "${mq.config.queue.logs}"
,autoDelete = "true"),
exchange = @Exchange(value = "${mq.config.exchange}",
type = ExchangeTypes.TOPIC)
,key = "*.log.*"
)
)
public class logsRecevicer {
@RabbitHandler
public void process(String msg){
System.out.println("All....recevier:"+msg);
}
}
2.1.3 创建生产者
创建一个新的SpringBoot项目
配置文件
spring.application.name=rabbitmq-demo07
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
spring.rabbitmq.port=5672
spring.rabbitmq.host=192.168.134.129
# 设置交换器名称
mq.config.exchange=log.topic
OrderSender生产者
package com.biao.provider;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class OrderSender {
@Autowired
private AmqpTemplate template;
@Value("${mq.config.exchange}")
private String exchange;
/**
* 发送信息
* @param msg
*/
public void send(String msg){
template.convertAndSend(exchange,"Order.log.debug","Order log debug"+msg);
template.convertAndSend(exchange,"Order.log.info","Order log info"+msg);
template.convertAndSend(exchange,"Order.log.error","Order log error"+msg);
template.convertAndSend(exchange,"Order.log.warn","Order log warn"+msg);
}
}
ProductSender生产者
package com.biao.provider;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ProductSender {
@Autowired
private AmqpTemplate template;
@Value("${mq.config.exchange}")
private String exchange;
/**
* 发送信息
* @param msg
*/
public void send(String msg){
template.convertAndSend(exchange,"Product.log.debug","Product log debug"+msg);
template.convertAndSend(exchange,"Product.log.info","Product log info"+msg);
template.convertAndSend(exchange,"Product.log.error","Product log error"+msg);
template.convertAndSend(exchange,"Product.log.warn","Product log warn"+msg);
}
}
UserSender生产者
package com.biao.provider;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class UserSender {
@Autowired
private AmqpTemplate template;
@Value("${mq.config.exchange}")
private String exchange;
/**
* 发送信息
* @param msg
*/
public void send(String msg){
template.convertAndSend(exchange,"user.log.debug","user log debug"+msg);
template.convertAndSend(exchange,"user.log.info","user log info"+msg);
template.convertAndSend(exchange,"user.log.error","user log error"+msg);
template.convertAndSend(exchange,"user.log.warn","user log warn"+msg);
}
}
2.1.4 测试效果
3.fanout
fanout会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中
应用举例:
- 大规模多用户在线(MMO)游戏可以使用它来处理排行榜更新等全局事件
- 体育新闻网站可以用它来近乎实时地将比分更新分发给移动客户端
- 分发系统使用它来广播各种状态和配置更新
3.1 fanout案例
FanoutExchange 的数据交换策略是把所有到达 FanoutExchang 的消息转发给所有与它绑定的Queue ,在这种策略中, routingkey 将不起任何作用.
3.1.1 创建项目导入依赖
3.1.2 配置文件
spring.application.name=rabbitmq-demo08
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
spring.rabbitmq.port=5672
spring.rabbitmq.host=192.168.134.129
# 设置交换器名称
mq.config.exchange=log.fanout
# 短信服务队列
mq.config.queue.sms=order.sms
# push服务队列
mq.config.queue.push=order.push
3.1.3 消费者
package com.biao.consumer;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "${mq.config.queue.sms}",autoDelete = "true")
,exchange = @Exchange(value = "${mq.config.exchange}",type = ExchangeTypes.FANOUT)
)
)
public class SmsReceiver {
@RabbitHandler
public void process(String msg){
System.out.println("Sms .... receiver");
}
}
package com.biao.consumer;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "${mq.config.queue.push}",autoDelete = "true")
,exchange = @Exchange(value = "${mq.config.exchange}",type = ExchangeTypes.FANOUT)
)
)
public class PushReceiver {
@RabbitHandler
public void process(String msg){
System.out.println("Push .... receiver");
}
}
3.1.4 创建生产者
配置文件
spring.application.name=rabbitmq-demo09
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
spring.rabbitmq.port=5672
spring.rabbitmq.host=192.168.134.129
# 设置交换器名称
mq.config.exchange=log.fanout
生产者
package com.biao.provide;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Sender {
@Autowired
private AmqpTemplate template;
@Value("${mq.config.exchange}")
private String exchange;
/**
* 发送信息
* @param msg
*/
public void send(String msg){
template.convertAndSend(exchange,"",msg);
}
}
3.1.5 测试
二、持久化
消息的可靠性是RabbitMQ的一大特色,RabbitMQ是如何保证信息的可靠性的呢?–>信息的持久化
案例基于上面的Direct案例改造
1.创建消费者
注意我们需要设置autoDelete=false
package com.biao.consumer;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "${mq.config.queue.error}",autoDelete = "false")
,exchange = @Exchange(value = "${mq.config.exchange}"
,type = ExchangeTypes.DIRECT)
,key = "${mq.config.queue.error.routing.key}"
)
)
public class ErrorReceiver {
@RabbitHandler
public void process(String msg){
System.out.println("Error....receiver"+msg);
}
}
2.创建服务提供者
package com.biao.provider;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Sender {
@Autowired
private AmqpTemplate template;
@Value("${mq.config.exchange}")
private String exchange;
@Value("${mq.config.queue.error.routing.key}")
private String routingKey;
public void send(String msg){
// 发送信息
template.convertAndSend(exchange,routingKey,msg);
}
}
3.单元测试
@SpringBootTest
class RabbitmqDemo03ApplicationTests {
@Autowired
private Sender sender;
@Test
void contextLoads() throws InterruptedException {
for (int i = 0; i < 1000; i++) {
Thread.sleep(2000);
sender.send("Hello RabbitMQ" + i);
}
}
}
4.测试
当消费者处理了一段时间的消息后,断开连接,然后消费者再上线我们发现消费者又能够处理掉线后提供者发送的消息,保证了消息的完整性
5.属性
autoDelete属性
-
@Queue :当所有的消费者客户端连接断开后,是否自定删除队列
true:删除 ,false:不删除 -
@Exchange:当所有的绑定队列都不在使用时,是否自动删除交换器
true:删除,false:不删除
三、ACK确认机制
1.什么是ACK机制
如果消息在处理过程中,消费者的服务器在处理消息时出现了异常,那么可能这条正在处理的消息就没有完成消息消费,数据就会丢失,为了确保数据不会丢失,RabbitMQ支持消息确认机制-ACK
2. ACK消息确认机制
ACK(Acknowledge Character)是消费者从RabbitMQ收到消息并处理完成后,反馈给RabbitMQ的,RabbitMQ接收到反馈信息后才会将消息从队列中删除。
- 如果一个消费者在处理消息出现了网络不稳定,服务器异常等现象,会将消息重新放入队列中。
- 如果在集群的情况下,RabbitMQ会立即将这个消息推送给这个在线的其他的消费者,这种机制保障了消费者在服务端故障的时候不会丢失任何的数据和任务
- 消息永远不会从RabbitMQ中删除:只有当消费者正确发送ACK反馈后,RabbitMQ收到确认后,消息才会从RabbitMQ的服务中删除
- 消息的ACK机制默认就是打开的
3.ACK的注意事项
如果忘记掉ACK,那么后果会比较严重,当Consumer退出时Message会一直重复分发,然后RabbitMQ会占用越来越多的内存,由于RabbitMQ会长时间的运行,因此这个 内存泄漏 是致命的,我们可以通过设置重试次数来防止这个问题,在Consumer的application.properties中设置如下参数
spring.rabbitmq.listener.simple.retry.enabled=true
## 设置重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
## 重试的间隔时间 spring.rabbitmq.listener.simple.retry.initial-interval=5000