研究的目的
定时任务问题一直是我们组项目的主要问题来源,很多问题的最终定位是由于定时任务异常停止导致的。这些定时任务通常是单点故障任务,一旦出现异常肯定会导致业务异常。因此,我一直想寻找一个方案来解决定时任务的单点故障问题。我觉得可以从下面两个方面来找手做。
- 可监控定时任务出现故障的时候可以第一时间得到通知;(监控系统)
- 试用分布式定时任务,解决单点故障的问题。
因为监控系统相关的技术已经有同事在研究了并且做了一次技术分享,而且我觉得要从根本上解决这类问题必须解决单点故障的问题,那么我最开始做的就是看看业内有没有什么开源的解决方案,结果确实有很多,那么剩下的任务就是找一款适合组内情况的开源框架来研究使用。那么我调研这项技术主要的关注点是什么呢?可以从下面几个方面综合考虑:
- 满足一致性需求 — 一个任务不能同时在多台机器上执行
- 文档齐全 — 方便学习交流
- 迭代时间和频率 — 常有更新说明近期有人维护
- 使用主流技术框架 — 主流技术比较容易学习和使用
- 上手接入简单 — 上手接入简单无须太多配置就可以应用到系统中
- 非入侵式,即可以方便接入而无须修改原有代码功能
- 框架简洁可拓展 — 简洁、可拓展的框架可以方便我们根据需求二次开发
分布式定时任务开源框架
确定了目标那么接下来就是选择一款开源框架来进行调研,google之后发现了有下面一些java技术开发的分布式定时任务开源框架,下面是这些框架的列表。
- Elastic-Job(当当网) githubElastic-Job 是是一个分布式调度解决方案,当当网对外的开源技术,它功能、文档齐全,但是接入配置项较多,有一定的学习成本。
- light-task-scheduler github 主要用于解决分布式任务调度问题,支持实时任务,定时任务和Cron任务。有较好的伸缩性,扩展性,健壮稳定性。提供可视化界面,当需要mysql支持,另外对spring-Quartz的支持文档不够清楚。
- clover CSDN 这个没有在git上找到源码,可用文档缺乏。
- TBSchedule(阿里) 阿里云 阿里2012年开源的分布式任务调用框架,功能与其他的差不多但是更新时间比较久远且缺乏文档。
- niubi-job github 是一个具备高可用特性的专门针对定时任务的任务调度框架,比较轻量级扩展性也不错,提供web监控页面。
- Uncode-Schedule 码云基于zookeeper的分布式任务调度组件,非常小巧,使用简单,只需要引入jar包,不需要单独部署服务端。确保所有任务在集群中不重复,不遗漏的执行。支持动态添加和删除任务。
基本上每种开源技术都可以集成了分布式框架zookeeper来作为支持。最终我觉得比较服务和我需求的是light-task-scheduler、niubi-job、Uncode-Schedule。最终我选择了Uncode-Schedule,因为从文档上面看到了直接对spring-Quartz的配置方式,比较适合我当前项目需求,而不需要进行额外的开发和修改。
选择Uncode-Schedule来深入学习了解
Uncode-Schedule 框架的深入了解可以查看说明文档,下面将该框架的主要功能点和要应用的一些配置给再次描述一遍。
功能概述
- 基于zookeeper+spring task/quartz/uncode task的分布任务调度系统。
- 确保每个任务在集群中不同节点上不重复的执行。
- 单个任务节点故障时自动转移到其他任务节点继续执行。
- 任务节点启动时必须保证zookeeper可用,任务节点运行期zookeeper集群不可用时任务节点保持可用前状态运行,zookeeper集群恢复正常运期。
- 支持动态添加、修改和删除任务,支持任务暂停和重新启动。
- 添加ip黑名单,过滤不需要执行任务的节点。
- 后台管理和任务执行监控。
- 支持spring-boot,支持单个任务运行多个实例(使用扩展后缀)。
说明
单节点故障时需要业务保障数据完整性或幂等性
模块架构
基于Quartz的XML配置
经过实践是可以不修改系统原有的定时任务,直接通过修改配置的方式使系统支持分布式。
注意:spring的MethodInvokingJobDetailFactoryBean改成cn.uncode.schedule.quartz.MethodInvokingJobDetailFactoryBean
<bean id="zkScheduleManager" class="cn.uncode.schedule.ZKScheduleManager"
init-method="init">
<property name="zkConfig">
<map>
<entry key="zkConnectString" value="183.131.76.147:2181" />
<entry key="rootPath" value="/uncode/schedule" />
<entry key="zkSessionTimeout" value="60000" />
<entry key="userName" value="ScheduleAdmin" />
<entry key="password" value="password" />
<entry key="autoRegisterTask" value="true" />
<entry key="ipBlacklist" value="127.0.0.2,127.0.0.3" />
</map>
</property>
</bean>
<bean id="taskObj" class="cn.uncode.schedule.SimpleTask"/>
<!-- 定义调用对象和调用对象的方法 -->
<bean id="jobtask" class="cn.uncode.schedule.quartz.MethodInvokingJobDetailFactoryBean">
<!-- 调用的类 -->
<property name="targetObject" ref="taskObj" />
<!-- 调用类中的方法 -->
<property name="targetMethod" value="print" />
</bean>
<!-- 定义触发时间 -->
<bean id="doTime" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail">
<ref bean="jobtask"/>
</property>
<!-- cron表达式 -->
<property name="cronExpression">
<value>0/3 * * * * ?</value>
</property>
</bean>
<!-- 总管理类 如果将lazy-init='false'那么容器启动就会执行调度程序 -->
<bean id="startQuertz" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="doTime"/>
</list>
</property>
</bean>
使用API或后台添加任务
1 动态添加任务
ConsoleManager.addScheduleTask(TaskDefine taskDefine);
2 动态删除任务
ConsoleManager.delScheduleTask(TaskDefine taskDefine);
3 动态更新任务
ConsoleManager.updateScheduleTask(TaskDefine taskDefine);
4 查询任务列表
ConsoleManager.queryScheduleTask();
uncode-schedule管理后台
访问URL:项目名称/uncode/schedule,如果servlet3.x以下,请手动配置web.xml文件
<servlet>
<servlet-name>UncodeSchedule</servlet-name>
<servlet-class>cn.uncode.schedule.web.ManagerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UncodeSchedule</servlet-name>
<url-pattern>/uncode/schedule</url-pattern>
</servlet-mapping>
分布式协调技术zookeeper介绍
zookeeper是分布式任务调度的底层支持框架,前面介绍的每一种分布式定时任务框架都是基于zookeeper来实现的,那么简单介绍下zookeeper安装在debian环境下分布式的搭建,需要了解详细的请戳。
安装
apt-get install zookeeperd
##启动停止
service zookeeper # {start|stop|status|restart|force-reload}
配置
配置/etc/zookeeper/conf/zoo.cfg
tickTime=2000
initLimit=5
syncLimit=2
dataDir=/Users/apple/zookeeper0/data
dataLogDir=/Users/apple/zookeeper0/logs
clientPort=2181
server.0=127.0.0.1:2888:3888
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2888:3888
新增了几个参数, 其含义如下:
- initLimit: zookeeper集群中的包含多台server, 其中一台为leader, 集群中其余的server为follower. initLimit参数配置初始化连接时, follower和leader之间的最长心跳时间. 此时该参数设置为5, 说明时间限制为5倍tickTime, 即5*2000=10000ms=10s.
- syncLimit: 该参数配置leader和follower之间发送消息, 请求和应答的最大时间长度. 此时该参数设置为2, 说明时间限制为2倍tickTime, 即4000ms.
- server.X=A:B:C 其中X是一个数字, 表示这是第几号server. A是该server所在的IP地址. B配置该server和集群中的leader交换消息所使用的端口. C配置选举leader时所使用的端口. 由于配置的是伪集群模式, 所以各个server的B, C参数必须不同.
参照zookeeper0/conf/zoo.cfg, 配置zookeeper1/conf/zoo.cfg, 和zookeeper2/conf/zoo.cfg文件. 只需更改dataDir, dataLogDir, clientPort参数即可.
配置/usr/lib/zookeeper/myid
myid 里面填写的内容就是当前服务器的编码, Server.X 的X值。
注意:
- 用apt-get install 安装之后,系统会创建 zookeeper 用户和用户组,在配置zookeeper日志和数据文件夹之后,需要配置文件夹的权限,否则启动会因为文件夹权限问题包异常。例如:
chown -R zookeeper:zookeeper /Users/apple/zookeeper0/data
- 确保不同服务器之间的端口有权限相互访问指定的端口。