基于消息队列(RabbitMQ)实现延迟任务
一、序言
延迟任务应用广泛,延迟任务典型应用场景有订单超时自动取消;支付回调重试。其中订单超时取消具有幂等性属性,无需考虑重复消费问题;支付回调重试需要考虑重复消费问题。
延迟任务具有如下特点:在未来的某个时间点执行;一般仅执行一次。
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>
基于消息队列(RabbitMQ)实现延迟任务的更多相关文章
- C#中使用消息队列RabbitMQ
在C#中使用消息队列RabbitMQ 2014-10-27 14:41 by qy1141, 745 阅读, 2 评论, 收藏, 编辑 1.什么是RabbitMQ.详见 http://www.rabb ...
- 几种常见的微服务架构方案简述——ZeroC IceGrid、Spring Cloud、基于消息队列
微服务架构是当前很热门的一个概念,它不是凭空产生的,是技术发展的必然结果.虽然微服务架构没有公认的技术标准和规范草案,但业界已经有一些很有影响力的开源微服务架构平台,架构师可以根据公司的技术实力并结合 ...
- 几种常见的微服务架构方案——ZeroC IceGrid、Spring Cloud、基于消息队列、Docker Swarm
微服务架构是当前很热门的一个概念,它不是凭空产生的,是技术发展的必然结果.虽然微服务架构没有公认的技术标准和规范草案,但业界已经有一些很有影响力的开源微服务架构平台,架构师可以根据公司的技术实力并结合 ...
- 消息队列--RabbitMQ(一)
1.消息队列概述 可以理解为保存消息的一个媒介/或者是个容器,与之相关有两个概念(即生产者(Publish)与消费者(Consumer)).所谓生产者,就是生产创造消息的一方,那么,消费者便是从队列中 ...
- ASP.NET Core消息队列RabbitMQ基础入门实战演练
一.课程介绍 人生苦短,我用.NET Core!消息队列RabbitMQ大家相比都不陌生,本次分享课程阿笨将给大家分享一下在一般项目中99%都会用到的消息队列MQ的一个实战业务运用场景.本次分享课程不 ...
- 消息队列rabbitmq的五种工作模式(go语言版本)
前言:如果你对rabbitmq基本概念都不懂,可以移步此篇博文查阅消息队列RabbitMQ 一.单发单收 二.工作队列Work Queue 三.发布/订阅 Publish/Subscribe 四.路由 ...
- node使用消息队列RabbitMQ一
基础发布和订阅 消息队列RabbitMQ使用 1 安装RabbitMQ服务器 安装erlang服务 下载地址 http://www.erlang.org/downloads 安装RabbitMQ 下载 ...
- (二)RabbitMQ消息队列-RabbitMQ消息队列架构与基本概念
原文:(二)RabbitMQ消息队列-RabbitMQ消息队列架构与基本概念 没错我还是没有讲怎么安装和写一个HelloWord,不过快了,这一章我们先了解下RabbitMQ的基本概念. Rabbit ...
- (一)RabbitMQ消息队列-RabbitMQ的优劣势及产生背景
原文:(一)RabbitMQ消息队列-RabbitMQ的优劣势及产生背景 本篇并没有直接讲到技术,例如没有先写个Helloword.我想在选择了解或者学习一门技术之前先要明白为什么要现在这个技术而不是 ...
- 消息队列rabbitmq/kafka
12.1 rabbitMQ 1. 你了解的消息队列 rabbitmq是一个消息代理,它接收和转发消息,可以理解为是生活的邮局.你可以将邮件放在邮箱里,你可以确定有邮递员会发送邮件给收件人.概括:rab ...
随机推荐
- python29day
内容回顾 网络编程 概念 B/S C/S架构 B/S browser server C/S client 装客户端使用的 server远程服务器的 osi七层协议 今日内容 tcp协议的编程 如何在连 ...
- Java虚拟机的意义
什么是Java虚拟机? 作为程序员,大家都知道写的代码都是在Java虚拟机上运行的,但大家是否知道,Java虚拟机又是什么呢? 先看看网上搜到有关JAVA虚拟机的介绍 : 虚拟机是一种抽象化的计算机, ...
- 阿里云服务器sshd-D cpu占用过高
发现阿里云服务器cpu占用到达了100%,原因是被植入了挖矿程序,解决方法如下 1.使用top命令查看进程id 直接kill杀死该进程过一会就会重新启动. 2.查看该进程所在的文件目录 这个文件基本上 ...
- #import和#include区别
#import与#include的类似,都是把其后面的文件拷贝到该指令所在的地方 #import可以自动防止重复导入 #import <> 用于包含系统文件 #import"&q ...
- 痞子衡嵌入式:揭秘i.MXRT1060,1010上串行NOR Flash冗余程序启动设计
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT1060,1010上串行NOR Flash冗余程序启动设计. 工业产品设计里经常会有冗余程序/备份程序设计的需求,因为在工业 ...
- haproxy https实现
一.实验环境 一.准备后端服务器 # yum -y install nginx # echo "10.0.0.7" > /usr/share/nginx/html/index ...
- Pandas之isna,fillna
isna() 释义 筛选为NaN的布尔值,可接受单个标量或者数组 举例 筛选stu_name为NaN的所有行: df = pd.DataFrame({'stu_name': ['Tom', 'Tony ...
- D介绍-概述
INTRODUCTION THE SELENIUM PROJECT AND TOOLS Selenium controls web browsers Selenium is many things, ...
- MySQL高性能学习笔记
索引 何为索引?有什么作用? 索引是一种用于快速查询和检索数据的数据结构.常见的索引结构有: B 树, B+树和 Hash. 索引的作用就相当于目录的作用.打个比方: 我们在查字典的时候,如果没有目录 ...
- 关于IDA无法从symbol server下载pdb的问题
在ida目录下,symsrv.dll同目录下创建一个symsrv.yes文件. symsrv.yes将可下载: symsrv.no将失败: 没有相关文件将会弹出授权询问,选择yes和no将创建对应文件 ...