任务调度系统就该这么设计(万能通用),稳的一批! 上

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
云原生网关 MSE Higress,422元/月
简介: 任务调度系统就该这么设计(万能通用),稳的一批!上


阅读一篇「定时任务框架选型」的文章时,一位网友的留言 到了我:

我看过那么多所谓的教程,大部分都是教“如何使用工具”的,没有多少是教“如何制作工具”的,能教“如何仿制工具”的都已经是凤毛麟角,中国 软件行业,缺的是真正可以“制作工具”的程序员,而绝对不缺那些“使用工具”的程序员!......  ”这个业界最不需要的就是“会使用XX工具的工程师”,而是“有创造力的软件工程师”!业界所有的饭碗,本质就是“有创造力的软件工程师”提供出来的啊!

写这篇文章,想和大家从头到脚说说任务调度,希望大家读完之后,能够理解实现一个任务调度系统的核心逻辑。

1 Quartz

Quartz是一款Java开源任务调度框架,也是很多Java工程师接触任务调度的起点。

下图显示了任务调度的整体流程:

Quartz的核心是三个组件。

  • 任务:Job 用于表示被调度的任务;
  • 触发器:Trigger 定义调度时间的元素,即按照什么时间规则去执行任务。一个Job可以被多个Trigger关联,但是一个Trigger 只能关联一个Job;
  • 调度器 :工厂类创建Scheduler,根据触发器定义的时间规则调度任务。

上图代码中Quartz 的JobStore是 RAMJobStore ,Trigger 和 Job 存储在内存中。

执行任务调度的核心类是 QuartzSchedulerThread

  1. 调度线程从JobStore中获取需要执行的的触发器列表,并修改触发器的状态;
  2. Fire 触发器,修改触发器信息(下次执行触发器的时间,以及触发器状态),并存储起来。
  3. 最后创建具体的执行任务对象,通过worker线程池执行任务。

接下来再聊聊 Quartz 的集群部署方案。

Quartz的集群部署方案,需要针对不同的数据库类型(MySQL ,  ORACLE) 在数据库实例上创建Quartz表,JobStore是:  JobStoreSupport

这种方案是分布式的,没有负责集中管理的节点,而是利用数据库行级锁的方式来实现集群环境下的并发控制。

scheduler实例在集群模式下首先获取{0}LOCKS表中的行锁,Mysql 获取行锁的语句:

{0}会替换为配置文件默认配置的QRTZ_。sched_name为应用集群的实例名,lock_name就是行级锁名。Quartz主要有两个行级锁触发器访问锁 (TRIGGER_ACCESS ) 和 状态访问锁(STATE_ACCESS )。

这个架构解决了任务的分布式调度问题,同一个任务只能有一个节点运行,其他节点将不执行任务,当碰到大量短任务时,各个节点频繁的竞争数据库锁,节点越多性能就会越差。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

2 分布式锁模式

Quartz的集群模式可以水平扩展,也可以分布式调度,但需要业务方在数据库中添加对应的表,有一定的强侵入性。

有不少研发同学为了避免这种侵入性,也探索出分布式锁模式

业务场景:电商项目,用户下单后一段时间没有付款,系统就会在超时后关闭该订单。

通常我们会做一个定时任务每两分钟来检查前半小时的订单,将没有付款的订单列表查询出来,然后对订单中的商品进行库存的恢复,然后将该订单设置为无效。

我们使用Spring Schedule的方式做一个定时任务。

@Scheduled(cron = "0 */2 * * * ? ")
public void doTask() {
   log.info("定时任务启动");
   //执行关闭订单的操作
   orderService.closeExpireUnpayOrders();
   log.info("定时任务结束");
 }

在单服务器运行正常,考虑到高可用,业务量激增,架构会演进成集群模式,在同一时刻 有多个服务执行一个定时任务,有可能会导致业务紊乱。

解决方案是在任务执行的时候,使用Redis 分布式锁来解决这类问题。

@Scheduled(cron = "0 */2 * * * ? ")
public void doTask() {
    log.info("定时任务启动");
    String lockName = "closeExpireUnpayOrdersLock";
    RedisLock redisLock = redisClient.getLock(lockName);
    //尝试加锁,最多等待3秒,上锁以后5分钟自动解锁
    boolean locked = redisLock.tryLock(3, 300, TimeUnit.SECONDS);
    if(!locked){
        log.info("没有获得分布式锁:{}" , lockName);
        return;
    }
    try{
       //执行关闭订单的操作
       orderService.closeExpireUnpayOrders();
    } finally {
       redisLock.unlock();
    }
    log.info("定时任务结束");
}

Redis的读写性能极好,分布式锁也比Quartz数据库行级锁更轻量级。当然Redis锁也可以替换成Zookeeper锁,也是同样的机制。

在小型项目中,使用:定时任务框架(Quartz/Spring Schedule)和 分布式锁(redis/zookeeper)有不错的效果。

但是呢?我们可以发现这种组合有两个问题:

  1. 定时任务在分布式场景下有空跑的情况,而且任务也无法做到分片;
  2. 要想手工触发任务,必须添加额外的代码才能完成。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

3 ElasticJob-Lite 框架

ElasticJob-Lite 定位为轻量级无中心化解决方案,使用 jar 的形式提供分布式任务的协调服务。

应用内部定义任务类,实现SimpleJob接口,编写自己任务的实际业务流程即可。

public class MyElasticJob implements SimpleJob {
    @Override
    public void execute(ShardingContext context) {
        switch (context.getShardingItem()) {
            case 0:
                // do something by sharding item 0
                break;
            case 1:
                // do something by sharding item 1
                break;
            case 2:
                // do something by sharding item 2
                break;
            // case n: ...
        }
    }
}

举例:应用A有五个任务需要执行,分别是A,B,C,D,E。任务E需要分成四个子任务,应用部署在两台机器上。

应用A在启动后, 5个任务通过 Zookeeper 协调后被分配到两台机器上,通过Quartz Scheduler 分开执行不同的任务。

ElasticJob 从本质上来讲 ,底层任务调度还是通过 Quartz ,相比Redis分布式锁 或者 Quartz 分布式部署 ,它的优势在于可以依赖 Zookeeper 这个大杀器 ,将任务通过负载均衡算法分配给应用内的 Quartz Scheduler容器。

从使用者的角度来讲,是非常简单易用的。但从架构来看,调度器和执行器依然在同一个应用方JVM内,而且容器在启动后,依然需要做负载均衡。应用假如频繁的重启,不断的去选主,对分片做负载均衡,这些都是相对比较 的操作。

ElasticJob 的控制台通过读取注册中心数据展现作业状态,更新注册中心数据修改全局任务配置。从一个任务调度平台的角度来看,控制台功能还是偏孱弱的。

相关实践学习
基于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
相关文章
|
2月前
|
存储 资源调度 Serverless
阿里巴巴经济体核心调度系统“伏羲”设计问题之伏羲系统的功能如何解决
阿里巴巴经济体核心调度系统“伏羲”设计问题之伏羲系统的功能如何解决
43 0
|
3月前
|
缓存 人工智能
通用研发提效问题之女娲的缓存方案,体现易用性的四重境界,如何解决
通用研发提效问题之女娲的缓存方案,体现易用性的四重境界,如何解决
|
3月前
|
数据采集 运维 监控
软件研发核心问题之用户行为采集容易出的问题如何解决
软件研发核心问题之用户行为采集容易出的问题如何解决
|
3月前
|
存储 缓存 运维
通用研发提效问题之什么是通用化方案,提高女娲的适用性如何解决
通用研发提效问题之什么是通用化方案,提高女娲的适用性如何解决
|
测试技术
【项目实战典型案例】10.对生产环境以及生产数据的敬畏
【项目实战典型案例】10.对生产环境以及生产数据的敬畏
|
算法 机器人 区块链
数字货币量化机器人系统开发(项目案例)/功能说明/逻辑方案/源码平台
  简单地说,量化交易机器人就是能够自动执行交易策略的交易软件。它借助于计算机技术和数学模型,对市场行情进行分析预测,并根据程序设定的规则和条件自动执行交易策略,完成交易操作。Compared with traditional manual trading,quantitative trading robots have faster trading speed,lower transaction costs,and higher trading efficiency.
|
人工智能 文字识别 NoSQL
风控系统就该这么设计,万能通用,稳的一批!(建议收藏)
风控系统就该这么设计,万能通用,稳的一批!(建议收藏)
188 0
风控系统就该这么设计,万能通用,稳的一批!(建议收藏)
|
消息中间件 资源调度 分布式计算
任务调度系统就该这么设计(万能通用),稳的一批! 下
任务调度系统就该这么设计(万能通用),稳的一批! 下
|
负载均衡 监控 安全
网关系统就该这么设计,万能通用,稳的一批!
网关系统就该这么设计,万能通用,稳的一批!
|
消息中间件 人工智能 JavaScript
风控系统就该这么设计(万能通用),稳的一批!
风控系统就该这么设计(万能通用),稳的一批!
下一篇
无影云桌面