保证消息可靠性
消息的可靠性可以分为三个方面:
1.发送方:
需要使用RabbitMQ的发送端确认机制,确认消息成功发送到RabbitMQ并被处理
需要使用RabbitMQ的消息返回机制,若没有发现目标队列,中间件会通知发送方
2.消费方:
需要使用RabbitMQ的消费端确认机制,确认消息没有发生处理异常。
需要使用RabbitMQ的消费端限流机制,限制消息推送速度,确保接收端服务稳定
3.RabbitMQ自身:
大量堆积的消息会给RabbitMQ产生很大的压力,需要使用RabbitMQ消息过期时间,防止消息大量积压
消息过期后直接被丢弃,无法对系统运行异常发出警报,需要使用RabbitMQ的死信队列,收集过期消息,以供分析
1.发送端确认机制:
消息发送后,若中间件收到了消息,会给发送端一个应答。
确认机制有三种:
1.单条同步确认:
实现方法:
配置channel,开启确认模式:
channel.confirmSelect()
每发送一条消息,就调用channel.waitForConfirms()等待确认
2.多条同步确认:【不推荐】
实现方法:
配置channel,开启确认模式:channel.confirmSelect()
发送多条消息后,调用channel.waitForConfirms()等待确认
返回true:表示多条消息都签收成功
返回false:表示 发送的多条消息中有失败的,但不知道是哪一个,或者是哪几个失败了
3.异步确认:【不推荐】
实现方法:
配置channel,开启确认模式:channel.confirmSelect()
在channel上添加监听:addConfirmListener,发送消息后,会回调此方法,通知是否发送成功
异步确认有可能是单条,也可能是多条,取决于MQ
单条同步确认代码实现:
使用的是上一章节的订单微服务的创建订单接口
public void createOrder(OrderCreateVO orderCreateVO) throws IOException, TimeoutException, InterruptedException {
log.info("createOrder:orderCreateVO:{}", orderCreateVO);
OrderDetailPO orderPO = new OrderDetailPO();
orderPO.setAddress(orderCreateVO.getAddress());
orderPO.setAccountId(orderCreateVO.getAccountId());
orderPO.setProductId(orderCreateVO.getProductId());
orderPO.setStatus(OrderStatus.ORDER_CREATING);
orderPO.setDate(new Date());
orderDetailDao.insert(orderPO);
OrderMessageDTO orderMessageDTO = new OrderMessageDTO();
orderMessageDTO.setOrderId(orderPO.getId());
orderMessageDTO.setProductId(orderPO.getProductId());
orderMessageDTO.setAccountId(orderCreateVO.getAccountId());
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
if(channel.waitForConfirms()){
log.info("confirm OK");
}else {
log.info("confirm Failed");
}
});
2.消息返回机制:
原理:消息发送后,中间件【exchange】会对消息进行路由,若没有发现目标队列,中间件会通知发送端,
Return Listener会被调用。可以在Return Listener中编写一些消息没有被路由的异常处理
消息返回的开启方法:
在RabbitMQ基础配置中有一个关键配置项:Mandatory
1.若Mandatory为 false,消息路由,找不到对应 的队列时,将会被直接丢弃。
2.若Mandatory为true,RabbitMQ才会处理无法路由的消息。
第三个参数就是设置 Mandatory 的
channel.basicPublish("exchange.order.settlement", "key.settlement",true, null,
messageToSend.getBytes());
Return Listener方法需要在消息发送前设置:
channel.addReturnListener(new ReturnCallback() {
@Override
public void handle(Return returnMessage) {
log.info("Message Return: returnMessage{}", returnMessage);
//除了打印log,可以加别的业务操作
}
});
if (message.getEnvelope().getDeliveryTag()%10 == 0){
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
channel.basicPublish("exchange.order.restaurant", "key.order",true, null, messageToSend.getBytes());
Thread.sleep(1000);
消息路由成功,该方法不会返回
3.消费端确认机制
默认情况下,消费端接收消息时,消息会被自动确认(ACK),这样mq就会认为消息被妥善发送了,当消费端消息处理异常时,发送端和消息中间件无法得知消息处理情况
需要使用RabbitMQ消费端确认机制,确认消息被正确处理。
消费端ACK类型:
1.自动ACK:消费端收到消息后,会自动签收消息
2.手动ACK:消费端收到消息后,不会自动签收消息,需要我们在业务代码中显示签收消息
手动ACK类型:
1.单挑手动ACK:multiple=false【推荐】
2.多条手动ACK:multiple=true【不推荐】
代码实现:
原先channel.basicConsume()监听函数,第二个参数就是设置是否自动的
4.消费端限流
业务高峰期,可能出现发送端和接收端性能不一致,大量消息被同时推送给接收端,造成接收端服务崩溃
需要使用RabbitMQ的消费端限流机制,限制消息推送速度,以保障接收端服务稳定
通过RabbitMQ-QoS【服务质量保障功能】保障限流
QoS功能保证了在一定数目的消息未被确认前,不消费新的消息。
Qos功能的前提是不使用自动确认
Qos原理:
当消费端有一定数量的消息未被ACK确认时,mq不会给消费端推送新的消息
mq使用QoS机制实现了消费端限流
消费端限流机制参数设置:
1.prefetchCount:针对一个消费端最多推送多少未确认的消息
2.global:true 针对整个消费端限流; false:针对当前channel【是AMPQ协议中的,RabbitMQ暂未实现】
3.prefetchSize:0 (单个消息大小限制,一般为0)【是AMPQ协议中的,RabbitMQ暂未实现】
代码实现:
在消费者的channel.basicConsume()之上添加 channel.QoS(prefetchCount) ,剩余两个没有实现
===========================================
1.若mq没有实现QoS时,发送端有可能会将大量消息一下子都推给消费端,消费端所有的消息都处于Unack状态。消费端会一条一条的处理堆积的消息,处于Unack状态的消息不断减少。
但是当有大量消息堆积在一个消费端时,这时启动另一个消费端,这两个消费端都监听这个队列,但是没有对【有50条消息已经推给了第一个消费端的情况】没有任何改善,
因为50条消息已经推给了第一个消费端。另一个消费者是抢不到消息的。只能等第一个消费者将消息处理完,业务才能正常展开。
2.mq实现QoS时,这时启动第二个消费端,是能够改善情况的,因为堆积的消息处于Ready状态,第二个消费端是能够抢夺堆积的消息的
===========================================
5.消息过期机制
过期机制是为了解决队列爆满的。
默认情况下,消息进入队列,会永远存在,直到被消费
大量堆积的消息会给RabbitMQ产生很大的压力,
所以需要使用RabbitMQ的消息过期机制,防止消息大量堆积
RabbitMQ的过期时间(TTL【 Time to Live】):生存时间
RabbitMQ的过期时间分为:消息TTL 和 队列TTL
1.消息TTL : 设置了单条消息的过期时间
2.队列TTL:设置了队列中所有消息的过期时间【不是队列自己的过期时间】
TTL的设置主要考虑技术架构与业务:
1.TTL应该明显长于服务的平均重启时间
2.建议TTL 长于业务高峰期时间
单条消息的过期时间代码实现:
就是在发送消息时,给消息设置特殊参数
channel.basicPublish("exchange.order.reward", "key.reward", null, messageToSend.getBytes());
特殊参数原先是null
//以毫秒为单位
AMQP.BasicProperties properties=new AMQP.BasicProperties().builder().expiration("15000").build();
channel.basicPublish("exchange.order.restaurant", "key.order", properties, messageToSend.getBytes());