一、序言
提早工作利用宽泛,提早工作典型利用场景有 订单超时主动勾销
; 领取回调重试
。其中订单超时勾销具备幂等性属性,无需思考反复生产问题;领取回调重试须要思考反复生产问题。
提早工作具备如下特点:在将来的某个工夫点执行;个别仅执行一次。
1、实现原理
生产者将带有提早信息的音讯发送到 RabbitMQ 交换机中,期待延迟时间完结方将音讯转发到绑定的队列中,消费者通过监听队列生产音讯。提早工作的要害在音讯在交换机中停留。
不言而喻,基于 RabbitMQ 实现提早工作对服务器的可靠性要求极高,交换机内部消息无长久化机制,比方单机模式服务重启,未开始的提早工作均失落。
2、组件选型
二、方案设计
(一)服务器
RabbitMQ 服务须要装置 x-delayed-message
插件以解决提早音讯。
(二)生产者
提早工作的实现对生产者的要求是将音讯牢靠的投递到交换机,因而应用 confirm 确认
机制即可。
订单生成之后,先入库,而后以订单 ID 为 key 将订单详情存入 Redis 中(长久化),向 RabbitMQ 发送异步 confirm 确定申请。如果收到失常投递返回,则删除 Redis 中订单 ID 为 key 的数据,回收内存,否则以订单 ID 为 key,从 Redis 中查问出订单数据,从新发送。
(三)消费者
提早工作的实现对消费者的要求是以信息不失落的形式生产音讯,具体表现在:手动确认音讯的生产,避免音讯失落;生产端继续稳固,避免音讯沉积;音讯生产失败有重试机制。
思考到订单提早勾销属于幂等性操作,因而无需思考音讯的反复生产问题。
三、SpringBoot 实现
实现局部仅贴一部分外围源码,残缺我的项目请拜访 GitHub。
(一)生产者
思考到下单是极为重要的操作,因而首先将订单落库、存盘,而后进行后续操作。
for (long i = 1; i <= 10; i++) {
/* 1. 模仿生成订单 */
BuOrder order = createOrder(i);
/* 2. 订单入库 */
orderService.removeById(order);
orderService.saveOrUpdate(order);
/* 3. 将订单存入信息 Redis */
RedisUtils.setObject(RabbitTemplateConfig.ORDER_PREFIX + i, order);
/* 4. 向 RabbitMQ 异步投递音讯 */
rabbitTemplate.convertAndSend(RabbitmqConfig.DELAY_EXCHANGE_NAME, RabbitmqConfig.DELAY_KEY, order, RabbitUtils.setDelay(30000), RabbitUtils.correlationData(order.getOrderId()));
}
生产者牢靠投递音讯
public void confirm(CorrelationData correlationData, boolean ack, String cause) {if (correlationData == null) {return;}
String key = ORDER_PREFIX + correlationData.getId();
if (ack) {
/* 如果音讯投递胜利,则删除 Redis 中订单数据,回收内存 */
RedisUtils.deleteObject(key);
} else {
/* 从 Redis 中读取订单数据,从新投递 */
BuOrder order = RedisUtils.getObject(key, BuOrder.class);
/* 从新投递音讯 */
rabbitTemplate.convertAndSend(RabbitmqConfig.DELAY_EXCHANGE_NAME, RabbitmqConfig.DELAY_KEY, order, RabbitUtils.setDelay(30000), RabbitUtils.correlationData(order.getOrderId()));
}
}
(二)消费者
消费者端手动确认,防止音讯失落;失败主动重试。
@RabbitListener(queues = RabbitmqConfig.DELAY_QUEUE_NAME)
public void consumeNode01(Channel channel, Message message, BuOrder order) throws IOException {if (Objects.equals(0, order.getOrderStatus())) {
/* 批改订单状态,设置为敞开状态 */
orderService.updateById(new BuOrder(order.getOrderId(), -1));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
log.info(String.format("消费者节点 01 生产编号为【%s】的音讯", order.getOrderId()));
}
}
消费者牢靠生产应至多开启两个及以上利用,确保音讯队列中不积压音讯。
(三)通用工具包
上述代码波及一个工具类RabbitUtils
,存在于如下依赖中,次要封装 RabbitMQ 极罕用的工具办法。
<dependency>
<groupId>xin.altitude.cms</groupId>
<artifactId>ucode-cms-common</artifactId>
<version>1.4.3.1</version>
</dependency>
喜爱本文点个♥️赞♥️反对一下,如有须要,可通过微信
dream4s
与我分割。相干源码在 GitHub,视频解说在 B 站,本文珍藏在博客天地。