一、rabbitMQ消息可靠性
消息可靠性控制主要分为两个方面,一个是在生产端的消息控制,另一方面是消费端的消息接收确认。
1、生产端消息确认机制
消息发送有两种方式控制消息的投递可靠性模式
confirm 确认模式:此模式是消息从 producer 到 exchange 的过程控制,并会返回一个 confirmCallback
return 退回模式:此模式是消息从 exchange 到 queue 的过程控制,并会返回一个 confirmCallback
(1)confirm 确认模式
开启confirm确认模式需要以下几个步骤:
1、设置ConnectionFactory的publisher-confirms="true" 开启 确认模式
2、使用rabbitTemplate.setConfirmCallback设置回调函数。当消息发送到exchange后回调confirm方法。在方法中判断ack,如果为true,则发送成功,如果为false,则发送失败,需要处理。
代码如下:
开启确认模式:springboot中设置为
publisher-confirm-type: simple
@Configuration
public class RabbitConfig {
private static final String EXCHANGE_NAME = "exchange_confirm";
private static final String QUEUE_NAME = "queue_confirm";
@Bean(QUEUE_NAME)
public Queue queue() {
return QueueBuilder.durable(QUEUE_NAME).build();
}
@Bean(EXCHANGE_NAME)
public Exchange exchange() {
return ExchangeBuilder.directExchange(EXCHANGE_NAME).durable(true).build();
}
@Bean
public Binding binding(@Qualifier(QUEUE_NAME)Queue queue,
@Qualifier(EXCHANGE_NAME)Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("info").noargs();
}
}
@SpringBootTest
class RabbitmqProviderApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
if (b){
System.out.println("消息发送成功");
}else{
System.out.println("消息发送失败");
}
}
});
rabbitTemplate.convertAndSend("exchange_confirm","info","无内鬼,可以交易");
}
参数解析
confirm(CorrelationData correlationData, boolean ack, String cause):
1、CorrelationData correlationData :相关配置信息
2、boolean ack :是否成功收到了消息。true 成功,false代表失败
3、String cause :失败原因
(2)、return 回退模式
当消息发送给Exchange后,Exchange路由到Queue失败时才会执行 ReturnCallBack开启回退模式需要以下步骤:
1、设定参数:publisher-returns="true"
2. 设置ReturnCallBack
3. 设置Exchange处理消息的模式:
(1). 如果消息没有路由到Queue,则丢弃消息(默认)
(2). 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack(需要通过设置`setMandatory`方法实现)
代码如下:
@Test
void contextLoadReturn() {
setMandatory必须设置,否则默认失败默认抛弃消息,不会返回
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
System.out.println(returnedMessage.getMessage());
System.out.println(returnedMessage.getReplyCode());
System.out.println(returnedMessage.getReplyText());
}
});
rabbitTemplate.convertAndSend("exchange_confirm","info11","无内鬼,可以交易");
}
ReturnedMessage为失败消息的一个封装类
public class ReturnedMessage {
private final Message message; //消息对象
private final int replyCode; //错误码
private final String replyText; //错误信息
private final String exchange; //交换机名称
private final String routingKey; //路由key
2、消费端信息接收确认机制
即Consumer Ack,ack指Acknowledge,确认。确认方式包含以下三种:
acknowledge="none" //默认全部所有消息消费成功,不确认
acknowledge="aotu" //根据异常信息类型自动确认
acknowledge="munaul" //手动确认
(1)不确认、自动确认与手动确认的区别:
不确认即consumer收到队列消息后就立刻确认收到,随后消息队列清除缓存的数据,但是如果consumer在处理数据逻辑是出现错误,则无法进行第二次发送数据,存在数据丢失的风险。
手动确认是指在consumer接收数据后并完成数据操作成功后再调用basicack方法进行确认,之后消息队列再清除数据。但是当逻辑出现问题时,可以调用basicNack方法要求queue重新发送数据,有效防止了数据丢失。
自动确认根据是否抛出异常进行确认。
(2)手动确认开启步骤如下:
1、设置acknowledge属性,设置ack方式 none:自动确认,manual:手动确认
2、如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);方法确认签收消息
3、如果出现异常,则在catch中调用 basicNack或 basicReject,拒绝消息,让MQ重新发送消息
listener:
simple:
acknowledge-mode: manual
direct:
acknowledge-mode: manual
@Component
public class MyRabbitListener {
@RabbitListener(queues = "queue_confirm")
public void lister(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.println(new String(message.getBody()));
int i =1/0;
System.out.println("停止");
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
System.out.println("返回");
channel.basicNack(deliveryTag,true,true);
}
}
}
3、消息可靠性保障
1、数据持久化:
exchange持久化
queue持久化
message持久化
2、confirm确认和consumer Ack确认
3、Broker高可用
二、高级特性
1、消息限流
RabbitMQ具有削峰填谷的作用,所以在这个过程中需要进行限流,防止消息一次性进入系统的数量超过系统负载。开启限流:
1、在<rabbit:listener-container> 中配置 prefetch属性设置消费端一次拉取消息数量
2、消费端的确认模式一定为手动确认。acknowledge="manual"
代码如下:
spring:
rabbitmq:
password: guest
username: guest
port: 5672
virtual-host: /
host: 192.168.183.123
listener:
simple:
acknowledge-mode: manual
prefetch: 1
direct:
acknowledge-mode: manual
prefetch: 1
@Component
public class MyRabbitListenerDlx {
@RabbitListener(queues = "queue_confirm")
public void lister(Message message, Channel channel) throws IOException, InterruptedException {
Thread.sleep(1500);
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.println(new String(message.getBody()));
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
System.out.println("返回");
channel.basicNack(deliveryTag,true,true);
}
}
}
注意:此处必须使用channel.basicAck(deliveryTag,true);
确认签收数据,否则在consumer收到第一次发送的信息后,queue会一直等待签收后再进行下一次发送。
2、TTL消息存活时间
TTL包含两种情况,一种是队列统一过期时间,另一种是单条消息过期时间。
(1)队列过期时间
管理界面设置方法:
代码设置:
@Bean(QUEUE_NAME)
public Queue queue() {
Map<String, Object> map = new HashMap<>();
map.put("x-message-ttl",10000);
return new Queue(QUEUE_NAME, true, false, false, map);
}
(2)消息过期时间
@Test
void contextLoadTTL() {
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("15000");
return message;
}
};
rabbitTemplate.convertAndSend("exchange_confirm","info","无内鬼,可以交易",messagePostProcessor);
}
注意:此处必须使用channel.basicAck(deliveryTag,true);
在同时设置队列过期和消息过期时,过期时间以短的为准。消息过期后,只有消息在队列顶端,才会判断其是否过期,所以单条信息过期移除其实很有可能会出现延时处理。
3、死信队列
消息成为死信有三种情况:
1. 队列消息长度到达限制;
2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
3. 原队列存在消息过期设置,消息到达超时时间未被消费;
死信队列设置流程:
设置两套队列,其中一套为死信队列,将在正常队列中添加到死信队列中,正常队列用于接收provider的消息,超过队列限制后,消息转发到死信队列,死信队列只用于供给consumer消费。
@Configuration
public class RabbitConfigDLx {
private static final String EXCHANGE_NAME = "exchange_DLX";
private static final String EXCHANGE_NAME1 = "exchange_DLX1";
private static final String QUEUE_NAME = "queue_DLX";
private static final String QUEUE_NAME1 = "queue_DLX1";
定义正常队列,注入死信队列信息,并制定限制条件
@Bean(QUEUE_NAME)
public Queue queue() {
Map<String, Object> map = new HashMap<>();
map.put("x-message-ttl",10000);
map.put("x-max-length",10);
map.put("x-dead-letter-exchange",EXCHANGE_NAME1);
map.put("x-dead-letter-routing-key","dead.#");
return new Queue(QUEUE_NAME, true, false, false, map);
}
@Bean(EXCHANGE_NAME)
public Exchange exchange() {
return ExchangeBuilder.directExchange(EXCHANGE_NAME).durable(true).build();
}
@Bean
public Binding bindingDLX(@Qualifier(QUEUE_NAME)Queue queue,
@Qualifier(EXCHANGE_NAME)Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("info").noargs();
}
定义死信队列
@Bean(QUEUE_NAME1)
public Queue queue1() {
return new Queue(QUEUE_NAME1, true, false, false, null);
}
@Bean(EXCHANGE_NAME1)
public Exchange exchange1() {
return ExchangeBuilder.directExchange(EXCHANGE_NAME1).durable(true).build();
}
@Bean
public Binding bindingDLX1(@Qualifier(QUEUE_NAME1)Queue queue,
@Qualifier(EXCHANGE_NAME1)Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("dead.#").noargs();
}
}
向正常队列发送消息
@Test
void contextLoadDLX() {
for (int i = 0; i < 30; i++) {
rabbitTemplate.convertAndSend("exchange_DLX","info","无内鬼,可以交易");
}
}
4.延迟队列
消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。延迟队列实现有两种方式:
1.可以使用死信队列+TTL实现。
2.使用定时器:定时器定时过短会频繁调用数据库,对性能影响较大,时间过长会出现时间误差。
5、日志与监控
RabbitMQ命令:
RabbitMQ默认日志存放路径: /var/log/rabbitmq/rabbit@xxx.log,其中xxx为主机名
查看队列
# rabbitmqctl list_queues
查看exchanges
# rabbitmqctl list_exchanges
查看用户
# rabbitmqctl list_users
查看连接
# rabbitmqctl list_connections
查看消费者信息
# rabbitmqctl list_consumers
查看环境变量
# rabbitmqctl environment
查看未被确认的队列
# rabbitmqctl list_queues name messages_unacknowledged
查看单个队列的内存使用
# rabbitmqctl list_queues name memory
查看准备就绪的队列
# rabbitmqctl list_queues name messages_ready
6、消息追踪
(1)在使用任何消息中间件的过程中,难免会出现某条消息异常丢失的情况。但是要定位这些异常,在RabbitMQ中就可以使用Firehose和rabbitmq_tracing插件功能来实现消息追踪。
firehose的机制是将生产者投递给rabbitmq的消息,rabbitmq投递给消费者的消息按照指定的格式发送到默认的exchange上(amq.rabbitmq.trace),即会发送两遍。
(2)启动管理平台消息追踪福
启用插件:
rabbitmq-plugins enable rabbitmq_tracing
在docker镜像中,直接启用可能会报错:
[root@zs zs]# rabbitmq-plugins enable rabbitmq_tracing
bash: rabbitmq-plugins: command not found...
这种情况下需要进入容器后再启用插件
[root@zs zs]# docker exec -it d6b5a5c68b15 /bin/bash
root@my-rabbit:/# rabbitmq-plugins enable rabbitmq_tracing
7、消息补偿