大数跨境
0
0

分布式任务调度平台xxl-job的内部原理,及在转转的落地实践

分布式任务调度平台xxl-job的内部原理,及在转转的落地实践 转转技术
2020-09-09
2



让世界因流转更美好



值此教师节来临之际,衷心祝愿所有的老师教师节快乐,身体健康,幸福平安,工作顺利,桃李满天下。您们辛苦了!

作者简介 ·









杜云杰,架构师,转转架构部负责人,负责服务治理、MQ、云平台、APM、IM、分布式调用链追踪、监控系统、配置中心、分布式任务调度平台、分布式ID生成器、分布式锁等基础组件。

道阻且长,拥抱变化;而困而知,且勉且行。




一 背景








转转之前的任务调度系统zzjob存在着以下问题:

· 客户端较重,需要与zzjob的中心DB建立连接,权限问题频出;

· 所有任务都是一级展示,没有按服务聚合的功能;

· 不能设置任务的超时时间;

· 无法中止正在执行的任务;

· 无失败重试;

· 无任务依赖;

· 无法在平台查看业务日志;

· 无权限控制;

·  ……

基于此,架构组调研并开发了一套新的分布式任务调度平台zzschedule。zzschedule是基于开源的分布式任务调度平台xxl-job,其核心设计目标是开发迅速、学习简单、量级轻、易扩展,并已在数百家公司内部得到了广泛的使用(如美团点评、滴滴出行、网易、搜狐等)。同时,针对转转的业务场景,zzschedule对xxl-job进行了二次扩展,方便转转同学快速接入、易于使用。

目前zzschedule有三套环境,分别是:

· 测试环境

· 沙箱环境

· 线上环境






二 主要功能特性







· 简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;

· 动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;

· 调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;

执行器HA(分布式):任务分布式执行,任务”执行器”支持集群部署,可保证任务执行HA;

· 注册中心:执行器会周期性自动注册任务,调度中心将会自动发现注册的任务并触发执行;

· 弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;

· 路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;

· 故障转移:任务路由策略选择”故障转移”情况下,如果执行器集群中某一台机器故障,将会自动Failover切换到一台正常的执行器发送调度请求;

· 阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度;

· 任务超时控制:支持自定义任务超时时间,任务运行超时将会主动中断任务;

· 任务失败重试:支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;其中分片任务支持分片粒度的失败重试;

· 分片广播任务:执行器集群部署时,任务路由策略选择”分片广播”情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务;

· 任务进度监控:支持实时监控任务进度;

· Rolling实时日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志;

· 任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行,多个子任务用逗号分隔;

· 触发方式:支持Cron方式、任务依赖方式、手动触发;

· 一致性:“调度中心”通过DB锁保证集群分布式调度的一致性,一次任务调度只会触发一次执行;

· 自定义任务参数:支持在线配置调度任务入参,即时生效;

· 调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞;

· 线程池隔离:调度线程池进行隔离拆分,慢任务自动降级进入”Slow”线程池,避免耗尽调度线程,提高系统稳定性;

· 运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等;

· 全异步:任务调度流程全异步化设计实现,如异步调度、异步运行、异步回调等,有效对密集调度进行流量削峰,理论上支持任意时长任务的运行。


转转扩展的功能主要有:

· 权限控制:接入转转SSO认证,自动识别当前登陆用户;统一以服务维度进行权限控制;

· 任务失败告警:扩展为企业微信的告警方式;

· 跨环境同步任务配置:在测试环境可一键将任务配置同步至沙箱、线上,避免业务反复创建任务;

· 可指定任务的执行实例列表:将任务限定在指定机器上执行,方便测试,同时避免实例代码不一致带来的干扰;

· 客户端封装成spring-boot-starter:starter自动区分当前环境和当前服务,并自动注册至调度平台,业务只需用@XxlJob和@JobHandler直接编写任务代码、创建和启动任务即可,免去注册服务等操作,真正实现一键接入,简单易用。





三 系统架构及内部原理






系统架构


上图是xxl-job的官方架构图,整体分为两个部分:

· 调度中心:web服务,身兼Portal和调度中心的功能。主要负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统性能不再受限于任务模块;支持可视化、简单且动态的管理调度信息,包括任务新建,更新,删除等,所有上述操作都会实时生效;同时支持监控调度结果以及执行日志、失败报警等,支持执行器Failover。

· 执行器:部署在业务进程里。主要负责接收调度请求并执行任务的业务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效。

其中,调度中心通过作者自研的xxl-rpc与执行器建立TCP长链接进行通信调度(注:2.2.0版本已改用语言无关的 RESTful API方式,方便跨语言对接调度中心或者实现执行器);外部组件仅依赖MySQL,多个调度中心进程也是通过MySQL锁来实现分布式锁的功能,避免重复调度。

从整体来看,xxl-job架构依赖较少,功能强大,简约而不简单,方便部署,易于使用。







内部原理


3.1 执行器的注册与发现

执行器的注册与发现通过以下两种方式:

· 主动式:执行器启动后,主动告知调度中心,并定时发送心跳;执行器要关闭时,也主动告知调度中心及时注销掉。

· 被动式:调度中心会有一个专门的后台线程定时调用所有注册上来的执行器接口,进行服务探活。如果发现执行器有异常会及时注销掉,避免任务再调度到该异常执行器上。


3.2 任务的调度与执行


这块是xxl-job的核心部分。如上图,任务的整体调度与执行流程如下:

1. 获取到MySQL锁的调度中心从MySQL获取未来5秒内的任务,并将任务加入到对应秒的时间轮槽里,然后更新任务的下次执行时间写入MySQL。例如当前时间是12:35,那么会加载12:40之前要运行的任务;若任务A要在12:37调度,那么会把A加入到37这一秒对应的时间轮槽里;

2.调度中心会有个专门线程每秒扫描时间轮,获取当前秒要执行的任务集合,并提交到线程池里。这里,线程池拆分为快、慢两个线程池,1分钟窗口期内任务耗时达500ms超过10次,该窗口期内判定为慢任务,慢任务自动降级进入”Slow”线程池,避免耗尽调度线程,提高系统稳定性;

3.线程池的工作线程向MySQL插入一条调度日志,并根据任务路由策略从服务的执行器列表中选择一个执行器(分片广播任务会选择所有执行器),然后将任务调度信息发送至目标执行器。调度信息主要包括调度日志ID、jobId、job名称、阻塞策略、超时时间、参数等;

4.执行器收到调度信息后,将调度信息加入到对应任务处理线程的等待队列中;任务处理程序串行从队列中取调度信息进行任务的执行,执行完后将结果写入一个公共的结果队列中。这里,每个任务有一个单独的处理线程和一个等待队列,调度信息放入队列,任务处理线程从队列里取调度信息进行执行,以此实现解耦;

5.执行器有一个专门的回调线程定时批量从结果队列里获取任务结果,并回调告知调度中心;

6.调度中心收到回调结果后,将结果更新至调度日志表中。如果任务失败,在此处可以进行重试。

由上可以看出,整个任务调度流程具如下特点:

1.秒级

2.快、慢线程池

3.全异步

4.优雅关闭:调度中心和执行器都有优雅关闭机制,避免任务丢失


3.3 多调度中心如何互斥


调度中心允许多实例部署以实现高可用,但如何做到互斥呢?

如上图所示,多调度中心通过MySQL锁来实现分布式锁进行互斥,从而避免任务的重复调度。MySQL里创建一个lock表,里边只有一行记录,lock_name字段值是“schedule_lock”。调度中心每次要加载任务时,需要开启一个手动事务,先获取schedule_task记录的写锁后,然后才能加载及更新任务;否则,说明其他调度中心已经获取成功在加载任务了,该调度中心只能等待其他调度中心事务提交才能再获取锁。流程的伪代码如下:

begin;  select * from lockwhere lock_name = 'schedule_lock' for update;  select * from jobwhere next_time < now()+5;  put jobs intotimewheel;  update jobs setnext_time=xxx where id in (...);commit;sleep 5s;

3.4 任务依赖


xxl-job可以解决任务依赖,并可以多级并联。比如在任务A执行完后触发任务B的执行,在跑离线数据时经常会遇到这种场景,如下图所示:

任务依赖是在调度中心收到任务调度的成功回调时进行触发的。


3.5 生命周期管理


xxl-job对任务调度有丰富的生命周期管理:

· 可超时:可超时的任务通过FutureTask

· 可终止:Thread.interrupt()

· 可重试

其状态流转如下:

3.6 执行日志


我们直接可以在任务调度平台上看到每次调度的相关日志,而且业务同学也可以通过工具XxlJobLogger.log("user={}",user)来添加一些业务日志方便跟进任务调度的执行状态。每次调度会生成一个日志文件,存放在执行器所在机器上,命名格式类似:/opt/log/zzschedule/{serviceName}/{yyyy-MM-dd}/{logId}.txt。

一次完整的获取调度日志流程如下:





四 在转转的落地实践







针对转转的业务场景,zzschedule对xxl-job进行了二次扩展,方便转转同学快速接入、易于使用。


4.1 权限控制


xxl-job自身的权限控制较弱,近乎没有。我们对其进行扩展,首先,我们接入转转SSO认证,并自动识别当前登陆用户信息;其次,给服务加上负责人信息,并将权限统一收归到服务的负责人。如果是服务的负责人,则有权限管理该服务下所有的任务。这些负责人是执行实例的starter在首次注册时,自动从CMDB(转转服务集群信息管理平台)来拉取的。


4.2 任务失败告警

转转员工目前统一使用企业微信,我们将任务失败告警也接入企业微信,效果如下图所示:


4.3 跨环境同步任务配置

我们在测试、沙箱、线上共部署了三套任务调度平台。对于同一个任务,我们需要在这三个环境分别创建一次,用户体验不太友好。基于此,我们对其进行扩展,测试完成后,业务同学可在测试环境将任务一键同步至沙箱和测试环境,避免重复创建的繁琐,效果如下图所示:


4.4 可指定任务的执行实例列表

xxl-job在任务调度时,根据路由策略从注册上来的执行器列表里选择一个来执行。但很多时候(比如线下并行测试),我们需要将任务固定在一个或者一批机器里来执行,方便定向观察。因此我们也对其进行扩展,业务同学可在任务里指定执行机器列表,调度时再根据路由策略从指定的机器列表里选择一个来执行。效果如下:


4.5 客户端封装成spring-boot-starter

业务同学在接入xxl-job时,需要手动注入XxlJobSpringExecutor Bean到Spring容器,还要进行一些基础配置,如调度中心地址、当前服务名称等。随后还要在任务调度平台手工注册服务,有一定的接入成本。

为了简化接入流程,我们将这些配置统一化,将客户端封装成一个统一的、自动化的spring-boot-starter,它能自动区分当前环境和当前服务,自动注入Bean,并自动注册至对应环境的调度平台,业务只需用@XxlJob和@JobHandler直接编写任务代码、创建和启动任务即可,免去注册服务等操作,真正实现一键接入,简单易用。





五 总结







xxl-job是市面上为数不多的一款功能强大、简单实用的分布式任务调度平台,真正做到了开发迅速、学习简单、轻量级,其架构简约而不简单,方便部署、易于扩展。我们架构部引入xxl-job,并针对转转的业务场景,对其进行了二次扩展,方便转转同学快速接入、易于使用。

zzschedule从2020.02上线至今半年时间里,已有74个服务共276个执行实例,共计325个任务。总调度次数达到1900多万次,目前日均约12万次。因其功能强大、简单易用,大量业务线同学已从zzjob迁移至zzschedule,日益成为转转技术栈中不可或缺的一个重量级利器组件。

另外,zzschedule运行半年来,针对业务同学经常遇到的问题跟大家总结下,后续我们我们也尽量通过程序来规避这些问题:

· 任务启动前确认Cron表达式:经常发现有业务创建任务后直接启动任务就不管了,也没确认Cron表达式是否正确。比如上午9点调度一次的“0 0 9 * * ?”配置成了“* * 9 * * ?”,这就变成了上午9点这一小时内每秒都会调度一次,相差3600倍。所以新建任务或者修改Cron后,一定要在“下次执行时间”里确认下。

· 终止任务并不保证成功:终止调度中的任务是通过Thread.interrupt()来进行的。对于Java线程比较熟悉的同学应该知道,对一个线程interrupt,是否能够中断还要取决于线程的状态。如线程处于WAITTING状态,那么是响应中断的,抛出InterruptedException;但如果线程处于RUNNING状态,仅仅是将中断标志位置位。还有,WAITTING状态即便抛出InterruptedException,还要看业务代码有没有catch,如果被catch了,仍然终止不了。所以,这种终止机制是比较弱的,不保证一定能够终止。

· 调度日志不要狂打:如前面章节所讲,每次调度会生成一个日志文件,业务也可以通过XxlJobLogger.log来添加业务日志。这里建议大家添加业务日志时,只添加一些重要节点的日志,而不是在这里狂刷日志。非重要节点的业务日志,还是打到业务日志文件里为好。




关于转转架构部 ·





负责服务治理、MQ、云平台、APM、IM、分布式调用链追踪、监控系统、基础存储、配置中心、分布式任务调度平台、分布式ID生成器、分布式锁等数十个基础组件


我们的愿景与使命:

· 降本、提质、增效;

· 一切服务于业务,脱离业务谈架构就是耍流氓;

· 开放包容,拥抱开源,站在巨人的肩膀上。


招聘信息

岗位职责

· 作为团队核心研发人员,参与RPC、MQ等中间件的推进和系统建设;

· 参与系统需求分析与设计,负责完成核心代码编写,接口规范制定;

· 解决各种疑难杂症,系统优化,并且完成产品、平台和组件的沉淀;

· 有“代码洁癖”和极客精神。

任职要求

· 三年及以上Java开发经验,具有扎实的Java功底;

· 了解JVM的原理,具有较好JavaIO、多线程、网络等方面的编程能力;

· 熟悉SpringMvc、SpringBoot、MyBatis等主流开源框架;

· 熟悉MySQL数据库,对查询优化有一定经验,对索引的使用及原理有深入认识;

· 熟悉Linux平台下常用命令操作,具备根据日志迅速定位问题并解决问题的能力;

· 有大规模高并发互联网应用的设计和开发经验,熟悉常规的分布式架构,熟悉缓存、消息队列等开源中间件,对优秀开源软件的源码有心得者优先;

· 具有较好的沟通能力,思路清晰,善于思考,能独立分析和解决问题;

· 有强烈的责任心和团队合作精神,良好的抗压能力,心态积极,能主动融入团队。


欢迎志同道合的小伙伴加入我们的团队,共创未来。投送简历至tc@zhuanzhuan.com(标题请注明:架构平台部)。


【声明】内容源于网络
0
0
转转技术
转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。 各种干货实践,欢迎交流分享,如有问题可随时联系 waterystone ~
内容 271
粉丝 0
转转技术 转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。 各种干货实践,欢迎交流分享,如有问题可随时联系 waterystone ~
总阅读64
粉丝0
内容271