欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

分布式任务调度框架--SkySchedule介绍

程序员文章站 2022-07-13 16:47:30
...

 

概述

 

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介绍
            
    
    博客分类: netty SkySchedulenetty分布式任务调度 
 

 

多分组集群模式

这种方式,理论上可以为任意数量的系统提供分布式任务调度服务。

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客户端关系图:

 


分布式任务调度框架--SkySchedule介绍
            
    
    博客分类: netty SkySchedulenetty分布式任务调度 
 

 

源码github地址:https://github.com/gantianxing/skySchedule

目前只是初始版,能满足上述分布式任务调度基本需求。同时欢迎意见,后续会抽时间慢慢完善,同时也希望感兴趣的朋友贡献代码。

 

代码使用详解

 

源码中一共有四个模块:sky-server、sky-common、sky-client、sky-test,关系如下:


分布式任务调度框架--SkySchedule介绍
            
    
    博客分类: netty SkySchedulenetty分布式任务调度 
 

 

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

 

 

 

 

  • 分布式任务调度框架--SkySchedule介绍
            
    
    博客分类: netty SkySchedulenetty分布式任务调度 
  • 大小: 63.4 KB
  • 分布式任务调度框架--SkySchedule介绍
            
    
    博客分类: netty SkySchedulenetty分布式任务调度 
  • 大小: 52.6 KB
  • 分布式任务调度框架--SkySchedule介绍
            
    
    博客分类: netty SkySchedulenetty分布式任务调度 
  • 大小: 18.8 KB