关于spring:分布式任务调度你知道和不知道的事

6次阅读

共计 10019 个字符,预计需要花费 26 分钟才能阅读完成。

导语

对于定时工作大家应该都不会生疏,从骨灰级别的 Crontab 到 Spring Task,从 QuartZ 到 xxl-job,随着业务场景越来越多样简单,定时工作框架也在一直的降级进化。

那么明天就来跟大家从以下三个方面聊一聊分布式任务调度:从单机定时工作到分布式任务调度平台的演进过程、腾讯云分布式任务调度平台 TCT 是如何应运而生的、TCT 具体落地案例状况和解决了哪些外围问题。

作者介绍

崔凯

腾讯云 CSIG 微服务产品核心产品架构师
多年分布式、高并发电子商务系统的研发、零碎架构设计教训,善于支流微服务架构技术平台的落地和施行
目前专一于微服务架构相干中间件的钻研推广和最佳实际的积淀,致力于帮忙企业实现数字化转型

场景类型

定时工作的场景千千万,但它的实质模型是个啥,怎么来了解定时工作呢,此处好有一比。定时工作其实就是 老师给学生安排作业,比方:

每天晚上 7 点准时开始写作业,写完了让家长查看签字。

“每天晚上 7 点准时”是对工夫精度和周期性的要求,是 7 点不是 8 点,是每天不是每周;“写作业”是对工作执行内容的明确,是写作业不是看奥特曼;“写完了让家长查看签字”使得“写作业”和“家长查看签字”能够解耦为两个逻辑动作,在孩子写作业的时候家长还能看个看书啥的。

言归正传,定时工作的典型落地场景在各行业中十分广泛:电商中定时开启促销流动入口、15 天未确认收货则主动确认收货、定点扫描未付款订单进行短信揭示等;金融保险行业中也有营销人员佣金计算、终端营销报表制作、组织关系定时同步、日清月清结算等场景。总结下来,笔者依照 工夫驱动、批量解决、异步解耦 三个维度来划分定时工作的场景类型。

工夫驱动型

以电商场景中定时开启流动入口为例,个别状况会在后盾配置好流动须要的各种参数,同时将活动状态的动静配置设置为敞开,当达到执行工夫后定时工作主动触发后开启促销流动。

可见,在工夫驱动型场景中,相比执行内容而言,业务更关注的是工作是定时执行还是周期执行、执行具体工夫点准确性、周期频率的长短等工夫因素。

批量解决型

批量解决型工作的特点在于须要 同时对大量 积攒的业务对象进行解决。此时,可能有的敌人会问,为什么不应用音讯队列解决?起因是某些特定的场景下,音讯队列并不可能进行简略代替,因为音讯队列更多的是通过每个音讯进行事件驱动,偏差更实时的解决。

以保险中佣金结算业务阐明,比方营销人员的佣金计算。营销人员会从投保人缴纳的保费中取得肯定比例的提成,并且这个比例会依据投保年限、险种的不同而变动,另外可能还会叠加公司的一些佣金激励政策等。这样的场景就须要积攒一定量的数据之后,定时的进行批量计算,而不能每个事件都进行计算。

异步解耦型

说到零碎的异步解耦肯定又会想到音讯队列,但音讯队列并不能实用某些内部零碎数据的获取,比方证券行业中股票软件公司对于交易所股票价格的抓取,因为股票价格对于股票软件公司是内部数据,应用音讯队列是很难进行内外部零碎间异步通信的。所以,个别状况会通过批处理工作定时抓取数据并存储,而后后端系统再对数据进行剖析整顿,使得内部数据获取和外部数据分析解决两局部逻辑解耦。

前世今生

单机定时工作

单机定时工作是最常见的,也是比拟传统的工作执行形式,比方 linux 内置的 Crontab。其通过 cron 表达式中分、时、日、月、周五种工夫维度,实现单机定时工作的执行。

# 每晚的 21:30 重启 smb
30 21 * * * /etc/init.d/smb restart

另外,在 java 中也有内置的定时工作,比方 java.util.Timer 类和它的升级版 ScheduledThreadPoolExecutor,另外在 Spring 体系中也提供了 Spring Task 这种通过注解疾速实现反对 cron 表达式的单机定时工作框架。

@EnableScheduling
@Service
public class ScheduledConsumerDemo {@Value("${consumer.request.echoUrl}")
    private String echoUrl;

    /**
     * 距离 1 秒申请 provider 的 echo 接口
     *
     * @throws InterruptedException
     */
    @Scheduled(fixedDelayString = "${consumer.auto.test.interval:1000}")
    public void callProviderPer1Sec() throws InterruptedException {String response = restTemplate.getForObject(echoUrl, String.class);
    }
}

不言而喻的,单机定时工作在应答简略的业务场景是很不便的,但在分布式架构未然成为趋势的当初,单机定时工作并 不能满足企业级生产以及工业化场景的诉求 ,次要体现在 集群工作配置对立治理、单点故障及单点性能、节点间工作的通信及协调、工作执行数据汇总等 方面。为了满足企业级生产的诉求,各类任务调度平台逐渐衰亡。

中心化调度

典型的中心化调度框架 quartz,其作为任务调度界的前辈和带头大哥,通过优良的调度能力、丰盛的 API 接口、Spring 集成性好等长处,使其一度成为任务调度的代名词。

quartz 架构中应用数据库锁保障多节点工作执行时的唯一性,解决了单点故障的问题。但数据库锁的集中性也产生了重大的性能问题,比方大批量工作场景下,数据库成为了业务整体调度的性能瓶颈,同时在利用侧还会造成局部资源的期待闲置,另外还做不到工作的并行分片

另一款出自公众点评的框架 xxl-job,次要特点在于简略、易集成、有可视化控制台,相比 quartz 次要差别在于:

  • 自研调度模块

xxl-job 将 调度模块 工作模块 解耦的异步化设计,解决了调度工作逻辑并重时,调度零碎性能大大降低的问题。其中,调度模块次要负责工作参数的解析及调用发动,工作模块则负责工作内容的执行,同时 异步调度队列 异步执行队列 的优化,使得无限的线程资源也可撑持一定量的 job 并发。

  • 调度优化

通过 调度线程池、并行调度 的形式,极大减小了调度阻塞的概率,同时进步了调度零碎的承载量。

  • 高可用保障

调度核心的数据库中会保留工作信息、调度历史、调度日志、节点注册信息等,通过 MySQL 保证数据的长久化和高可用。工作节点的 故障转移(failover)模式和心跳检测,也会动静感知每个执行节点的状态。

但因为 xxl-job 应用了跟 quartz 相似的数据库锁机制,所以同样不能防止数据库成为性能瓶颈以及中心化带来的其它问题。

去中心化调度

为了解决中心化调度存在的各种问题,国内开源框架也是八仙过海、尽显神通,比方口碑还不错的 powerjob、当当的 elastic-job、唯品会的 saturn。saturn 整体上是基于开源的 elastic-job 进行改良优化的,所以本文只针对 powerjob 和 elastic-job 做简要介绍。

powerjob 诞生于 2020 年 4 月,其中蕴含了一些比拟新的思路和元素,比方 反对基于 MapReduce 的分布式计算、动静热加载 Spring 容器等 。在性能上, 多任务工作流编排、MapReduce 执行模式、提早执行 是亮点,同时声称所有组件都反对程度扩大,其外围组件阐明如下:

  • powerjob-server:调度核心,对立部署,负责任务调度和治理;
  • powerjob-worker:执行器,提供单机执行、播送执行和分布式计算;
  • powerjob-client:可选组件,OpenAPI 客户端。

powerjob 在解决中心化调度时的无锁调度设计思路值得借鉴,外围逻辑是 通过 appName 作为业务利用分组的 key,将 powerjob-server 和 powerjob-worker 以分组 key 进行逻辑绑定,即确保每个 powerjob-worker 集群在运行时只会连贯到一台 powerjob-server,这样就不须要锁机制来避免工作被多台 server 同时拿到,从而造成反复执行的问题。

尽管 powerjob 在各方面剖析下来绝对优良,但毕竟产品迭代周期比拟短,仍须要通过市场大规模利用来一直打磨产品细节,以验证产品的性能、易用性和稳定性。

elasticjob 蕴含 elasticjob-lite 和 elasticjob-cloud 两个独立子项目,本文次要以 elasticjob-lite 为例开展。

elasticjob-lite 定位为轻量级无中心化解决方案,在继承 quartz 的根底上,同时应用了 zookeeper 作为注册核心。在产品设计层面上,集体了解 elasticjob 相比其余分布式任务调度框架,更加偏重数据处理和计算,次要体现在如下两方面:

elasticjob-lite 的无中心化

  • 没有调度核心的设计,在业务程序引入 elasticjob 的 jar 包后,由 jar 包进行工作的调度、状态通信、日志落盘等操作。
  • 每个工作节点间都是对等的,会在 zookeeper 中注册工作相干的信息(工作名称、对等实例列表、执行策略等),同时依赖 zookeeper 的选举机制进行执行实例的选举。

elasticjob-lite 的弹性分片

  • 基于 zookeeper,工作执行实例之间能够近乎实时的感知到对方的高低线状态,使得工作分片的调配能够随着工作实例数量的调整而调整,并且保障负载绝对平均。
  • 在工作实例高低线时,并不会影响以后的工作,会在下次任务调度的时候从新分片,以防止工作的反复执行。

通过上述剖析,elasticjob 更多的是针对分布式工作计算场景设计,更适宜做大量数据的分片计算或解决,尤其对资源利用率有要求的场景下更有劣势。

演进过程

在粗略的介绍了各个支流的分布式任务调度框架后,一个问题呈现了:是哪些次要因素推动了框架一步步倒退演进?笔者简要概括为如下 4 个因素:

  • 业务复杂性:原先的业务复杂性低,2、3 行代码就能够搞定;随着业务复杂性进步,工作的组织状态和执行内容都产生了很大变动,逐渐衍生出 工作编排、框架生态交融、多语言及多终端反对 等诉求。
  • 场景多样性:不再仅仅是简略的定时工作执行,相似批量计算、业务解耦等场景的问题,也逐渐开始应用分布式任务调度框架解决。对框架能力的要求在于,更丰盛的工作执行策略、动静分片计算的反对、丰盛的工作治理能力等 方面上。
  • 分布式架构:分布式架构趋势的全面到来,是最重要的推动因素。框架的整体设计须以分布式架构为前提,在 工作节点及调度核心间的通信、调度平台的高可用、工作节点的故障解决及复原、任务调度可视化运维等 方面,都是全新的挑战。
  • 海量数据并发:当海量的业务数据及并发调用成为常态,就使得分布式任务调度平台须要在 执行器性能、执行工夫精准度、工作的并行及异步解决、节点资源弹性管控 等方面推动优化,以帮忙晋升平台整体的吞吐量。

分布式任务调度框架的演进,是业务零碎从单体架构向分布式架构演进的一个分支。分布式任务调度平台能力的不断完善,与业务架构的微服务化演进不可分割。

同理,目前各行业的业务零碎逐渐迁徙上云,企业数字化转型趋势显著,将来分布式任务调度平台的演进过程同样离不开云原生产业环境,平台的整体架构须要深度交融云原生体系,能力满足将来多方面一直变动的产业诉求。

“云上”的 TCT

分布式任务调度服务(Tencent Cloud Task)是腾讯云自主研发的一款轻量级、高牢靠的分布式任务调度平台。通过指定工夫规定,严格触发调度工作,保障调度工作的牢靠、有序执行。该服务反对国内通用的工夫表达式、执行生命周期治理,解决传统定时调度工作的单点故障及可视化水平低等问题。同时,反对工作分片、工作流编排等简单调度工作解决能力,笼罩宽泛的任务调度利用场景,如数据备份、日志切分、运维监控、金融日切等。

性能介绍

TCT 在性能方面次要分为三个局部:调度治理平台、任务调度服务、开发集成(SDK)。调度治理平台提供优雅的可视化界面交互,任务调度服务实现分布式场景下的任务调度,开发集成深度交融开源框架,其中具体性能特点阐明如下。

丰盛的工作配置

  • 多种执行形式:反对随机节点、播送、分片执行形式,满足不同利用场景。
  • 多种触发策略:反对定时触发、周期触发、工作流触发、人工手动触发策略。
  • 欠缺的容错机制:反对异样重试、超时中断、手动进行等多种工作容错爱护机制。

可视化的工作治理

  • 工作治理视图:展现工作的执行状态,提供新增工作、编辑工作、删除工作、手动执行、启动 / 停用工作等操作能力。
  • 执行记录视图:展现所有惯例工作、工作流工作的执行批次详情列表,反对根据所属工作、部署组为查问过滤条件。
  • 执行列表视图:展现选定工作的执行批次详情列表,反对针对工作批次的进行、从新执行操作。
  • 执行详情视图:展现工作执行批次的执行实例列表,反对针对执行实例的进行、从新执行、日志查问操作。
  • 工作流治理视图:展现工作流工作的执行状态,提供工作流工作新建、可视化流程编排、启动 / 停用工作流工作等操作能力。

欠缺的工作运行监控告警

  • 立体化监控:提供工作运行状态、工作执行批次状态、执行实例运行状态的立体化监控,反对针对执行实例的线上日志查看能力。
  • 灵便告警策略:集成云监控能力提供工作执行批次、执行实例异样告警,工作流工作执行批次、批次工作、执行实例异样告警能力,反对灵便的指标告警及事件告警配置。

架构原理

TCT 各组件简介如下

  • 触发器:解析工作的触发规定;
  • 调度器:派发须要执行的工作、治理工作状态等;
  • 监控:工作执行相干的监控数据上报;
  • 控制台:管理员的控制台界面;
  • 接入层:工作下发、状态上报等音讯的信道管理器;
  • 接入网关:对立对接接入层及 SDK 的网关;
  • SDK:和业务过程运行在一起,负责执行工作中定义的一段具体代码逻辑。

首先,由触发器解析用户在控制台配置并存入 DB 的工作信息,并将解析后的执行信息投入到 MQ 中。其次,由调度器生产执行信息并通过接入层下发到具体的执行器节点上(接入层中有具体的节点注册信息,包含 IP 地址等)。最初,当 SDK 所在节点实现工作的执行后(胜利、失败、未响应等),会将执行后果通过 TCP 长连贯传回给调度器,而后调度器会跟 DB 进行交互实现工作状态的变更,同时上报工作执行状况到监控模块。

通过性能简介能够发现 TCT 根本涵盖了常见的任务调度场景中所需性能,尤其在可视化视图方面做了大量的工作,同时依靠腾讯云齐备的基础设施建设,在高可用保障和缩小运维老本方面也提供了极大保障。此外,TCT 源于 TSF 技术平台,对 TSF 利用人造集成,撑持组件能够很不便的获取 TSF 利用的相干信息,如 TSF 部署组 ID、节点 IP、利用 ID 等,因而在工作执行效率上也会更高。

不过,由整体架构图发现 TCT 采纳中心化调度计划,调度器、触发器及控制台组件无状态,反对程度扩大,组件及 SDK 间通过 TCP 长连贯通信;而数据流依赖 DB 及 MQ,在工作数量大、执行频率高的大规模落地场景下,DB 和 MQ 的吞吐量就会成为性能卡点,即便能够优化也会有显著下限。所以依据目前 TCT 的产品状态,其更多的实用于轻量级任务调度场景。

分片执行案例

背景概述

分片执行模式是大批量数据处理场景下常常用到的执行形式,本案例以保险行业中子公司每天向总公司汇总当天营销数据的业务场景为例进行阐明。

从上图可见,汇总营销数据的服务(后文称 summarydata)每天凌晨 2:00 定时调用 34 个子公司提供的营销数据查问 API。之所以应用分片执行形式,是因为汇总营销数据的操作须要在同一时间触发,且整体汇总工夫越短越精确。此外,各公司的营销数据量并不相同,而且即便是雷同的子公司每天产生的营销数据量也不雷同。

配置步骤

依据如上业务背景形容,同时基于现有资源状况,整体配置思路为:

  • 创立一个 summarydata 部署组,其中新建 4 个实例,单个实例线程池数量为3
  • 利用代码中将 34 个子公司一一对应到 1~34 的公司 ID 上;
  • 依据大抵地区和日均产生的数据量,将 34 家公司划分到 NORTH、SOUTH、EAST、WEST 四个区域;
  • 分片数量为 4,每个分片对应到 1 个实例,即 1 个实例 至多 计算 1 个区域的数据;
  • 每个区域 key 对应的子公司 ID 列表可通过代码配置进行半自动调整,避免某个子公司数据量陡增状况;
  • 为避免统计反复,不配置工作主动重试,采纳手动弥补。

步骤一:触发类代码编写并打包

public class SimpleShardExecutableTask implements ExecutableTask {private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    @Override
    public ProcessResult execute(ExecutableTaskData executableTaskData) {
        // 输入工作执行元数据
        TaskExecuteMeta executeMeta = executableTaskData.getTaskMeta();
        LOG.info("executeMetaJson:{}",executeMeta.toString());
        // 输入调配给本实例的分片参数
        ShardingArgs shardingArgs = executableTaskData.getShardingArgs();
        LOG.info("ShardCount: {}", shardingArgs.getShardCount());
        Integer shardingKey = shardingArgs.getShardKey();
        LOG.info("shardingKey: {}", shardingKey);
        String shardingValue = shardingArgs.getShardValue();
        LOG.info("shardingValue: {}", shardingValue);
        // 模仿工作执行
        try {this.doProcess(shardingValue);
        } catch (Exception e) {e.printStackTrace();
        }
        return ProcessResult.newSuccessResult();}

    public void doProcess(String shardingValue) throws Exception {if (shardingValue.equals(CompanyMap.NORTH.area)){Arrays.stream(CompanyMap.NORTH.companyIds)
                    .forEach(companyId->LOG.info("calling north subsidiary_{} api.....",companyId));
        } else if(shardingValue.equals(CompanyMap.SOUTH.area)){Arrays.stream(CompanyMap.SOUTH.companyIds)
                    .forEach(companyId->LOG.info("calling south subsidiary_{} api.....",companyId));
        } else if(shardingValue.equals(CompanyMap.EAST.area)){Arrays.stream(CompanyMap.EAST.companyIds)
                    .forEach(companyId->LOG.info("calling east subsidiary_{} api.....",companyId));
        } else if(shardingValue.equals(CompanyMap.WEST.area)){Arrays.stream(CompanyMap.WEST.companyIds)
                    .forEach(companyId->LOG.info("calling west subsidiary_{} api.....",companyId));
        }  else {throw new Exception("input shardingValue error!");
        }
        ThreadUtils.waitMs(3000L);
    }

    enum CompanyMap{NORTH("NORTH", new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9}),
        SOUTH("SOUTH",new int[]{10,11,12,13,14,15,16,17,18,19}),
        EAST("EAST",new int[]{20,21,22,23,24,25,26,27,28}),
        WEST("WEST",new int[]{29,30,31,32,33,34});

        private String area;
        private int[] companyIds;

        CompanyMap(String key,int[] values){
            this.area = key;
            this.companyIds = values;
        }

        public String getArea() { return area;}
        public void setArea(String area) {this.area = area;}
        public int[] getCompanyIds() {return companyIds;}
        public void setCompanyIds(int[] companyIds) {this.companyIds = companyIds;}
    }
}

步骤二:创立利用及部署组,并实现部署

步骤三:创立 TCT 工作

步骤四:手动启动工作测试

测试成果

通过控制台查看实例执行状况,同时可通过分片参数按钮,查问某个实例执行批次内的分片参数。

通过利用日志查看后果,可发现有 1 个实例运行了 2 个分片工作,是因为 TCT 对实例负载状况进行了判断,抉择了绝对闲暇的实例。

此外还进行了服务内实例异样的测试,即当 summarydata 服务中 4 个实例仅余 1 个实例失常时工作的执行状况(因为日志较长,笔者节选了重要局部)。能够看到前 3 个分片工作是同时且应用不同线程执行的,第 4 个分片工作是在前 3 个工作执行实现后再执行的,合乎预期。

将来方向

分布式任务调度平台框架间的竞争过程漫长而胶着,各家厂商都在寻求产品价值上的突破口,TCT 也仍有很多有余,须要从市场需求和技术趋势的角度继续深度思考。针对分布式任务调度市场,笔者粗略总结了如下几点将来产品可能的优化方向:

  1. 去中心化

中心化的分布式任务调度平台毛病显著,难以撑持企业大规模落地场景。同时,市场中的产品及技术演进趋势逐步向去中心化倒退,起因在于去中心化的分布式任务调度平台才具备大规模商业化落地的可能,胜利的商业化落地案例也是产品走向成熟的标记。

  1. 容器化

分布式任务调度平台组件及组件间通信目前多为传统虚机形式,如果能同时实现撑持组件的容器化部署,就能够更好的施展容器平台疾速启停、资源调度、程度扩大等方面的劣势,以进步撑持侧整体可用性,缩小扩缩容时的运维老本,无效晋升平台整体的吞吐量,而高可用、弹性扩缩、高性能是大型企业数字化转型云原生的重要考量因素。

  1. 可编程

越来越多的分布式工作场景须要针对多个工作做简单的工作编排,目前支流的编排仍局限于工作间串行、并行、与或等简略的逻辑解决。将来更多的须要一种通用的、可编程的模板语言,用于形容工作参数及内容、DAGS(有向无环图)、操作符、触发动作等,标准化各家厂商对于工作编排的定义形式。

  1. 容错弥补

在工作及工作流执行异样时的解决策略也有很多方面须要欠缺,比方因为实例夯死导致的过期触发问题、工作追赶和工作沉积问题、工作流场景下工作异样后是整体重试还是断点续传重试等。

  1. 场景降级

目前各家产品在常见的定时工作场景中,性能同质化水平比拟高。但随着云原生、大数据等相干畛域的疾速倒退,分布式任务调度平台也逐步产生了新的利用场景,如大数据场景下的分布式计算及计算汇总、调度平台对接 serverless 利用等,这都对产品的场景和性能提出更高的要求。

结语

通过对定时工作的场景、演进历史、各平台框架的介绍以及腾讯云自研分布式任务调度框架 TCT 的实际案例形容,笔者继前人的根底上对分布式任务调度框架的利用现状和将来倒退进行了简要剖析,之前晓得的和不晓得的当初读者敌人应该都晓得了。谨心愿本文能在技术选型及开源建设方面提供些许思路和视角,供企业和开源社区参考。

援用

https://cloud.tencent.com/doc…
http://www.quartz-scheduler.org/
https://www.xuxueli.com/xxl-job
https://shardingsphere.apache…
http://www.powerjob.tech/
https://vipshop.github.io/Sat…

正文完
 0