分布式任务调度框架--SkySchedule介绍
概述
SkySchedule是基于netty实现的分布式任务调度框架,不依赖zookeeper等其他服务。主要原理是:客户端启动时通过netty与SkySchedule服务端建立长连接,通过长连接发送心跳消息,服务端可以统计到当前存活的客户端列表,为每个客户端分配“任务编号”。通过“任务id”对“客户端总数”取模,每个客户端获取对应“任务编号”的任务,实现分布式任务调度。
该分布式任务调度适用场景
生产者:假设有一个系统时刻都在生成任务,这些任务不会立即执行,需要暂时存放到一个mysql任务表表。
任务表:每个任务以一条记录的形式存放在msyql任务表中,表结构大致如下:
列名 |
类型 |
描述 |
id |
Int |
连续自增主键 |
task_data |
varchar(50) |
任务数据 |
task_time |
datetime |
任务执行时间 |
status |
Int |
状态:0 新建 1执行成功 2-执行失败 |
消费者:为了防止任务积压,并且能在最短的时间内把已经到期的任务执行完,我们需要多台机器并行执行这些任务。
分布式任务调度器SkySchedule职责:
1、通过分布式调度,把任务均分到各个消费者服务器。
2、保证任务100%被执行。一般情况下任务执行有三种情况(跟mq的消息处理类似):
At most once 任务可能没有执行,但绝不会重复执行。
At least one 任务绝对会执行,但可能会重复执行。
Exactly once 每条任务肯定会被执行一次且仅执行一次,很多时候这是用户所想要的。
由于在我所在项目的业务场景中,允许重复执行,目前SkySchedule只支持到At least one。Exactly once后续会提供。
3、某台消费者服务器挂掉,能自动重新分配任务到其他消费者服务器(重新分配的这部分任务执行可能会有短时间延迟)。如果增加消费者服务器,也能对剩余的任务自动重新分配。
4、为了防止单点故障,SkySchedule服务需要部署到多台服务器上的,如果其中某台挂掉或出现故障,存活的SkySchedule服务器能正常对外服务。
部署方式:
多分组集群模式:
这种方式,理论上可以为任意数量的系统提供分布式任务调度服务。
SkySchedule以集群方式部署,并做分组,比如:每三台server做为一个分组。每个分组,可以为多个系统提供分布式调度服务。上图红色框部分表示SkySchedule集群。
蓝色框部分,表示多个需要分布式任务调度的子系统。以“系统1”为例:“系统1”中的每台服务器启动时,向指定的SkySchedule分组中的每台server建立一个长连接。
这种方式需要开发一个集群管理页面来管理自己的服务器列表,并进行分组管理。以及系统接入注册管理页面,为某个系统分配到指定的SkySchedule分组为其服务。这部分相关管理功能没有提供开源源码,可以根据自己公司业务实现。
单分组集群模式:
如果只有少量或单个系统需要分布式任务调度服务,可以把SkySchedule只部署在少量服务器上(比如三台,防止单点故障),并把服务ip和端口整理成配置文件。
在需要分布式调度服务的系统中,引入该配置文件。程序启动时读取配置文件,与SkySchedule服务端建立长连接。
目前SkySchedule服务端只实现了“单分组集群模式”,多分组模式还需要添加相关管理功能才能实现。
以下内容都是基于“单分组集群模式”进行讲解。
客户端任务分配:
1、在需要任务调度的“子系统”中引入SkySchedule客户端jar包。
2、引入SkySchedule服务ip、端口列表配置文件。
3、使用jar包中ClientNode中的两个常量来调整从mysql任务表获取任务的sql语句
ClientNode. totalNode : 表示“总节点数”,也就是所有存活的“消费者客户端”个数。
ClientNode. nodeNum : 表示当前客户端“任务编号”。
当某个“消费者客户端”挂掉或新增时,SkySchedule服务端会计算最新的“总节点数”和每台客户端对应的“任务编号”,同步给每个客户端。
每台客户端获取已到期任务sql语句为:
Select * from task_info where (id % totalNode) = nodeNum and task_time < now()
task_info为任务表名,where条件有个取模操作,表示只取自己的属于任务。具体原理如下:
任务分配原理:
为了方便理解 举个例子:假如当前总共有3个客户端,分别的:
对于每个客户端而言totalNode都为3。
对于“客户端1”其任务编号nodeNum=0
对于“客户端2”其任务编号nodeNum=1
对于“客户端3”其任务编号nodeNum=2
在通过使用msyql任务表中的任务id对3取模,
如果结果为0,说明该任务会分配给“客户端1”执行,
如果结果为1,说明该任务会分配给“客户端2”执行,
如果结果为2,说明该任务会分配给“客户端3”执行,
提供这种方式就可以保证所有任务会被100%的执行。
另外mysql任务表的id是连续自增的,也可以理论上做到平台分配。
这就是SkySchedule分布式任务调度的实现原理,其实跟淘宝的tbschedule原理差不多,只是SkySchedule是通过netty实现,不需要借助其他服务,相对较为轻量。tbschedule需要借助zookeeper才能正常服务。
下图为: 生产者、消费者、SkySchedule服务端、SkySchedule客户端关系图:
源码github地址:https://github.com/gantianxing/skySchedule
目前只是初始版,能满足上述分布式任务调度基本需求。同时欢迎意见,后续会抽时间慢慢完善,同时也希望感兴趣的朋友贡献代码。
代码使用详解
源码中一共有四个模块:sky-server、sky-common、sky-client、sky-test,关系如下:
1、服务端:涉及到的模块为:sky-server、sky-common,通过maven编译打包会生成一个sky-server-1.0-SNAPSHOT.war的war,直接部署这个war包到jdk1.8+tomcat 8中环境中。注意这里必须是jdk1.8+tomcat 8的环境,如果无法启动检查下端口“9991”是否被占用。
假设一共部署了3台服务器:192.168.1.100、192.168.1.101、192.168.1.102。
2、服务端:涉及到的模块为:sky-client、sky-common,通过maven编译打包上述代码,还会生成两个jar包:sky-client-1.0-SNAPSHOT.jar、sky-common-1.0-SNAPSHOT.jar。把这两个jar包引入到需要做分布式调度的“应用服务工程”中。再进行少量配置即可,配置过程如下:
sky-test是一个模拟测试“应用服务工程”,必须为spring工程,建议spring使用4.0以上(sky-test使用的是4.3.1.RELEASE)。创建SkySchedule-client.properties,内容为:
#SkySchedule 服务ip端口列表 多个以","间隔 server.ip.port = localhost:9991,localhost:9992 #如果服务端挂掉,重连服务端间隔时间,单位秒 RE.CONN.WAIT.SECONDS=5 #向服务端发现心跳请求间隔时间,单位秒 TASK.REQ.WAIT.SECONDS=15 #25秒没有收到服务器返回,断开链接,放到重连map READ.WAIT.SECONDS=25 #如果空闲20秒发送一次ping信息 WRITE.WAIT.SECONDS=20 #系统编号 group.id=1000 #用户名 user.name=moon #密码 password=walker
把server.ip.port属性改为你自己的SkySchedule服务端列表,其他属性可以保持不变。比如把server.ip.port改为上述提到的:192.168.1.100:9991,192.168.1.101:9991,192.168.1.102:9991。
如果有多个不同类型的应用需要接入SkySchedule,需要对每个应用指定一个不同的“系统编号”group.id,默认是1000,如果只有一个系统需要接入,可以不用修改。
然后需要解析SkySchedule-client.properties到spring的Environment中,可以使用xml装配方式,也可以使用spring bean装配方式。由于sky-test构造的无web.xml配置的web工程,这里采用的后者,装配方式如下:
@Configuration @ComponentScan(basePackages = {"com.sky.schedule.client"}) @PropertySource("classpath:SkySchedule-client.properties") public class RootConfig { @Bean public PropertySourcesPlaceholderConfigurer placeholderConfigurer(){ return new PropertySourcesPlaceholderConfigurer(); } }
启动多个sky-test实例,每个实例分配的totalNode、nodeNum是否正常。
测试
下面我们直接使用源码中的代码进行测试,启动一个sky-server(SkySchedule服务端);依次启动两个sky-test,用来模拟两个需要分布式调度的“应用服务端”(SkySchedule客户端)。下面是测试步骤:
1、启动一个sky-test:观察sky-server服务端日志,每隔20秒,会打印一条消息:
return task info groupId:1000,total:1,nodeNum:0
说明当前客户端总节点数为1,当前客户端“任务编号”为0,也就是说所有任务都由该客户端执行。
2、再启动一个sky-test:观察sky-server服务端日志,每隔20秒,会打印两条消息:
return task info groupId:1000,total:2,nodeNum:1
return task info groupId:1000,total:2,nodeNum:0
说明当前客户端数为2,两个客户端的“任务编号”分别为0、1,也就是说一个客户端会执行id尾数为:0,2,4,6,8的任务,另一个客户端会执行id尾数为:1,3,5,7,9。
动态的增加客户端,会重新分配任务,测试通过。
3、停止其中一个sky-test(模拟其中一个客户端挂掉):观察sky-server服务端日志,每隔20秒,只会打印一条消息:
return task info groupId:1000,total:1,nodeNum:0
模拟其中一个客户端挂掉,会重新分配任务,测试通过。
通过这三个测试,可以发现当客户端个数发生变化时,SkySchedule会动态的重写分配任务。
另外,如果你是直接运行github中的代码,没有修改SkySchedule-client.properties中的server.ip.port属性。在进行上述测试时,细心的你会发现每隔5秒会打印一条错误信息:
ERROR netty.NettyClient - connect server failed-localhost:9992
主要原因是server.ip.port配置了两个服务端:localhost:9991,localhost:9992。但实际上我们只部署一个localhost:9991实例。假如你再启动一个localhost:9992服务端实例,这个错误会消失。这里模拟的就是:如果一个SkySchedule服务端挂掉(或者重启),恢复正常后,客户端能自动重连。
也就是说客户端在启动时,至少要保证有一个SkySchedule服务端是启动的。在客户端已经启动的情况下,假如所有服务端都挂掉,也不影响,只是这时如果一个客户端挂掉,就无法实现任务动态分配了。简单的说,只要有一个SkySchedule服务端存活就能保证分布式任务调度正常运行,但最好启动多个SkySchedule服务端,防止单点故障。
总结
最后总结下,使用SkySchedule做分布式调度,你只需要3步即可完成:
1、启动多个服务端,不用改任何配置,使用默认配置即可。
2、把sky-client-1.0-SNAPSHOT.jar、sky-common-1.0-SNAPSHOT.jar这两个jar包引入到需要分布式任务调度的“应用服务”工程。
3、把sky-test工程下的SkySchedule-client.properties复制到“应用服务”工程的classpath下,修改其中server.ip.port、group.id两个属性。通过xml或者spring bean装配的方式,把配置文件注入到spring容器中(Environment)。
由于底层是采用netty建立的长连接,性能非常优异,对“应用系统”来说几乎毫无感知,也无需配置zookeeper等服务。并且服务端代码也非常轻量,感兴趣的朋友可以看下源码。
其实现在要实现分布式任务调度的手段其实很多,比如我同事采用redis+mq实现过一个分布式任务调度,但个人觉得依赖的外部基础服务太多,如果redis、mq其中任何一个出现问题就会对应用服务产生影响。但采用SkySchedule就可以完全避免这些问题,并且对应用服务不会产生额外负担,只需维持一个netty长连接即可。
最后感谢我的同事“曾老师”对group分组支持部分的完善。
在使用过程中有任何问题或建议,请直接留言或者站内信。一周内会不定期回复。
SkySchedule server端管理页面使用介绍:http://moon-walker.iteye.com/blog/2391954
转载请注明出处:
http://moon-walker.iteye.com/blog/2386504
上一篇: 系统融合(三)--组件化
下一篇: 京东活动系统--亿级流量架构应对之术
推荐阅读
-
详解免费开源的DotNet任务调度组件Quartz.NET(.NET组件介绍之五)
-
分布式任务调度框架 Azkaban —— Flow 1.0 的使用
-
开源分布式Job系统,调度与业务分离-HttpJob.Agent组件介绍以及如何使用
-
说说Quartz Scheduler任务调度框架
-
分布式任务调度中间件SchedulerX
-
LTS 轻量级分布式任务调度框架(Light Task Schedule)
-
分布式任务调度框架—SkySchedule介绍(二)
-
分布式任务调度框架—SkySchedule介绍(二)
-
分布式任务调度框架--SkySchedule介绍
-
分布式任务调度框架--SkySchedule介绍