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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 任务调度系统就该这么设计(万能通用),稳的一批!上


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

我看过那么多所谓的教程,大部分都是教“如何使用工具”的,没有多少是教“如何制作工具”的,能教“如何仿制工具”的都已经是凤毛麟角,中国 软件行业,缺的是真正可以“制作工具”的程序员,而绝对不缺那些“使用工具”的程序员!......  ”这个业界最不需要的就是“会使用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
相关文章
|
11天前
|
存储 弹性计算 关系型数据库
活动实践 | 告别资源瓶颈,函数计算驱动多媒体文件处理测评
本方案介绍了一种高效处理文件的方法,适用于企业办公和社交媒体应用。通过阿里云的函数计算、对象存储OSS和轻量消息队列,实现文件的异步处理,如格式转换和水印添加,有效减轻了核心应用的负担,提高了业务稳定性和资源利用率。方案包括云服务器ECS、云数据库RDS、OSS存储等组件,支持快速部署和资源清理。
|
5月前
|
Java Spring
通用研发提效问题之配置的若干场景下若干方案的变化该如何解决
通用研发提效问题之配置的若干场景下若干方案的变化该如何解决
|
人工智能 文字识别 NoSQL
风控系统就该这么设计,万能通用,稳的一批!(建议收藏)
风控系统就该这么设计,万能通用,稳的一批!(建议收藏)
199 0
风控系统就该这么设计,万能通用,稳的一批!(建议收藏)
|
消息中间件 资源调度 分布式计算
任务调度系统就该这么设计(万能通用),稳的一批! 下
任务调度系统就该这么设计(万能通用),稳的一批! 下
|
存储 设计模式 Oracle
关于分投趣质押模式开发系统搭建原理
存储在链上的数据未以可以有效或轻松地被Dapp使用的格式注册。为了做到这一点,数据需要被索引和组织以进行高效检索。
|
负载均衡 监控 安全
网关系统就该这么设计,万能通用,稳的一批!
网关系统就该这么设计,万能通用,稳的一批!
|
消息中间件 人工智能 JavaScript
风控系统就该这么设计(万能通用),稳的一批!
风控系统就该这么设计(万能通用),稳的一批!
|
负载均衡 监控 安全
网关系统就该这么设计(万能通用),稳的一批!
网关系统就该这么设计(万能通用),稳的一批!
|
资源调度 分布式计算 Kubernetes
技术抉择:阿里云13年后重构全部核心调度系统
在阿里云十三年的发展历史上,重新设计调度系统算得上是一个重要的技术抉择。
1377 10
技术抉择:阿里云13年后重构全部核心调度系统
|
存储 JSON NoSQL
「数据密集型系统搭建」原理篇|夯实基础,灵活设计
数据建模规范、常识、技巧很多,本章从万事开头难的数据建模开始,剖析下数据选择上有哪些常见设计规则,看看这些约束或经验背后蕴含着哪些出色的项目实践总结,在数据类型的选择上如何进行合理选择和取舍方案的。
488 0
「数据密集型系统搭建」原理篇|夯实基础,灵活设计
下一篇
DataWorks