贼好用,冰河开源了这款精准定时任务和延时队列框架!!

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
日志服务 SLS,月写入数据量 50GB 1个月
简介: 在实际工作中,很多小伙伴在开发定时任务时,会采取定时扫描数据表的方式实现。然而,这种方式存在着重大的缺陷:如果数据量大的话,频繁的扫描数据表会对数据库造成巨大的压力;难以支撑大规模的分布式定时任务;难以支持精准的定时任务;大量浪费CPU的资源;扫描的数据大部分是不需要执行的任务。那么,既然定时扫描数据表存在这么多的弊端,那么,有没有一种方式来解决这些问题呢?今天,冰河就带着他的开源项目mykit-delay来了!!

项目简述

Mykit体系中提供的简单、稳定、可扩展的延迟消息队列框架,提供精准的定时任务和延迟队列处理功能。

项目模块说明

  • mykit-delay-common: mykit-delay 延迟消息队列框架通用工具模块,提供全局通用的工具类
  • mykit-delay-config: mykit-delay 延迟消息队列框架通用配置模块,提供全局配置
  • mykit-delay-queue:  mykit-delay 延迟消息队列框架核心实现模块,目前所有主要的功能都在此模块实现
  • mykit-delay-controller: mykit-delay 延迟消息队列框架Restful接口实现模块,对外提供Restful接口访问,兼容各种语言调用
  • mykit-delay-core: mykit-delay 延迟消息队列框架的入口,整个框架的启动程序在此模块实现
  • mykit-delay-test: mykit-delay 延迟消息队列框架通用测试模块,主要提供Junit单元测试用例

需求背景

  • 用户下订单后未支付,30分钟后支付超时
  • 在某个时间点通知用户参加系统活动
  • 业务执行失败之后隔10分钟重试一次

类似的场景比较多 简单的处理方式就是使用定时任务 假如数据比较多的时候 有的数据可能延迟比较严重,而且越来越多的定时业务导致任务调度很繁琐不好管理。

队列设计

整体架构设计如下图所示。

微信图片_20211120130419.jpg

开发前需要考虑的问题

  • 及时性 消费端能按时收到
  • 同一时间消息的消费权重
  • 可靠性 消息不能出现没有被消费掉的情况
  • 可恢复 假如有其他情况 导致消息系统不可用了 至少能保证数据可以恢复
  • 可撤回 因为是延迟消息 没有到执行时间的消息支持可以取消消费
  • 高可用 多实例 这里指HA/主备模式并不是多实例同时一起工作
  • 消费端如何消费

当然初步选用redis作为数据缓存的主要原因是因为redis自身支持zset的数据结构(score 延迟时间毫秒) 这样就少了排序的烦恼而且性能还很高,正好我们的需求就是按时间维度去判定执行的顺序 同时也支持map list数据结构。

简单定义一个消息数据结构

private String topic;/***topic**/
private String id;/***自动生成 全局唯一 snowflake**/
private String bizKey;
private long delay;/***延时毫秒数**/
private int priority;//优先级
private long ttl;/**消费端消费的ttl**/
private String body;/***消息体**/
private long createTime=System.currentTimeMillis();
private int status= Status.WaitPut.ordinal();

运行原理:

  • 用Map来存储元数据。id作为key,整个消息结构序列化(json/…)之后作为value,放入元消息池中。
  • 将id放入其中(有N个)一个zset有序列表中,以createTime+delay+priority作为score。修改状态为正在延迟中
  • 使用timer实时监控zset有序列表中top 10的数据 。如果数据score<=当前时间毫秒就取出来,根据topic重新放入一个新的可消费列表(list)中,在zset中删除已经取出来的数据,并修改状态为待消费
  • 客户端获取数据只需要从可消费队列中获取就可以了。并且状态必须为待消费 运行时间需要<=当前时间的 如果不满足 重新放入zset列表中,修改状态为正在延迟。如果满足修改状态为已消费。或者直接删除元数据。

客户端

因为涉及到不同程序语言的问题,所以当前默认支持http访问方式。

  • 添加延时消息添加成功之后返回消费唯一ID POST /push {…..消息体}
  • 删除延时消息 需要传递消息ID GET /delete?id=
  • 恢复延时消息 GET /reStore?expire=true|false expire是否恢复已过期未执行的消息。
  • 恢复单个延时消息 需要传递消息ID GET /reStore/id
  • 获取消息 需要长连接 GET /get/topic

用nginx暴露服务,配置为轮询 在添加延迟消息的时候就可以流量平均分配。

目前系统中客户端并没有采用HTTP长连接的方式来消费消息,而是采用MQ的方式来消费数据这样客户端就可以不用关心延迟消息队列。只需要在发送MQ的时候拦截一下 如果是延迟消息就用延迟消息系统处理。

消息可恢复

实现恢复的原理 正常情况下一般都是记录日志,比如mysql的binlog等。

这里我们直接采用mysql数据库作为记录日志。

目前创建以下2张表:

  • 消息表 字段包括整个消息体
  • 消息流转表 字段包括消息ID、变更状态、变更时间、zset扫描线程Name、host/ip

定义zset扫描线程Name是为了更清楚的看到消息被分发到具体哪个zset中。前提是zset的key和监控zset的线程名称要有点关系 这里也可以是zset key。

支持消息恢复

假如redis服务器宕机了,重启之后发现数据也没有了。所以这个恢复是很有必要的,只需要从表1也就是消息表中把消息状态不等于已消费的数据全部重新分发到延迟队列中去,然后同步一下状态就可以了。

当然恢复单个任务也可以这么干。

数据表设计

这里,我就直接给出创建数据表的SQL语句。

DROP TABLE IF EXISTS `mykit_delay_queue_job`;
CREATE TABLE `mykit_delay_queue_job` (
  `id` varchar(128) NOT NULL,
  `bizkey` varchar(128) DEFAULT NULL,
  `topic` varchar(128) DEFAULT NULL,
  `subtopic` varchar(250) DEFAULT NULL,
  `delay` bigint(20) DEFAULT NULL,
  `create_time` bigint(20) DEFAULT NULL,
  `body` text,
  `status` int(11) DEFAULT NULL,
  `ttl` int(11) DEFAULT NULL,
  `update_time` datetime(3) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `mykit_delay_queue_job_ID_STATUS` (`id`,`status`),
  KEY `mykit_delay_queue_job_STATUS` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for mykit_delay_queue_job_log
-- ----------------------------
DROP TABLE IF EXISTS `mykit_delay_queue_job_log`;
CREATE TABLE `mykit_delay_queue_job_log` (
  `id` varchar(128) NOT NULL,
  `status` int(11) DEFAULT NULL,
  `thread` varchar(60) DEFAULT NULL,
  `update_time` datetime(3) DEFAULT NULL,
  `host` varchar(128) DEFAULT NULL,
  KEY `mykit_delay_queue_job_LOG_ID_STATUS` (`id`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

关于高可用

分布式协调还是选用zookeeper。

如果有多个实例最多同时只能有1个实例工作 这样就避免了分布式竞争锁带来的坏处,当然如果业务需要多个实例同时工作也是支持的,也就是一个消息最多只能有1个实例处理,可以选用zookeeper或者redis就能实现分布式锁了。

最终做了一下测试多实例同时运行,可能因为会涉及到锁的问题性能有所下降,反而单机效果很好。所以比较推荐基于docker的主备部署模式。

运行模式

  • 支持 master,slave (HA)需要配置mykit.delay.registry.serverList zk集群地址列表
  • 支持 cluster 会涉及到分布式锁竞争 效果不是很明显  分布式锁采用redissetNx实现
  • StandAlone

目前,经过测试,推荐使用master slave的模式,后期会优化Cluster模式

如何接入

为了提供一个统一的精准定时任务和延时队列框架,mykit-delay提供了HTTP Rest接口供其他业务系统调用,接口使用简单方便,只需要简单的调用接口,传递相应的参数即可。

消息体

以JSON数据格式参数 目前只提供了http协议

  • body                    业务消息体
  • delay                   延时毫秒 距createTime的间隔毫秒数
  • id                         任务ID 系统自动生成 任务创建成功返回
  • status                  状态 默认不填写
  • topic                     标题
  • subtopic               保留字段
  • ttl                          保留字段
  • createTime           创建任务时间 非必填 系统默认

添加任务

/push  
    POST application/json
{"body":"{hello world}","delay":10000,"id":"20","status":0,"topic":"ces","subtopic":"",ttl":12}

删除任务

删除任务 需要记录一个JobId

/delete?jobId=xxx
   GET

恢复单个任务

用于任务错乱 脑裂情况 根据日志恢复任务

/reStoreJob?JobId=xxx
   GET

恢复所有未完成的任务

根据日志恢复任务

/reStore?expire=true
   GET

参数expire 表示是否需要恢复已过期还未执行的数据

清空队列数据

根据日志中未完成的数据清空队列中全部数据。清空之后 会删除缓存中的所有任务

/clearAll
 GET

客户端获取队列方式

目前默认实现了RocketMQActiveMQ的推送方式。依赖MQ的方式来实现延时框架与具体业务系统的耦合。

消息体中消息与RocketMQActiveMQ 消息字段对应关系

mykit-delay RocketMQ ActiveMQ 备注
topic topic topic 点对点发送队列名称或者主题名称
subtopic subtopic subtopic 点对点发送队列子名称或者主题子名称
body 消息内容 消息内容 消息内容

关于系统配置

延迟框架与具体执行业务系统的交互方式通过延迟框架配置实现,具体配置文件位置为mykit-delay-config项目下的resources/properties/starter.properties文件中。

测试

需要配置好数据库地址和Redis的地址 如果不是单机模式 也需要配置好Zookeeper

运行mykit-delay-test模块下的测试类io.mykit.delay.test.PushTest添加任务到队列中

启动mykit-delay-test模块下的io.mykit.delay.TestDelayQueue消费前面添加数据 为了方便查询效果 默认的消费方式consoleCQ 控制台输出

扩展

支持zset队列个数可配置 避免大数据带来高延迟的问题。

近期规划

  • 分区(buck)支持动态设置
  • redis与数据库数据一致性的问题 (重要
  • 实现自己的推拉机制
  • 支持可切换实现方式 目前只是依赖Redis实现,后续待优化
  • 支持Web控制台管理队列
  • 实现消息消费TTL机制

如果这款开源框架对你有帮助,请小伙伴们打开github链接:https://github.com/sunshinelyz/mykit-delay  ,给个Star,让更多的小伙伴看到,减轻工作中繁琐的扫描数据表的定时任务开发。也希望能够有越来越多的小伙伴参与这个开源项目,我们一起养肥它!!


好了,不早了,今天就到这儿吧,我是冰河,我们下期见!!

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
消息中间件 存储 负载均衡
🔥产品:直播送礼延迟这么大,你就不能快点吗
先赞后看,南哥助你Java进阶一大半其实抖音的实时音视频技术RTC,是来源于火山引擎RTC的支持,抖音、火山引擎、巨量引擎属于字节跳动公司旗下的不同业务板块。我是南哥,一个Java学习与进阶的领路人。相信对你通关面试、拿下Offer进入心心念念的公司有所帮助。
🔥产品:直播送礼延迟这么大,你就不能快点吗
|
4月前
|
开发者 缓存 数据库
【性能奇迹】Wicket应用的极速重生:揭秘那些让开发者心跳加速的调优秘技!
【8月更文挑战第31天】在软件开发中,性能优化是确保应用快速响应和高效运行的关键。本书《性能调优:Apache Wicket应用的速度提升秘籍》详细介绍了如何优化Apache Wicket应用,包括代码优化、资源管理、数据库查询优化、缓存策略及服务器配置等方面。通过减少不必要的组件渲染、优化SQL查询、使用缓存和调整服务器设置等方法,本书帮助开发者显著提升Wicket应用的性能,确保其在高并发和数据密集型场景下的稳定性和响应速度。
48 0
|
7月前
|
存储 SQL 前端开发
🚀经常发文章的你是否想过定时发布是咋实现的?🚀
🚀经常发文章的你是否想过定时发布是咋实现的?🚀
|
7月前
|
消息中间件 算法 Java
金石推荐 |【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(上)
金石推荐 |【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(上)
116 1
|
7月前
|
存储 算法 Java
金石推荐 | 【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(下)(一)
金石推荐 | 【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(下)
131 1
|
7月前
|
算法 Java 索引
金石推荐 | 【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(下)(二)
金石推荐 | 【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(下)
68 1
|
7月前
|
存储 消息中间件 算法
精华推荐 |【算法数据结构专题】「延时队列算法」史上非常详细分析和介绍如何通过时间轮(TimingWheel)实现延时队列的原理指南
精华推荐 |【算法数据结构专题】「延时队列算法」史上非常详细分析和介绍如何通过时间轮(TimingWheel)实现延时队列的原理指南
156 1
|
Web App开发 缓存 负载均衡
阿里技术官面鹅厂,被高并发问蒙,含泪整理全网最全线程并发文档
当你开始开始去跳槽面试的时候,明明只是一份15K的工作,却问你有没有高并发、分布式经验,火箭造的让你猝不及防,结果就是凉凉。现如今市场高并发编程、分布式、负载均衡、集群等可以说是现在高级架构后端求职的必备技能。
|
存储 算法 Java
【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(下)
承接上一篇文章【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(上)】我们基本上对层级时间轮算法的基本原理有了一定的认识,本章节就从落地的角度进行分析和介绍如何通过Java进行实现一个属于我们自己的时间轮服务组件,最后,在告诉大家一下,其实时间轮的技术是来源于生活中的时钟。
143 1
【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(下)
|
安全 5G 定位技术
IM开发者的零基础通信技术入门(十二):上网卡顿?网络掉线?一文即懂!
本文将详细介绍生活中遇到的常见网络问题,及可能的解决方法,虽说是一篇技术文章,但内容将一如既往地通俗易懂,简单实用。
126 0

热门文章

最新文章