一、安装
1.Ubuntu18.04在线安装
sudo apt update
# 安装RabbitMQ服务器软件包
sudo apt install rabbitmq-server
# 创建管理员用户
sudo rabbitmqctl add_user admin 123456
# 授予管理员用户的访问权限
sudo rabbitmqctl set_user_tags admin administrator
# 启用RabbitMQ管理插件
sudo rabbitmq-plugins enable rabbitmq_management
# 重新启动RabbitMQ服务以使更改生效
sudo systemctl restart rabbitmq-server
# 设置RabbitMQ开机自启
sudo systemctl enable rabbitmq-server
2.Windows安装
erlang语言包下载地址: https://erlang.org/download/otp_versions_tree.html
rabbitmq下载地址: 版本对照地址: https://www.rabbitmq.com/which-erlang.html
3.Docker安装
# 拉取并启动
# --hostname 指定主机名(RabbitMQ 的一个重要注意事项是它根据所谓的节点名称存储数据,默认为主机名)
sudo docker run -d -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_VHOST=test -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' -v /var/lib/rabbitmq:/var/lib/rabbitmq --privileged=true --name rabbitmq rabbitmq
# 设置开机自启
sudo docker update rabbitmq --restart=always
# 启动界面管理
sudo docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_management
4.Docker-compose安装
version: "3.8"
services:
rabbitmq:
image: daocloud.io/library/rabbitmq:3.8.7
container_name: rabbitmq
restart: always
environment:
RABBITMQ_DEFAULT_USER: "admin"
RABBITMQ_DEFAULT_PASS: "123456"
volumes:
- ./data/:/var/lib/rabbitmq/
ports:
- 5672:5672
- 15672:15672
networks:
mynet:
driver: bridge
5.集群搭建
- 修改主机名称
# 修改etc/hosts下的文件
# 在三个节点下分别配置如下内容:
192.168.80.161 node1
192.168.80.162 node2
192.168.80.163 node3
# 使用命令修改(如果配置文件配置节点名称修改不过来可以使用这种方式):
sudo hostnamectl set-hostname newhostname
# 使修改生效
sudo systemctl restart systemd-hostnamed
- 同步cookie
# 确保节点间使用同一个cookie,需要将/var/lib/rabbitmq/.erlang.cookie文件从主节点同步到各个从节点
scp /var/lib/rabbitmq/.erlang.cookie admin@192.168.80.163:~/
# 切回根目录,这里是因为权限不够所以将文件发送到了根目录
cd
# 将文件移动至/var/lib/rabbitmq/.erlang.cookie
sudo mv .erlang.cookie /var/lib/rabbitmq/.erlang.cookie
# 修改文件所属用户及用户组
sudo chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie
- 加入集群
# 从节点停止服务,rabbitmqctl stop会将erlang虚拟机关闭,而这种方式只关闭rabbitmq服务
sudo rabbitmqctl stop_app
# 从节点重置(当前node1为主节点)
sudo rabbitmqctl reset
# 加入主节点
sudo rabbitmqctl join_cluster rabbit@node1
# 启用应用服务
sudo rabbitmqctl start_app
# 查看集群状态
sudo rabbitmqctl cluster_stauts
# 不知道啥原因,登录不上去了,重新添加个用户并授权
sudo rabbitmqctl add_user admin 123456
sudo rabbitmqctl set_user_tags admin administrator
6.镜像队列
6.1.精确模式
# rabbitmqctl set_policy 固定写法
# ha-test 自定义的策略名称
# ^test\. 匹配队列的正则表达式
# ha-mode: 策略模式
# ha-params: 如果是使用了精确模式,需要指定备份的数量(这个案例中是2,也就是指主机1份,备份节点1份)
# ha-sync-mode: 三种情况
# automatic: 镜像节点与主节点之间的同步是自动的。镜像节点会自动追随主节点的变化
# manual: 镜像节点与主节点之间的同步是手动的。需要手动触发同步操作
# explicit:镜像节点与主节点之间的同步需要显式命令触发
# 命令如下:
rabbitmqctl set_policy ha-exactly "^test\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
6.2.全部备份
rabbitmqctl set_policy ha-all "^all\." '{"ha-mode":"all"}'
6.3.节点模式
# ha-params: 如果策略模式为nodes,这里需要明确指定副本所在节点名称
rabbitmqctl set_policy ha-nodes "^simple\." '{"ha-mode":"nodes","ha-params":["rabbit@node1", "rabbit@node2"]}'
二、模式(官方介绍)
1.简单模式
# 消费者
@RabbitListener(queuesToDeclare = @Queue(name = "simple.queue", arguments =
@Argument(name = "x-queue-mode", value = "lazy")))
public void simple(String message) {
System.out.println(message);
}
# 生产者
@Resource
private RabbitTemplate rabbitTemplate;
@Test
public void simple() {
rabbitTemplate.convertAndSend("simple.queue", "简单队列");
}
2.工作模式
多个消费者消费同一个队列,默认情况下轮询从通道中获取消息
# 配置文件配置
spring.rabbitmq.listener.simple.prefetch=1
# 消费者
@RabbitListener(queuesToDeclare = @Queue(name = "work.queue", arguments = @Argument(name = "x-queue-mode", value = "lazy")))
public void work1(String msg) {
System.out.println(msg);
}
@RabbitListener(queues = "work.queue")
public void work2(String content) {
System.err.println(content);
}
# 生产者
@Test
public void test() {
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("work.queue", "第" + i + "条消息");
}
}
3.发布与订阅
# 消费者
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "fanout1", arguments = @Argument(name = "x-queue-mode",value = "lazy")),
exchange = @Exchange(name = "my.fanout", type = ExchangeTypes.FANOUT)))
public void fanout1(String message) {
System.out.println(message);
}
RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "fanout2", arguments = @Argument(name = "x-queue-mode", value = "lazy")),
exchange = @Exchange(name = "my.fanout", type = ExchangeTypes.FANOUT)
))
public void fanout2(String message) {
System.err.println(message);
}
# 生产者
@Test
public void test() {
rabbitTemplate.convertAndSend("my.fanout", "", "测试发布与订阅模式");
}
4.路由模式
# 消费者
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "routing1.queue"),
exchange = @Exchange(name = "routing.exchange", type = ExchangeTypes.DIRECT),
key = {"info"}
))
public void routing1(String message) {
System.out.println("info接收到的消息为: " + message);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "routing2.queue"),
exchange = @Exchange(name = "routing.exchange"),
key = {"err", "info"}
))
public void routing2(String message) {
System.err.println("err接收到的消息为: " + message);
}
# 生产者
@Resource
private RabbitTemplate rabbitTemplate;
@Test
public void test1() {
rabbitTemplate.convertAndSend("routing.exchange", "info", "测试路由模式");
}
@Test
public void test2() {
rabbitTemplate.convertAndSend("routing.exchange", "err", "测试路由模式");
}
5.主题模式
#:表示匹配0个或多个单词
*::表示匹配1个单词
# 消费者
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1", durable = "true"),
exchange = @Exchange(name = "topic.exchange", type = ExchangeTypes.TOPIC),
key = {"topic.#"}
))
public void topic1(String message) {
System.out.println("消费者1接收到的消息为: " + message);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue3", durable = "true"),
exchange = @Exchange(name = "topic.exchange", type = ExchangeTypes.TOPIC),
key = {"topic.*.black"}
))
public void topic3(String message) {
System.out.println("消费者3接收到的消息为: " + message);
}
# 生产者
@Test
public void test() {
rabbitTemplate.convertAndSend("topic.exchange", "topic.test.black", "测试主题模式1");
}
@Test
public void test2() {
rabbitTemplate.convertAndSend("topic.exchange", "topic.test", "测试主题模式2");
}
6.发布确认
配置文件中配置如下:
spring.rabbitmq.publisher-confirm-type: correlated
注意: 非必要不要开启。非常影响性能
# 配置类配置
@Configuration
@Slf4j
public class RabbitMQConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.debug("id为{}的消息处理成功", correlationData);
} else {
log.error("消息处理失败,失败原因为: {}", cause);
}
});
}
# 消费者
@RabbitListener(queuesToDeclare = @Queue(name = "confirm.queue"))
public void confirm(String message) {
System.out.println(message);
}
# 生产者
@Test
public void test() {
CorrelationData correlationData = new CorrelationData("unique-id-" + System.currentTimeMillis());
rabbitTemplate.convertAndSend("confirm.queue1", "", "测试confirm模式", correlationData);
}
三、队列
1.惰性队列
接收到消息后直接存入磁盘而非内存
消费者要消费消息时才会从磁盘中读取并加载到内存
支持数百万条的消息存储
# 注解方式定义惰性队列
@RabbitListener(queuesToDeclare = @Queue(name = "simple.queue", arguments = @Argument(name = "x-queue-mode", value = "lazy")))
public void simple(String message) {
System.out.println(message);
}
# Bean方式定义惰性队列
@Bean
public Queue errorQueue() {
return QueueBuilder.durable("error.queue").lazy().build();
}
2.延迟队列
1.将插件添加到plugins目录下
2.添加延迟队列插件
sudo docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_delayed_message_exchange
注意: 3.8版本之前没有提供插件模式,需要使用死信交换机实现延时队列。具体示例参考死信队列
# 消费者
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "delay.queue"),
exchange = @Exchange(name = "delay.direct", delayed = "true"),
key = "delay"
))
public void delay(String message) {
System.out.println(message);
}
# 生产者
@Test
public void test() {
rabbitTemplate.convertAndSend("delay.direct", "delay", "测试延迟队列", new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setDelay(5000);
return message;
}
});
}
3.死信队列
成为死信的三种情况:
- 消费者使用basic.reject或basic.nack声明消费失败,并且消息的requeue参数设置为false
- 消息是一个过期消息,超时无人消费
- 要投递的队列消息堆积满了,最早的消息可能成为死信
下例是以死信交换机实现的延迟队列的案例,通过超时时间使消息成为死信并监听死信队列进行消费
-
定义队列
/**
* 1. 分别定义业务队列&业务交换机 和 死信队列&死信交换机
* 2. 业务队列通过x-dead-letter-routing-key参数指定routing-key,通过x-dead-letter-exchange参数指定绑定的交换机
*/
@Bean
public Queue initQueue() {
return QueueBuilder.durable("my.queue")
.withArgument("x-dead-letter-exchange", "dlx.exchange") // 指定死信交换机
.withArgument("x-dead-letter-routing-key", "dlx.err") // 指定死信交换机routingKey
.build();
}
@Bean
public DirectExchange initExchange() {
return ExchangeBuilder.directExchange("my.exchange").build();
}
@Bean
public Binding initBinding() {
return BindingBuilder.bind(initQueue()).to(initExchange()).with("key");
}
@Bean("dlxQueue")
public Queue dlxQueue() {
return QueueBuilder.durable("dlx.queue").lazy().build();
}
@Bean("dlxExchange")
public DirectExchange dlxExchange() {
return ExchangeBuilder.directExchange("dlx.exchange").build();
}
@Bean
public Binding dlxBinding(@Qualifier("dlxQueue") Queue queue, @Qualifier("dlxExchange") Exchange exchange) {
// return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with("dlx.err");
return BindingBuilder.bind(queue).to(exchange).with("dlx.err").noargs();
}
- 定义消费者
@RabbitListener(queues = "dlx.queue")
public void delay1(String message) {
System.out.println(message);
}
- 定义提供者
@Test
public void test() {
rabbitTemplate.convertAndSend("my.exchange", "key", "测试延迟队列", message -> {
message.getMessageProperties().setExpiration("10000");
return message;
});
}
四、消费者消息可靠性
1.手动签收
# auto: 自动确认,根据异常类型进行判断是否签收
# manual: 需要在代码中手动签收
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: auto
# 手动签收示例
@RabbitListener(queuesToDeclare = @Queue(name = "simple.ack", arguments = @Argument(name = "x-queue-mode", value = "lazy")))
public void ack(String content, Channel channel, Message message) throws IOException {
try {
// 手动签收
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
// 第一个boolean是否允许多条处理,第二个boolean为是否重回队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
2.消费失败重试(自动补偿)
spring:
rabbitmq:
listener:
simple:
retry:
# 开启消费者失败重试
enabled: on
# 初始失败等待时长
initialInterval: 1000ms
# 下次等待时长为 multiplier * last-initial
multiplier: 1
# 最大重试次数
maxAttempts: 5
# false为无装填,如果包含事务改为false
stateless: false
3.重回队列
# 消费者
@Bean
public Queue errorQueue() {
return QueueBuilder.durable("error.queue").lazy().build();
}
@Bean
public DirectExchange errorExchange() {
return new DirectExchange("error.direct");
}
@Bean
public Binding errorBinding() {
return BindingBuilder.bind(errorQueue()).to(errorExchange()).with("error");
}
// 指定重新回到哪个队列
@Bean
public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate) {
return new RepublishMessageRecoverer(rabbitTemplate, errorExchange().getName(), "error");
}
# 生产者需要携带messageId
@Bean
public MessageConverter messageConverter() {
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
converter.setCreateMessageIds(true);
return converter;
}
五、其他
1.消息幂等性
解决幂等性的方案比较多。主要思想就是通过一个唯一标识来分辨消息是否已经消费过
令牌机制
在进入页面时生成一个唯一标识(例如token),操作完成后删除这个唯一标识。每次进行标识的校验。如果存在则方形,不存在则表示消息已经处理过了
全局唯一ID
例如使用MQ中的messageId来判断消息是否消费过
乐观锁方式
2.消息追踪
2.1.Filehose方式
在rabbitmq中会有一个内置交换机amq.rabbitmq.trace,它用于消息的追踪和审计。
实现方式:
1.开启firehose
sudo systemctl enable rabbitmq-server
2.定义一个队列接收消息并且与trace交换机绑定
弊端:
开启消息追踪可能会导致性能开销,特别是在高流量环境中。因为每个消息都要被发送到
amq.rabbitmq.trace
交换机,这增加了系统的负担。将所有消息发送到
amq.rabbitmq.trace
交换机可能导致消息的泄漏和安全风险。因为消息被发送到一个预定义的队列,如果你的系统中有敏感信息,确保在使用这个功能时做好相应的安全措施。如果系统中的消息量非常大,且对每条消息都需要进行追踪,使用
amq.rabbitmq.trace
交换机可能不是最佳选择。考虑使用其他监控和追踪工具来处理高频率的消息追踪需求。
2.2.插件方式
1.查看已启用的插件(非必须) rabbitmq-plugins list
2.启用插件 rabbitmq-plugins enable rabbitmq_tracing
3.在控制台会Admin界面下的右侧会多出一个Tracing
4.添加一个trace
3.队列参数
3.1.消息过期时间
@Bean
public Queue backupQueue() {
return QueueBuilder.durable("backup").withArgument("x-message-ttl", 6000).build();
}
3.2.队列最大长度(条)
# 队列中最多存入100条消息
@Bean
public Queue backupQueue() {
return QueueBuilder.durable("backup").withArgument("x-max-length", 100).build();
}
3.3.队列最大长度(字节)
# 队列中的消息总字节量为102400
@Bean
public Queue backupQueue() {
return QueueBuilder.durable("backup").withArgument("x-max-length-bytes", 102400).build();
}
3.4.队列消息溢出
/**
* x-overflow可选值:
* drop-head: 当队列满时,丢弃队列头部消息
* reject-publish: 当队列满时,拒绝发布新消息
* reject-publish-dlx: 当队列满时,拒绝发布新消息,并将拒绝的消息发布到死信队列
*/
@Bean
public Queue backupQueue() {
return QueueBuilder.durable("backup").withArgument("x-overflow", "drop-head").build();
}
3.5.指定死信队列
@Bean
public Queue initQueue() {
return QueueBuilder.durable("my.queue")
.withArgument("x-dead-letter-exchange", "dlx.exchange") // 指定死信交换机
.build();
}
3.6.指定死信队列路由键
@Bean
public Queue initQueue() {
return QueueBuilder.durable("my.queue")
.withArgument("x-dead-letter-routing-key", "dlx.err") // 指定死信交换机路由键
.build();
}
3.7.单例模式
用于确保在队列上只有一个活跃的(正在消费消息的)消费者。当设置了这个属性后,一旦有一个消费者开始处理消息,其他消费者将被暂停,直到这个消费者完成或者发生了重新分发(requeue)操作
这个属性对于确保消息的顺序处理和避免重复处理非常有用。当队列的消息需要按照严格的顺序处理时,可以通过设置
single active consumer
来确保一次只有一个消费者在处理消息。这在一些特定的业务场景中是非常有用的
@Bean
public Queue backupQueue() {
return QueueBuilder.durable("backup").withArgument("x-single-active-consumer", true).build();
}
3.8.队列优先级
/**
* 取值范围0-255,数值越大越先执行
*/
@Bean
public Queue backupQueue() {
return QueueBuilder.durable("backup").withArgument("x-max-priority", 10).build();
}
3.9.备份交换机
@Bean("simpleQueue")
public Queue initQueue() {
return QueueBuilder.durable("simple").withArgument("alternate-exchange", "backup-exchange").build();
}