目录
七 RabbitMQ的其他操作
7.1 消息的可靠性(发送可靠)
7.1.1 confim机制(保证发送可靠)
7.1.2 Return机制(保证发送可靠)
7.1.3 编写配置文件
7.1.4 开启Confirm和Return
7.2 手动Ack(保证接收可靠)
7.2.1 添加配置文件
7.2.2 手动ack
7.3 避免消息重复消费
7.3.1 导入依赖
7.3.2 编写配置文件
7.3.3 修改生产者
7.3.4 修改消费者
官方文档 RabbitMQ Documentation | RabbitMQ
MQ全称为Message Queue,消息队列是应用程序和应用程序之间的通信方法。
RabbitMQ是一个Erlang开发的AMQP(高级消息排队 协议)(英文全称:Advanced Message Queuing Protocol )的开源实现。-------------接上章
七 RabbitMQ的其他操作
7.1 消息的可靠性(发送可靠)
7.1.1 confim机制(保证发送可靠)
RabbitMQ的事务:事务可以保证消息100%传递,可以通过事务的回滚去记录日志,后面定时再次发送当前消息。事务的操作,效率太低,加了事务操作后,比平时的操作效率至少要慢100倍。
RabbitMQ除了事务,还提供了Confirm的确认机制,这个效率比事务高很多。
消息传递可靠性 |
---|
7.1.2 Return机制(保证发送可靠)
Confirm只能保证消息到达exchange,无法保证消息可以被exchange分发到指定queue。
而且exchange是不能持久化消息的,queue是可以持久化消息。
采用Return机制来监听消息是否从exchange送到了指定的queue中
消息传递可靠性 |
---|
在消息发送方项目上加入下面内容:
7.1.3 编写配置文件
spring:
rabbitmq:
host: 你的地址
port: 5672
virtual-host: /tingyi
username: test
password: test
publisher-confirms: true
publisher-returns: true
7.1.4 开启Confirm和Return
package com.tingyi.rabbitmq.config;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author 听忆
*/
@Component
public class PublisherConfirmAndReturnConfig implements RabbitTemplate.ConfirmCallback ,RabbitTemplate.ReturnCallback{
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct // init-method
public void initMethod(){
//指定 ConfirmCallback
rabbitTemplate.setConfirmCallback(this);
//指定 ReturnCallback
rabbitTemplate.setReturnCallback(this);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(ack){
System.out.println("消息已经送达到Exchange");
}else{
System.out.println("消息没有送达到Exchange");
}
}
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("消息没有送达到Queue");
}
}
7.2 手动Ack(保证接收可靠)
7.2.1 添加配置文件
-
在消费方application.yml文件添加下面配置, 改为手动应答机制.
spring:
rabbitmq:
host: 你的地址
port: 5672
virtual-host: /tingyi
username: test
password: test
listener:
simple:
acknowledge-mode: manual
7.2.2 手动ack
package com.tingyi.rabbitmq.topic;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author 听忆
*/
@Component
public class Consumer {
@RabbitListener(queues = "boot-queue")
public void getMessage(String msg, Channel channel, Message message) throws IOException {
System.out.println("接收到消息:" + msg);
try {
int i = 1 / 0;
/**
* 消费者发起成功通知
* 第一个参数: DeliveryTag,消息的唯一标识 channel+消息编号
* 第二个参数:是否开启批量处理 false:不开启批量
* 举个栗子: 假设我先发送三条消息deliveryTag分别是5、6、7,可它们都没有被确认,
* 当我发第四条消息此时deliveryTag为8,multiple设置为 true,会将5、6、7、8的消息全部进行确认。
*/
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
e.printStackTrace();
/**
* 返回失败通知
* 第一个参数: DeliveryTag,消息的唯一标识 channel+消息编号
* 第二个boolean true所有消费者都会拒绝这个消息,false代表只有当前消费者拒绝
* 第三个boolean true消息接收失败重新回到原有队列中
*/
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
}
}
}
7.3 避免消息重复消费
重复消费消息,会对非幂等行操作造成问题
重复消费消息的原因是,消费者没有给RabbitMQ一个ack
重复消费 |
---|
为了解决消息重复消费的问题,可以采用Redis,在消费者消费消息之前,现将消息的id放到Redis中,
id-0(正在执行业务)
id-1(执行业务成功)
然后使用ack给RabbitMQ返回消息
如果RabbitMQack失败,在RabbitMQ将消息交给其他的消费者时,先执行setnx,如果key已经存在,获取他的值,如果是0,当前消费者就什么都不做,如果是1,直接ack。
极端情况:第一个消费者在执行业务时,出现了死锁,在setnx的基础上,再给key设置一个生存时间。
备注: java中的方法叫做setIfAbsent, redis中的命令叫做setnx
作用: 如果为空就set值,并返回1, true 如果存在(不为空)不进行操作,并返回0, false
7.3.1 导入依赖
生产者和消费者都加入下面依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.4.5</version>
</dependency>
7.3.2 编写配置文件
spring:
redis:
host: 你的地址
port: 6379
7.3.3 修改生产者
@Test
public void contextLoads() throws IOException {
CorrelationData messageId = new CorrelationData(UUID.randomUUID().toString());
//第四个参数: 设置消息唯一id
rabbitTemplate.convertAndSend("boot-topic-exchange","slow.red.dog","你看听忆哇",messageId);
System.in.read();
}
7.3.4 修改消费者
package com.tingyi.rabbitmq.topic;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* @author 听忆
*/
/**
* java中的方法叫做setIfAbsent, redis中的命令叫做setnx
* 作用:
* 如果为空就set值,并返回1, true
* 如果存在(不为空)不进行操作,并返回0, false
*/
@Component
public class Consumer {
@Autowired
private StringRedisTemplate redisTemplate;
@RabbitListener(queues = "boot-queue")
public void getMessage(String msg, Channel channel, Message message) throws IOException {
//0. 获取MessageId, 消息唯一id
String messageId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");
//1. 设置key到Redis
if(redisTemplate.opsForValue().setIfAbsent(messageId,"0", 10, TimeUnit.SECONDS)) {
//2. 消费消息
System.out.println("接收到消息:" + msg);
//3. 设置key的value为1
redisTemplate.opsForValue().set(messageId,"1",10,TimeUnit.SECONDS);
//4. 手动ack
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}else {
//5. 获取Redis中的value即可 如果是1,手动ack
if("1".equalsIgnoreCase(redisTemplate.opsForValue().get(messageId))){
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
}
}
}