消息的confirm机制
是什么
消息确认机制指的是:生产者将消息发送给mq server后,如果mq server接收到消息后,就会给生产者一个应答。
生产者通过confirm机制来确保消息发送到mq server。
为什么
消息的confirm机制是可靠性投递的核心
怎么做
mq confirm机制的流程图:
代码层面实现confirm机制步骤:
- 在channel上开启确认模式。
channel.confirmSelect()
- 在channel上添加监听器,监听mq-server返回的应答
//1. 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.91.137");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("fcp");
connectionFactory.setConnectionTimeout(100000);
//2. 创建连接
Connection connection = connectionFactory.newConnection();
//3. 创建channel
Channel channel = connection.createChannel();
//4. 设置消息投递模式为:确认模式
channel.confirmSelect();
//5.添加消息确认监听
channel.addConfirmListener(new MyConfirmListener());
自定义消息确认监听器
public class MyConfirmListener implements ConfirmListener {
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("handleAck 发送成功-----> deliveryTag: "+deliveryTag+", multiple: "+multiple);
}
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("handleNack 发送失败-----> deliveryTag: "+deliveryTag+", multiple: "+multiple);
}
}
return listener消息处理机制
是什么
return listener用来处理一些不可路由的消息。
不可路由的消息:
- mq server中没有对应的exchange来接收消费者的消息
- 消息能投递到mq server中的交换机上,但交换机根据routingkey找不到对应的队列
如何处理不可路由的消息:
- 生产端的mandatory设置为false(默认),那么该消息将被删除
- 生产端的mandatory设置为true,mq server将会调用生产段的ReturnListener来处理。
怎么做
流程图实现:
2.代码实现
实现步骤:
- 在channel添加ReturnListener监听器
部分代码如下:
//1. 创建工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.91.137");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("fcp");
connectionFactory.setConnectionTimeout(100000);
//2. 创建一个连接
Connection connection = connectionFactory.newConnection();
//3. 创建一个channel
Channel channel = connection.createChannel();
//4. 设置监听不可达消息
channel.addReturnListener(new MyReturnListener());
public class MyReturnListener implements ReturnListener {
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("replyCode:"+replyCode);
System.out.println("replyText:"+replyText);
System.out.println("exchange:"+exchange);
System.out.println("routingKey:"+routingKey);
System.out.println("properties:"+properties);
System.out.println("msg body:"+new String(body));
}
}
消费端的ack机制
是什么
消费者在消费后需要给mq server一个应答,是ack还是nack。
消费端有俩种ack类型:
- 自动ack (默认)
- 手动ack
mq默认采用的是自动ack,但有时候我们需要手动签收
怎么做
实现起来非常简单,只需要在消费消息的时候指定是否自动签收即可
channel.basicConsume(queueName, false,new MyAckConsumer(channel));
消费端限流
消费者默认的ack方式是auto,即来多少签收多少。
那么想象这么一个场景, 当消费者没有启动,而生产者向mq发送了大量的消息。当消费者启动的时候,将会有大量的消息涌来,导致消费方压力骤增,甚至奔溃。
为了解决这样的问题,rabbitmq提供了一个qos(服务质量保证)。他允许我们在消费端关闭了自动ack的前提下,设置每次出队的消息数,只有当消费端确认后,才会继续推送消息过来。
实现方式:
channel.BasicQos(prefetchSize,prefetchCount,global)
参数说明:
- prefetchSize:指定设定消息的大小。一般填写0表示不限制
- prefetchCount:消息的阈值,即每次过来几条消息(一般填写1 一条一条处理)
- glabal:表示是channel级别的还是consumer级别的限制。
消息的可靠性投递是什么
可靠性投递主要保障下面俩点
- mq server成功接收到生产者发送的消息
- 消费者成功消费消息,并删除mq中的消息
为了保障这俩点,引入了ack消息确认的机制,即:
- producer需要接收mq server的确认应答
- 消费者成功消息后,需要通知mq server。
接口幂等性及消息幂等性
什么是接口幂等性
接口幂等性指的是用于对于同一个操作发起的一次或多次请求的结果是一致的。比如用户在提交订单时,由于网络原因导致后台延迟处理,用户点击了多次,在没有幂等性的条件下,就会造成订单的重复提交。
什么情况下需要考虑幂等性
- 查询操作接口:对于查询的接口来说,天生满足幂等性
- 删除操作接口:如果不考虑返回结果的话,删除接口也是满足幂等性的。因为删除一次和删除多次的结果都是一样的
- 更新操作接口:
① 如果是常量更新操作,那么接口天生满足幂等性
② 如果是增量更新操作,需要考虑幂等性 - 新增操作:需要考虑接口的幂等性
# 常量更新操作
update table set salary = 4000 where id = 1;
# 增量更新操作
update table set salary= salary+1 where id=1;
MQ幂等性问题产生的原因
MQ在进行可靠性投递投递的时候,存在消息的重发行为,如果接口不保证幂等性,会导致消息的重复消费
MQ服务端如何保证幂等性
rabbitmq消息运转流程图:
消息队列服务会为每条消息都生成一个与事务无关的全局ID(inner_msg_id),rabbitmq server在接收到消息后,会先根据inner_msg_id去判断是否为重复消息(数据库中查询),再决定是否进行消息入库,这样保证每条消息都只会入库一次。
消费端如何保证幂等性
我们可以为每个消息提供一个唯一性的ID,然后通过redis的setnx命令来保证幂等性。
关于幂等性的详细介绍,可以查看这篇文章幂等性及各种解决方案
通过定时任务保证消息可靠性
定时任务保证消息可靠性流程图如下:
上面流程图中有几个要点:
- 由于消息的发送可能失败,所以发送消息前需要先将消息入库,并保存消息的状态
- 定时任务定时抓取数据库中,5分钟内消息状态未完结(未被消费成功)且重试次数小于5的消息进行重发。对于重试次数超过5次的消息,需要人工检查原因
- 由于涉及到消息的重发机制,所以在消费端需要做消息的幂等性
这种方式的不足
从流程图中可以看到,为了保证消息的可靠性,我们引入了一个msg_db
数据库。生产段和消费端都操作了俩个数据库,涉及到了分布式事务,对性能有所影响。
通过延迟队列保证消息可靠性
延迟队列保证消息可靠性流程图:
在延迟检查版本中,引入了回调检查服务,使得订单服务和商品服务都只需进行单库操作即可,避免了分布式事务。
有几个关键点:
- 延迟检查消息的延迟时间:应确保正常情况下消费者已成功消费。比如正常情况下,消费者最迟3秒也能消费完成,那么延迟时间就不应该低于3秒
- 消费者消费成功后,向回调检查服务发送消息,使消息入库(作为消费者是否消费成功的依据)
- 回调服务监听到延迟检查消息后,去数据库中查询消息是否存在。如过不存在说明消费者消费失败,重新发送消息
代码都已经上传到码云:
码云地址
基于定时任务实现的版本在:mq-reliabledelivery-timetask-order和mq-reliabledelivery-timestask-product模块中
基于延迟队列实现的版本在:mq-reliabledelivery-delaycheck-order、mq-reliabledelivery-deleycheck-product 和 mq-reliabledelivery-deleycheck-callback中
如有错误,欢迎指出