如何使用Redis让周期异步任务变得Fault-tolerant且Dynamic

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介:         Python技术栈的同学一定都非常了解Celery——基于消息队列的分布式任务调度系统。(具体用法介绍不在此赘述)。通过Celery可以快速高效的将大规模的任务实时分发到众多的不同的机器上,让用户只关注每个单独任务的处理,而非调度分配任务本身。

        Python技术栈的同学一定都非常了解Celery——基于消息队列的分布式任务调度系统。(具体用法介绍不在此赘述)。通过Celery可以快速高效的将大规模的任务实时分发到众多的不同的机器上,让用户只关注每个单独任务的处理,而非调度分配任务本身。另外,Celery中有一个非常好用的周期任务发生器Beat,通过此进程可以帮助我们快速开发出大规模的周期任务,也就是任务的生产者。例如我们当前的应用每天就会产生15万以上的周期任务,但是与之相伴随的就是一个非常重要的问题,原生Beat在Celery中是一个单点进程,不具备容错能力。所以如何让周期任务发生器变得具有容错性(Fault-tolerant)——即周期任务产生器进程Beat挂掉后(或者机器宕机)快速恢复进程,是必须要考虑的问题。Celery在某种程度上是具有容错性的,但高可用性也是由条件的:需要保证所使用的消息中间件的高可用性,并且只保证消息存储和消费过程的容错性。而另一个问题就是原生Beat不支持动态添加/删除/修改(Dynamic)周期任务的方法,当应用的周期任务越来越多,不能因为新增一个任务而重启整个Beat进程,导致其他任务周期产生影响。

        当然,开源对于这两类问题,已经有很多成熟的解决方法,往往大家都殊途同归,借助消息中间件的高可用性,来进行故障转移。这里我也是站在别人的肩上,看看redbeat如何借助Redis来做到周期任务的高可用性(Fault-tolerant),以及周期任务的弹性动态变化能力(Resilient)。

一、原生Celery4.x Beat是如何产生周期任务的?

        Celery4.x中默认使用的是PersistentScheduler作为默认调度器的持久化方式,即使用写文件的方式(序列化)记录所有周期任务的最后执行时间last_run_at以及周期调度的频率(interval/crontab),Celery支持用户自己实现持久化的方式如将每次调度任务的时间信息,记录在mysql数据库,redis/mongodb等。用户可以通过重载PersistentScheduler,自己实现存储调度任务的信息的方式。如对于django用户可以使用django-celery-beat实现通过控制台进行动态的创建,编辑,删除周期性任务。但django-celery-beat暂不确定是否可以保证容错性的beat(即允许多个beat进程同时调度)。不过,我们可以确定,要实现Beat的Fault-tolerant&Dynamic,必须要将待调度的周期性任务的相关信息,存储到某种类型的后端数据库,无论是mysql还是nosql的redis等。

        下面我们用一张图来说明整个原生Beat的调度方式

        由上图可以了解到,Celery4.x中使用了最小堆的方式来计算下一次需要触发调度的周期性任务(Celery3.x并未使用此数据结构)。同时会在内存后端存储(数据库/文件)中记录每个任务最后一次的调度时间。但是从原生Beat中也可以看出,只有当重启beat进程时才能对周期性任务进行增删改操作,并无法进行动态修改,并且不支持容错行为。

二、Redbeat如何重载原生Scheduler?

        使beat做到Fault-tolerant特性,重要的一点是有多节点的beat进程冗余。而要想动态的增加/删出/改变周期性任务,则需将每个任务的调度相关信息,存储在某一中心化存储中,如mysql/redis。将调度信息在后端进行存储也进一步保证了beat调度的任务准确性和唯一性。 在实现上需要做到以下五点。

  • 重载Scheduler:初始化加载以及更新所有的周期性任务,将所有周期性任务的定时信息,参数等防止到redis中存储。
  • 重载ScheduleEntry: 每一个周期性任务对应一个entry,记录任务的调度周期,参数以及最近最后一次调度时间,并序列化至redis,用key-value方式记录这些信息。
  • 使用redis中的Sorted Set数据结构存储每个任务下一次调度的时间,代替最小堆。
  • 需要保证所有的任务的定时信息如interval/crontab等参数,均获取自redis/任何中心化存储(如diamond),保证任务调度信息可以动态变化
  • 利用简易的redis分布式锁实现Fault-tolerant,同时保证一个时刻只能有一个beat进程真实进行调度

      下图简单的表达整个Redbeat的调度流程。

        可以看到beat进程的执行中,通过分布式锁保证同一时刻仅有一个beat进程进行调度,其他进程均会被阻塞,直到调度进程挂掉,超时后释放锁。另外通过redis中Sorted Set数据结构模拟原生beat中的最小堆判断本次应该调度的任务。整个过程相对精简直观。

        对于动态改变Beat中周期任务的行为,可以分为新增/删除/修改定期任务执行周期三种。

  • 新增任务:1. 将新增任务名插入至statics中,2.并且任务定义调度等信息放入对应任务名的hash对象中。3.同时把任务及对应的当前时间插入至schedule的Redis对象中,触发下一轮调度。
  • 删除任务:1. 把任务相关定义调度信息所对应的hash对象删除,从而使下一轮调度时无法获取对应信息,进而调度失败。2. 删除statics中改任务对应的名称
  • 修改定期任务执行的周期:直接修改任务名在redis中对应的hash对象内的调度信息即可

        至此,一个简单的具有容错和动态改变任务调度信息的周期性任务即完成。

三、总结:

      当然此实现也有一些小的缺陷。比如当beat挂掉后,最长的恢复时间取决于redis_lock的超时时间,并且保证高可用性的最为重要的问题,即要保证redis的高可用性。对于这个问题,本人暂未深入研究。 另外对于整个当前Redbeat实现的性能的极限也并没有进行测试,对于我们的应用来说,共有400+的周期性任务,并且平均一天内这些任务一共会执行15万次,此实现对于满足我们的需求已足够。

参考文章源码:

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
14天前
|
NoSQL Java API
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
|
6月前
|
存储 监控 负载均衡
保证Redis的高可用性是一个涉及多个层面的任务,主要包括数据持久化、复制与故障转移、集群化部署等方面
【5月更文挑战第15天】保证Redis高可用性涉及数据持久化、复制与故障转移、集群化及优化策略。RDB和AOF是数据持久化方法,哨兵模式确保故障自动恢复。Redis Cluster实现分布式部署,提高负载均衡和容错性。其他措施包括身份认证、多线程、数据压缩和监控报警,以增强安全性和稳定性。通过综合配置与监控,可确保Redis服务的高效、可靠运行。
226 2
|
6月前
|
缓存 NoSQL Java
面试官:Redis如何实现延迟任务?
延迟任务是计划任务,用于在未来特定时间执行。常见应用场景包括定时通知、异步处理、缓存管理、计划任务、订单处理、重试机制、提醒和数据采集。Redis虽无内置延迟任务功能,但可通过过期键通知、ZSet或Redisson实现。然而,这种方法精度有限,稳定性较差,适合轻量级需求。Redisson的RDelayedQueue提供更简单的延迟队列实现。
447 9
|
6月前
|
存储 缓存 NoSQL
Redis实现延迟任务的几种方案
Redis实现延迟任务的几种方案
|
6月前
|
存储 NoSQL Java
Redis 实现延迟任务的深度解析
【4月更文挑战第17天】
255 0
|
6月前
|
监控 NoSQL 测试技术
python使用Flask,Redis和Celery的异步任务
python使用Flask,Redis和Celery的异步任务
|
6月前
|
存储 NoSQL API
【小小思考】Redis实现去重任务队列
【2月更文挑战第1天】思考一下如何用Redis实现去重的任务队列,主要有List 、List + Set/Hash/Bloom Filter、ZSet、Lua和开源库等方式。
235 1
|
NoSQL 安全 容灾
1分钟实现Redis数据迁移任务
NineData 基于全量复制、增量日志复制技术,提供了高效、安全可靠的 Redis 不停机迁移方案。当然,除了 Redis,NineData 已经支持数十种常见数据库的迁移复制,实现数据库迁移、数据容灾、数据双活、数据仓库实时集成等业务场景。同时,除了 SAAS 模式外,还提供了企业专属集群模式,满足企业最高的数据安全合规要求。
263 0
面试官:Redis分布式锁超时了,任务还没执行完怎么办?
今天主要分享的是面试中常见的redis的一些面试内容。如果你正好需要刚好可以帮你回顾一下,如果不需要可以收藏起来后面用到的时候翻出来回顾。
|
NoSQL Redis
【Redis原理机制 四】基于Redis实现延时任务
【Redis原理机制 四】基于Redis实现延时任务
56 0