1.RabbitMQ基于docker安装单机版本
1.1 搜索rabbitmq镜像
docker search rabbitmq:management
1.2 拉取镜像
docker pull rabbitmq:management
1.3 创建并启动容器
docker run -d --name rabbitmq -p 15672:15672 -p 5672:5672 rabbitmq:management
1.4 查看rabbitmq管理页面
访问地址 ip:15672
默认账号密码:guest/guest
2.RabbitMQ基本概念
2.1 virtual host
虚拟主机:rabbitmq中有多个虚拟主机,每个虚拟主机都有全套的AMQP组件,可以针对每个虚拟主机进行权限和数据分配,并且每个虚拟主机间是完全隔离的。
2.2 connection
客户端与rabbitmq进行交互,需要建立一个tcp连接,这个连接就是connection。
2.3 channel
用于客户端与rabbitmq进行数据交互,大多数的数据操作都是在信道channel这个层面进行操作。
2.4 exchange
交换机主要与生产者打交道,生产者发送的消息通过exchange交换机分配到各个不同的Queue队列中,消费者通常只关注自己感兴趣的队列即可。
2.5 Queue
队列就是存放数据的最小单位。队列结构天生就具有FIFO的顺序。
3.RabbitMQ编程模式
只介绍官方提供的常见编程模式
3.1 Hello World
不需要指定exchange交换机,生产者指定queue推送消息,消费者指定queue进行消费消息。
3.2 work queues
跟hello world模式基本一致,区别是有多个消费者,多个消费者竞争消费,轮询消费消息。
3.3 publish/subscribe
发布订阅模式,每个消费者监听自己的队列,完成各自的业务逻辑
3.4 routing
根据routing key精确指定发送数据到对应的队列中
3.5 topic
根据routing key进行模糊匹配
4.springboot整合RabbitMQ
4.1 创建项目
使用spring initializer创建springboot-rabbitmq项目,然后创建模块producer、consumer,项目目录如下:
4.2 生产者
- 引用jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 配置application.yaml
spring:
rabbitmq:
host: ip
port: 5672
username: guest
password: guest
# 虚拟机可以不配置,若配置则需要在管理端创建,默认是/
virtual-host: boot-test
# 开启publisher-confirm 有以下可选值
# simple:同步等待confirm结果,直到超时
# correlated:异步回调,定义ConfirmCallback。mq返回结果时会回调这个ConfirmCallback
# NONE:默认不开启
publisher-confirm-type: correlated
publisher-returns: true
- 配置类
说明:生产者将消息推送到exchange交换机,exchange将消息转发到对应的queue队列中,所以配置类需要定义交换机、队列、交换机与队列关系等配置。
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
public static final String EXCHANGE_NAME = "boot-exchange";
public static final String QUEUE_NAME = "boot-queue_ttl";
// 1. 交换机
@Bean("bootExchange")
public Exchange exchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
// 2. 队列
@Bean("bootQueue")
public Queue queue() {
return QueueBuilder.durable(QUEUE_NAME).ttl(10000).build();
}
// 3. 交换机与队列绑定关系
@Bean("binding")
public Binding binding(@Qualifier("bootExchange") Exchange exchange, @Qualifier("bootQueue") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("#.boot").noargs();
}
}
- 失败回调
生产者将消息推送到exchange交换机可能失败,对应失败回调配置是 publisher-confirm-type: correlated
配置完成后需要写回调处理逻辑,创建类 RabbitmqConfirmCallBack
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
@Component
public class RabbitmqConfirmCallBack implements RabbitTemplate.ConfirmCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
System.out.println("发送消息到exchange成功...");
} else {
System.out.println("发送消息到exchange失败...\n" + cause);
}
}
}
exchange交换机将消息根据路由规则转发到队列queue中可能失败,对应失败回调配置是publisher-returns: true
配置完成后需要写回调处理逻辑,创建类 RabbitmqReturnCallBack
@Component
public class RabbitmqReturnCallBack implements RabbitTemplate.ReturnsCallback {
/**
* 消息路由失败回调
*/
@Override
public void returnedMessage(ReturnedMessage returned) {
System.out.println(returned.toString());
}
}
- 测试代码
import com.sxd.producer.common.RabbitmqConfirmCallBack;
import com.sxd.producer.common.RabbitmqReturnCallBack;
import com.sxd.producer.config.RabbitConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
class RabbitmqTests {
@Resource
private RabbitTemplate template;
@Resource
private RabbitmqConfirmCallBack confirmCallBack;
@Resource
private RabbitmqReturnCallBack returnCallBack;
@Test
void producerTest() {
template.setMandatory(true);
template.setConfirmCallback(confirmCallBack);
template.setReturnsCallback(returnCallBack);
String msg = "ABC";
template.convertAndSend(RabbitConfig.EXCHANGE_NAME, "producer.boot", msg);
}
}
- 运行结果
4.3 消费者
- 引用jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 配置application.yaml
spring:
rabbitmq:
host: ip
port: 5672
username: guest
password: guest
virtual-host: boot-test
listener:
# 容器类型simple或direct 简单理解为一对一;direct理解为一对多个消费者
simple:
acknowledge-mode: manual
prefetch: 1
server:
port: 8081
- 创建监听
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Service;
@Service
public class ListenerService {
@RabbitListener(queues = "boot-queue_ttl")
public void consumerTest(Message msg, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws Exception {
System.out.println(new String(msg.getBody()));
channel.basicAck(deliveryTag, true);
}
}
- 测试结果
5.死信队列
5.1 什么是死信队列?
简单理解就是处理死信消息的队列,那什么是死信消息呢?
消息成为死信消息有三种情况:
- 消息被否定,针对消费者端,首先配置参数default-requeue-rejected(默认值为true)为false, 然后使用channel.basicNack(批量拒绝)或者channel.basicReject(拒绝单条)方法拒绝消息。
- 消息超过设定时长未被消费。
- 超过队列长时,绑定了死信队列的消息会进入死信队列。
5.2 死信队列简单使用
使用步骤:
- 声明死信交换机
- 声明死信队列
- 绑定交换机与队列关系
- 业务队列设置死信交换机(消费者将消息推送到交换机后,由交换机路由到队列中,死信消息是由业务队列推送到死信交换机后,路由到死信队列中)
代码实现:
- 参数配置 default-requeue-rejected: false
spring:
rabbitmq:
host: 192.168.230.144
port: 5672
username: guest
password: guest
virtual-host: boot-test
listener:
# 容器类型simple或direct 简单理解为一对一;direct理解为一对多个消费者
simple:
acknowledge-mode: manual
prefetch: 1
default-requeue-rejected: false
- 配置类RabbitConfig
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitConfig {
public static final String EXCHANGE_NAME = "boot-exchange";
public static final String QUEUE_NAME = "boot-queue_ttl";
public static final String DEAD_EXCHANGE_NAME = "dead-letter-exchange";
public static final String DEAD_QUEUE_NAME = "dead-letter-queue";
// 1. 交换机
@Bean("bootExchange")
public Exchange exchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
// 2. 队列
@Bean("bootQueue")
public Queue queue() {
Map<String, Object> args = new HashMap<>(2);
args.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
args.put("x-dead-letter-routing-key", "#.boot");
// 绑定死信交换机及路由信息
return QueueBuilder.durable(QUEUE_NAME).withArguments(args).ttl(60000).build();
}
// 3. 交换机与队列绑定关系
@Bean("binding")
public Binding binding(@Qualifier("bootExchange") Exchange exchange, @Qualifier("bootQueue") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("#.boot").noargs();
}
// 死信交换机
@Bean("deadLetterExchange")
public Exchange deadLetterExchange() {
return ExchangeBuilder.topicExchange(DEAD_EXCHANGE_NAME).build();
}
// 死信队列
@Bean("deadLetterQueue")
public Queue deadLetterQueue() {
return QueueBuilder.durable(DEAD_QUEUE_NAME).build();
}
// 死信交换机与死信队列绑定关系
@Bean("deadLetterBinding")
public Binding deadLetterBinding(@Qualifier("deadLetterExchange") Exchange exchange,
@Qualifier("deadLetterQueue") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("#.boot").noargs();
}
}
- 测试结果
启动生产者服务后推送消息到业务队列中。
60秒消息超时后,消息推送到死信队列中。
5.3 利用死信队列实现延迟队列思路
延迟队列:等待一段时间后消费消息。
实现思路:设置队列超时时长,当消息超时则会进入死信队列,消费者指定消费的队列为死信队列。
消息 -> 业务队列(此时不消费) -> 超时进入私信队列 -> 消费者消费
6.RabbitMQ使用中的常见问题
6.1 消息可靠性投递
消息丢失的情况有以下几种:
- 生产者推送消息到exchange交换机时,有两种方法可以感知,一是手动开启(channel.txSelect)/提交(channel.txCommit)事务,二是本文案例中的confirm回调机制。
- exchange交换机路由到队列时,通过本文案例中return回调机制判断消息是否路由失败,然后处理路由失败消息。
- 消费者拉取消息时,通过ack应答机制,ack参数设置:NONE(自动ack)、MANUAL(手动ack)、AUTO(如果方法未抛异常,则发送ack;如果方法抛出异常,并且不是AmqpRejectAndDontRequeueException则发送nack,并且重新入队列;如果抛出异常是AmqpRejectAndDontRequeueException则发送nack且不会重新入队列)
6.2 幂等性问题
在代码进行处理,通过业务唯一id进行判断,防止由于网络原因或者rabbitmq重新入队机制导致消息重复消费。
6.3 消息顺序消费
单个队列+单消息推送(一个消费者)才能保证消息顺序消费。