RabbitMQ -> 概述理解
MQ:message queue,指消息队列,是存在于主系统之外的,能够存放消息的地方;发送方连接MQ,将消息发送到MQ进行存放,消费方连接MQ,在指定的队列获取消息进行消费,能够达到,异步处理,应用解耦,流量消峰的目的。*
文章目录
四大核心
- 生产者:将消息发送到交换机,交换机通过路由将消息发送到指定的队列保存
- 交换机:生产者是通过Exchange交换机将消息发送给队列的
- 队列:存放交换机路由的信息
- 消费者:连接队列进行消费
Broker:代表MQ整个应用,包含exchange,queue
exchange和queue之间通过routingKey进行Binding
producer:生产者
Consumer:消费者
生产者和消费者都是通过Connection和Broker进行连接(每个Connection可以创建多个channel进行通讯)
一、安装RabbitMQ
- 官网下载地址:
https://www.rabbitmq.com/download.html
- 文件上传服务器(路劲任意),包括erlang的环境
- 安装文件执行
rpm -ivh erlang-21.3-1.el7.x86_64.rpm
yum install socat -y
rpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm
- 常用指令
启动:systemctl start rabbitmq-server
关闭:systemctl stop rabbitmq-server
重启:systemctl restart rabbitmq-server
状态:systemctl status rabbitmq-server
安装Web管理插件:rabbitmq-plugins enable rabbitmq_management(访问15672)
二、使用RabbitMQ
1.引入库(Springboot搭建)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>${rabbit.version}</version>
</dependency>
2.简单的入门案例
MQ的配置:
@Configuration
public class SummarizeConfig {
//交换机NAME
public String EXCHANGE = "test_exchange";
//队列NAME
public String QUEUE = "test_queue";
//路由
public String ROUTINGKEY = "test_routingKey";
//声明交换机
@Bean
public DirectExchange getExchange() {
return ExchangeBuilder.directExchange(EXCHANGE).build();
}
//声明队列
@Bean
public Queue getQueue() {
return QueueBuilder.durable(QUEUE).build();
}
//队列绑定交换机
@Bean
public Binding bindingQueueToExchange() {
return BindingBuilder.bind(getQueue()).to(getExchange()).with(ROUTINGKEY);
}
}
生产者:
@RestController
public class SummarizeController {
@Resource
private RabbitTemplate rabbitTemplate;
@GetMapping
public void sendMessage(){
rabbitTemplate.convertAndSend("test_exchange", "test_routingKey", "message");
}
}
消费者:
@Component
public class RabbitMqLister {
@RabbitListener(queues = "test_queue")
public void acceptDealyInfo(Message message, Channel channel){
System.out.println("接受到的消息是" + new Date().toString() + new String(message.getBody()));
}
}
三、RabbitMQ实战技能点
1.消息应答
消息应答就是在消费者消费掉消息之后,告诉MQ,我已经消费了消息,MQ就会把该消息进行删除;应答的方式分为自动应答和手动应答,都是在消费方进行设置
-
自动应答:(默认)
消息一旦被消费就会立马被删除,但是由于消费者在处理过程中可能会出现异常,就可能会导致消息不能成功消费,消息丢失 -
手动应答:
在消息消费的地方,手动进行一个消息的应答确认
@Component
public class SummarizeLister {
@RabbitListener(queues = "delay_queue")
public void acceptDealyInfo(Message message, Channel channel) {
System.out.println("接受到的消息是" + new Date().toString() + new String(message.getBody()));
try {
if (true) {
//应答确认(第二个参数就是是否批量确认,true:该队列的所有消息都会确认,)
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} else {
//未确认
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
注意:未应答的消息会重新入队列,被其他队列消费
2.消息持久化
消息默认是存储在内存中的,一旦mq服务器宕掉了,消息就会丢失,我们就需要将队列和消息进行持久化
队列持久化:
队列持久化需要在申明队列的时候,进行持久化参数的设置
@Configuration
public class SummarizeConfig {
//队列NAME
public String QUEUE = "test_queue";
//声明队列
@Bean
public Queue getQueue() {
//第二个参数就是durable:true配置队列持久化
return new Queue(QUEUE, true);
}
}
消息持久化:
消息持久化需要在生产消息方进行设置
@RestController
public class SummarizeController {
@Resource
private RabbitTemplate rabbitTemplate;
@GetMapping
public void sendMessage(){
MessageProperties messageProperties = new MessageProperties();
//设置deliveryMode属性
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
rabbitTemplate.convertAndSend("test_exchange", "test_routingKey",messageProperties);
}
}
注:消息配置了持久化,但是不能保证绝对的消息不丢失,还需要配合发布确认
不公平分发
@Component
public class SummarizeLister {
@RabbitListener(queues = "delay_queue")
public void acceptDealyInfo(Message message, Channel channel) throws IOException {
//不公平分发,这个预取值得调节来判断队列处理的速度,速度慢的可以设置低值,来让消息分发到其他更快的机器上
channel.basicQos(5);
System.out.println("接受到的消息是" + new Date().toString() + new String(message.getBody()));
try {
if (true) {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} else {
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3.发布确认
生产者将信道设置成confirm模式,所有在该信道上发布的消息将会指派一个唯一的id(从1开始),消息一旦被投递到匹配的队列之后,broker就会发布一个确认给生产者,如果消息和队列有持久化,那么确认消息就会在持久化之后发送,confirm模式是异步的,在确认消息的同时,继续发送消息,如果消息丢失,会发送一条nack消息,可以在回调方法中处理该nack消息
1.单个确认(批量确认就是使用同一个channel发送多条数据之后,waitForConfirm一次)
//开启发布确认
channel.confirmSelect();
//确认发布
channel.waitForConfirms();
2.异步确认
//开启发布确认
channel.confirmSelect();
/**
1. 线程安全有序的一个哈希表,适用于高并发的情况
2. 1.轻松的将序号与消息进行关联
3. 2.轻松批量删除条目 只要给到序列号
4. 3.支持并发访问
*/
ConcurrentSkipListMap<Long, String> outstandingConfirms = new
ConcurrentSkipListMap<>();
/**
5. 确认收到消息的一个回调
6. 1.消息序列号
7. 2.true 可以确认小于等于当前序列号的消息
8. false 确认当前序列号消息
*/
ConfirmCallback ackCallback = (sequenceNumber, multiple) -> {
if (multiple) {
//返回的是小于等于当前序列号的未确认消息 是一个 map
ConcurrentNavigableMap<Long, String> confirmed =
outstandingConfirms.headMap(sequenceNumber, true);
//清除该部分未确认消息
confirmed.clear();
}else{
//只清除当前序列号的消息
outstandingConfirms.remove(sequenceNumber);
}
};
ConfirmCallback nackCallback = (sequenceNumber, multiple) ->
{String message = outstandingConfirms.get(sequenceNumber);
System.out.println("发布的消息"+message+"未被确认,序列号"+sequenceNumber);
};
//异步确认发布
channel.addConfirmListener(ackCallback, nackCallback);
注:增加了一个监听器,异步的,在发送消息的同时能够确认消息ack/nack两个回调方法;异步的方法显然效率是最高的!
四、交换机(Exchange)
发送者将消息是发给交换机,然后交换机通过一定的规则发送给匹配的队列进行消费
交换机类型:
直接(direct), 主题(topic) ,标题(headers) , 扇出(fanout)
1. 扇出交换机(Fanout):
广播类型:该类型的交换机,会把消息发送给绑定该交换机的所有队列,跟routingKey无关
@Configuration
public class SummarizeConfig {
//交换机NAME
public String EXCHANGE = "test_exchange";
//队列NAME
public String QUEUE = "test_queue";
//Fanout交换机
@Bean
public FanoutExchange getExchange() {
return ExchangeBuilder.fanoutExchange(EXCHANGE).build();
}
//声明队列
@Bean
public Queue getQueue() {
//第二个参数就是durable:true配置队列持久化
return new Queue(QUEUE, true);
}
//队列绑定交换机
@Bean
public Binding bindingQueueToExchange() {
return BindingBuilder.bind(getQueue()).to(getExchange());
}
}
2. 直连交换机(Direct)
根据routingKey,绑定交换机,根据发送消息时候指定的routingKey发送到指定队列
@Configuration
public class SummarizeConfig {
//交换机NAME
public String EXCHANGE = "test_exchange";
//队列NAME
public String QUEUE = "test_queue";
//路由
public String ROUTINGKEY = "test_routingKey";
//Fanout交换机
@Bean
public DirectExchange getExchange() {
return ExchangeBuilder.directExchange(EXCHANGE).build();
}
//声明队列
@Bean
public Queue getQueue() {
//第二个参数就是durable:true配置队列持久化
return new Queue(QUEUE, true);
}
//队列绑定交换机
@Bean
public Binding bindingQueueToExchange() {
return BindingBuilder.bind(getQueue()).to(getExchange()).with(ROUTINGKEY);
}
}
3.主题交换机(Topics):
主题交换机的特点就是,routingKey能够匹配通配符:*:代替一个单词、#:代替0个或者多个单词
@Configuration
public class SummarizeConfig {
//交换机NAME
public String EXCHANGE = "test_exchange";
//队列NAME
public String QUEUE = "test_queue";
//路由(topic的主要特点就是能够匹配通配符)
public String ROUTINGKEY = "*.test.#";
//Fanout交换机
@Bean
public TopicExchange getExchange() {
return ExchangeBuilder.topicExchange(EXCHANGE).build();
}
//声明队列
@Bean
public Queue getQueue() {
//第二个参数就是durable:true配置队列持久化
return new Queue(QUEUE, true);
}
//队列绑定交换机
@Bean
public Binding bindingQueueToExchange() {
return BindingBuilder.bind(getQueue()).to(getExchange()).with(ROUTINGKEY);
}
}
@RestController
public class SummarizeController {
@Resource
private RabbitTemplate rabbitTemplate;
@GetMapping
public void sendMessage(){
//这里的routingKey就可以使用.来间隔单词,使用通配符匹配多样的路由,发送到一个或者多个队列
rabbitTemplate.convertAndSend("test_exchange", "ac.test.stu1", "你好");
}
}
五、死信队列
死信队列的理解和作用:
由于单纯的队列,可能会出现一些异常情况导致消息不能够被消费,一直存在队列中,就需要配置死信队列去消费这些不能正常消费的消息,缓解正常队列的压力
死信队列的来源:
1.消息TTL过期
2.队列达到队列最大长度(队列满了,之后的队列就会丢失)
3.消息被拒绝
死信队列的配置
就是在正常的队列上绑定一个死信交换机,死信交换机和死信队列也是正常的绑定
@Configuration
public class RabbitMqConfig {
//声明交换机
public String X_EXCHANGE = "X";
public String Y_EXCHANGE = "Y";
//队列
public String a_queue = "A";
public String b_queue = "B";
public String c_queue = "C";
//不设置过期时间的队列,能够接受任何过期时间的消息,但是由于队列的关系,先进先出原则,导致后续的消息不能按时的过期
public String d_queue = "D";
@Bean
public DirectExchange x_exchange(){
return new DirectExchange(X_EXCHANGE);
}
@Bean
public DirectExchange y_exchange(){
return new DirectExchange(Y_EXCHANGE);
}
/**
* 这个队列就不需要设置过期时间,直接绑定死信队列,接受过期消息(消息的过期时间由发送消息的时候设置)
*/
@Bean
public Queue d_queue(){
//这个队列需要绑定死信交换机
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", Y_EXCHANGE);
arguments.put("x-dead-letter-routing-key", "YD");
return QueueBuilder.durable(d_queue).withArguments(arguments).build();
}
@Bean
public Binding queueDBindingExchange(){
return BindingBuilder.bind(d_queue()).to(x_exchange()).with("XD");
}
@Bean
public Queue a_queue(){
//这个队列需要绑定死信交换机
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", Y_EXCHANGE);
arguments.put("x-dead-letter-routing-key", "YD");
arguments.put("x-message-ttl", 5000);
return QueueBuilder.durable(a_queue).withArguments(arguments).build();
}
@Bean
public Binding queueABindingExchange(){
return BindingBuilder.bind(a_queue()).to(x_exchange()).with("XA");
}
@Bean
public Queue b_queue(){
//这个队列需要绑定死信交换机
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", Y_EXCHANGE);
arguments.put("x-dead-letter-routing-key", "YD");
arguments.put("x-message-ttl", 10000);
return QueueBuilder.durable(b_queue).withArguments(arguments).build();
}
@Bean
public Binding queueBBindingExchange(){
return BindingBuilder.bind(b_queue()).to(x_exchange()).with("XB");
}
//死信队列和交换机绑定
@Bean
public Queue c_queue(){
return QueueBuilder.durable(c_queue).build();
}
//注意,死信队列和死信交换机绑定的时候,这里的routingKey一定要和上面正常的队列绑定死信交换机时设置的routingKey一致
@Bean
public Binding queueCBindingExchange(){
return BindingBuilder.bind(c_queue()).to(y_exchange()).with("YD");
}
}
消息TTL过期,可以在队列设置,可以在发送消息的时候设置(上面配置了队列TTL,也有消息TTL)
@RestController
public class SummarizeController {
@Resource
private RabbitTemplate rabbitTemplate;
@GetMapping
public void sendMessage(){
MessageProperties messageProperties = new MessageProperties();
//设置消息过期时间
messageProperties.setExpiration("10000");
rabbitTemplate.convertAndSend("test_exchange", "ac.test.stu1",messageProperties);
}
}
队列长度
@Bean
public Queue d_queue(){
//这个队列需要绑定死信交换机
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", Y_EXCHANGE);
arguments.put("x-dead-letter-routing-key", "YD");
//设置队列的最大长度
arguments.put("x-max-length", 6);
return QueueBuilder.durable(d_queue).withArguments(arguments).build();
}
消息被拒
消费方在消费消息的时候,拒绝消息
@Component
public class SummarizeLister {
@RabbitListener(queues = "delay_queue")
public void acceptDealyInfo(Message message, Channel channel) {
try {
if (true) {
System.out.println("接受到的消息是" + new Date().toString() + new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} else {
//拒绝消息
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
以上三种情况,只要是发送到的正常队列,在配置的时候,绑定了死信交换机,那么出现任何一种情况,消息都会传给死信交换机发送给死信队列,由监听死信队列的消费者进行消费处理
六、延迟队列
延迟队列的作用可能就是在指定时间之后进行一个信息的处理,本质上其实就是使用死信队列和TTL相结合的方式,让消息到了指定时间过期,过期之后放到死信队列中信息下一步消费,正常的队列就不需要再设置对应的消费者,让消息等待直到过期;
消息过期和队列过期的区别
消息过期:存放在队列中,如果队列前面存在还未消费的消息,那么该过期消息不会立即被队列丢弃,要等到消费到该消息的时候才丢弃;
队列过期:队列设置了TTL,那么一旦消息过期就会立马被队列丢弃,如果配置了死信队列,会丢个死信队列
延迟队列的配置
@Component
public class MsgTtlQueueConfig {
public static final String Y_DEAD_LETTER_EXCHANGE = "Y";
public static final String QUEUE_C = "QC";
//声明队列 C 死信交换机
@Bean("queueC")
public Queue queueB(){
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
//声明当前队列的死信路由 key
args.put("x-dead-letter-routing-key", "YD");
//声明队列的 TTL(注意这里,如果是队列设置了TTL,那么这个队列最好是同一时间过期的消息,如果不设置队列TTL,发送消息的时候设置消息的TTL,那么这个队列就属于一个公共的队列)
args.put("x-message-ttl", 40000);
//没有声明 TTL 属性
return QueueBuilder.durable(QUEUE_C).withArguments(args).build();
}
//声明队列 B 绑定 X 交换机
@Bean
public Binding queuecBindingX(@Qualifier("queueC") Queue queueC,
@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueC).to(xExchange).with("XC");
}
}
注意:如果是全部过期消息都放到了一个队列,就会出现到期不能及时被消费的情况,因为队列会按照顺序进行消费
Rabbitmq 插件实现延迟队列
rabbitMq的安装目录的plgins目录下,安装rabbitmq_delayed_message_exchange插件
安装步骤:
1.进入plugins目录:/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins
2.执行插件:rabbitmq-plugins enable rabbitmq_delayed_message_exchange
插件安装完成之后,交换机里面就会多出一种x-delayed-message类型
配置插件延迟交换机
配置
@Configuration
public class DelayRabbitMqConfig {
//声明延迟交换机名称
public String DELAY_EXCHANGE = "delay_exchange";
public String DELAY_QUEUE = "delay_queue";
public String DELAY_KEY = "delay_key";
//声明交换机
@Bean
public CustomExchange getCustomExchange() {
Map<String, Object> map = new HashMap<>();
//自定义交换机的类型
map.put("x-delayed-type", "direct");
return new CustomExchange(DELAY_EXCHANGE, "x-delayed-message", true, false, map);
}
//声明队列
@Bean
public Queue getQueue(){
return QueueBuilder.durable(DELAY_QUEUE).build();
}
//绑定延迟交换机和队列
@Bean
public Binding binDing(){
return BindingBuilder.bind(getQueue()).to(getCustomExchange()).with(DELAY_KEY).noargs();
}
}
生产者
@RestController
public class DelayQueueController {
@Resource
private RabbitTemplate rabbitTemplate;
/**
* 延迟插件的实现,延迟的是交换机
* 这里在设置消息的时候,不能使用expiretion,过期时间,必须使用setDelay()延迟时间
* 基于插件的延迟交换机就能够实现,先过期的先消费
* 1.安装delay-message插件
* 2.配置自定义交换机类型为:x-delayed-message,还需要设置交换机map参数x-delayed-type,direct
* 3.正常的声明队列,绑定队列,发送消息(发送的时候需要注意的是,设置消息的延迟时间使用的是setDelay,而不是之前的过期时间), 监听消息
*/
@GetMapping("/delay2/{message}")
public void sendDelayQueueInfo2(@PathVariable String message){
rabbitTemplate.convertAndSend("delay_exchange","delay_key","10秒消息" + message, msg -> {
msg.getMessageProperties().setDelay(10000);
return msg;
});
rabbitTemplate.convertAndSend("delay_exchange","delay_key","5秒消息" + message, msg -> {
msg.getMessageProperties().setDelay(5000);
return msg;
});
}
}
注意:配置了插件的交换机之后,发送到这个队列的消息就会按照过期时间进行排列,按照顺序进行消费,就不会出现过期不死的情况
七、发布确认高级(确认和回退)
发布确认高级:指在确保消息发送给交换机,交换机发送给队列的时候,进行一个确认,保证消息最终能够达到队列
1.ConfirmCallback:消息确认,返回参数是否为true判断消息是否发送成功
2.ReturnsCallback:消息是否路由成功
发布确认高级需要在配置文件中配置:
spring:
rabbitmq:
host: xx.xx.xx.xx
username: xx
password: xx
port: 5672
# 开启发布消息到交换机确认回调
publisher-confirm-type: correlated
# routingKey 错误回调(队列问题)
publisher-returns: true
NONE:禁用发布确认模式,是默认值
CORRELATED:发布消息成功到交换器后会触发回调方法
Springboot配置
@RestController
public class ConfirmQueueController {
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
//这个是发送到交换机的确认(g高级确认就是使用rabbitmq对象的时候,需要设置ConfirmCallBack -> 消息发送到交换机确认;
// returnCallBack -> 路由到队列失败回退方法;两个方法都可以获取到message,可以后续继续处理)
rabbitTemplate.setConfirmCallback((var1, var2, var3) -> {
assert var1 != null;
System.out.println("收到消息:"+ var1.getId() + "");
if (var2) {
System.out.println("消息发送到交换机成功");
} else {
System.out.println("消息发送到交换机失败");
}
});
/**
* true:
* 交换机无法将消息进行路由时,会将该消息返回给生产者
* false:
* 如果发现消息无法进行路由,则直接丢弃
*/
rabbitTemplate.setMandatory(true);
//设置队列失败 回退函数(如果同时设置了回退消息,以及备份交换机,那么备份交换机的优先级是要高于回退消息的)
rabbitTemplate.setReturnsCallback(returnedMessage -> System.out.println("回退的消息:" + new String(returnedMessage.getMessage().getBody()) + " 回退的交换机:" + returnedMessage.getExchange() + "回退routingkey: " + returnedMessage.getRoutingKey()));
}
/**
* 延迟插件的实现,延迟的是交换机
* 这里在设置消息的时候,不能使用expiretion,过期时间,必须使用setDelay()延迟时间
* 基于插件的延迟交换机就能够实现,先过期的先消费
* 1.安装delay-message插件
* 2.配置自定义交换机类型为:x-delayed-message,还需要设置交换机map参数x-delayed-type,direct
* 3.正常的声明队列,绑定队列,发送消息(发送的时候需要注意的是,设置消息的延迟时间使用的是setDelay,而不是之前的过期时间),监听消息
*/
@GetMapping("/confirm/{message}")
public void sendDelayQueueInfo2(@PathVariable String message){
CorrelationData correlationData = new CorrelationData();
correlationData.setId("1");
rabbitTemplate.convertAndSend("confirm_exchange","confirm_key","正常的队列消息" + message, correlationData);
rabbitTemplate.convertAndSend("confirm_exchange","confirm_key1","错误routingKey消息" + message, correlationData);
//rabbitTemplate.convertAndSend("confirm_exchange","confirm_key","回调测试信息" + message);
}
}
八、备份交换机
备份交换机可以理解为正常交换机的备胎,给一个交换机申明了一个备份交换机之后,如果消息不能路由时,这个消息就会被转发到备份交换机,由备份交换机进行处理
备份交换机配置
配置的原理就是在正常的交换机上面配置一个alternate-exchange属性的交换机,然后交换机和队列就是正常的申明和绑定关系;如果备份交换机和确认和回退同时配置了,那么备份交换机的优先级要高于回退配置
@Configuration
public class ComfirmRabbitMqConfig {
//声明交换机名称,队列名称,routingKey
public String CONFIRM_EXCHANGE = "confirm_exchange";
public String CONFIRM_QUEUE = "confirm_queue";
public String CONFIRM_KEY = "confirm_key";
public String BACKUPS_EXCHANGE = "backups_exchange";
public String BACKUPS_QUEUE = "backups_queue";
public String WARING_QUEUE = "waring_queue";
//声明交换机(备份交换机,就是在普通交换机增加了callBack,returnBack,之后,需要单独的设置一个交换机,来作为当前这个交换机的备份交换机,所以普通交换机在创建的时候,需要设置“alternate-exchange”,属性)
@Bean
public DirectExchange getConfirmDirectExchange() {
return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE).durable(true).withArgument("alternate-exchange", BACKUPS_EXCHANGE).build();
}
@Bean
public FanoutExchange getBackupsExchange() {
return new FanoutExchange(BACKUPS_EXCHANGE);
}
//声明队列
@Bean
public Queue getConfirmQueue(){
return QueueBuilder.durable(CONFIRM_QUEUE).build();
}
@Bean
public Queue getBackupsQueue(){
return QueueBuilder.durable(BACKUPS_QUEUE).build();
}
@Bean
public Queue getWaringQueue(){
return QueueBuilder.durable(WARING_QUEUE).build();
}
//绑定延迟交换机和队列
@Bean
public Binding binDingConfirm(){
return BindingBuilder.bind(getConfirmQueue()).to(getConfirmDirectExchange()).with(CONFIRM_KEY);
}
//fanout类型交换机(备份交换机和队列都是正常的配置以及正常的交换机和队列绑定,只是在普通的交换机创建的时候,需要申明属性alternate-exchange,备份交换机)
@Bean
public Binding binDingBackups(){
return BindingBuilder.bind(getBackupsQueue()).to(getBackupsExchange());
}
@Bean
public Binding binDingWaring(){
return BindingBuilder.bind(getWaringQueue()).to(getBackupsExchange());
}
}
九、重复消费问题
重复消息作为mq中是重点问题,消费者正常消费了消息,但是由于异常情况不能ack,导致消息再次回到队列中,从而导致消息重复消费
解决方案:使用唯一ID
唯一ID+数据库,每次消费的时候对这个消息的id进行一个数据库查询对比是否已经消费过了,适合低并发的情况
唯一ID+redis,使用redis的原子性redis 执行 setnx 命令,来对比是否已经消费过了,并发高推荐
在消息进行消费的时候,先根据消息的id查询是否已经存在,存在就是重复消息,不存在就正常消费;消费完成回调的时候,如果确认成功,就删除id,确认失败就另行处理
十、优先级队列
优先级队:需要设置队列优先级,消息优先级,消费者需要等待消息已经发送到队列中之后,才去消费(需要对消息进行排序)
1. 配置优先级队列
@Configuration
public class PriorityMqConfig {
//声明交换机
public String EXCHANGE = "priority_exchange";
//队列
public String a_queue = "priority_queue";
@Bean
public DirectExchange exchange(){
return new DirectExchange(EXCHANGE);
}
//优先级队列,队列需要设置优先级,发送消息的时候也需要设置优先级
@Bean
public Queue queue(){
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-max-priority", 10);
return QueueBuilder.durable(a_queue).withArguments(arguments).build();
}
@Bean
public Binding getBinding(){
return BindingBuilder.bind(queue()).to(exchange()).with("priority");
}
}
2.发送消息
@RestController
@RequestMapping("/priority")
public class PriorityQueueController {
@Resource
private RabbitTemplate rabbitTemplate;
@GetMapping("/confirm/{message}")
public void sendDelayQueueInfo2(@PathVariable String message){
for (int i = 0; i < 10; i++) {
CorrelationData correlationData = new CorrelationData();
correlationData.setId(String.valueOf(i));
//如果想要使用到优先级的话,就需要在配置队列的时候,设置x-max-priority参数,然后再发消息的时候,设置setPriority(),数值越大优先级越高,但是消息这边设置的数值不能高于队列配置时设定的数值
if (i == 5) {
rabbitTemplate.convertAndSend("priority_exchange", "priority", "priority 队列消息 " + i + " " + message, msg -> {
msg.getMessageProperties().setPriority(5);
return msg;
}, correlationData);
} else {
rabbitTemplate.convertAndSend("priority_exchange","priority","priority 队列消息 " + i + " " + message, correlationData);
}
}
}
}
十一、惰性队列
惰性队列会尽可能的将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储;默认情况下消息是存储在内存中的,及时设置了持久化,内存中也有一个备份,也会阻塞队列;惰性队列的内存开销很小,但是最好使用在消息量特别大的情况下。
惰性队列的配置
只需要在申明队列的时候,设置x-queue-mode参数即可
//声明队列
@Bean
public Queue getQueue() {
//第二个参数就是durable:true配置队列持久化
HashMap<String, Object> aguments = new HashMap<>();
aguments.put("x-queue-mode", "lazy");
return new Queue(QUEUE, true,false,false, aguments);
}
总结
以上是基于RabbitMQ的基本使用
也是学习RabbitMQ的一个汇总