前段时间一直在思考分布式定时任务的实现,虽然市面上有很多强大的分布式定时任务框架,比如elastic-job,xxx-job等等,但是我还是把我对于分布式定时任务的相关思考记录下来。欢迎感兴趣的小伙伴参与进来讨论。
目前做java web技术栈的话,大部分应用的实现还是在spring的生态圈中进行扩展,即使是定时任务,spring也有对应的组件:
@Component
@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public class SaticScheduleTask {
//3.添加定时任务
@Scheduled(cron = "0/5 * * * * ?")
//或直接指定时间间隔,例如:5秒
//@Scheduled(fixedRate=5000)
private void configureTasks() {
System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
}
}
按照上面的配置,可以很简单地实现我们想要的定时任务。但是会存在以下几个问题:
1.不能灵活地修改配置。即修改定时规则需要重启服务。
2.无法应对集群部署。集群部署会存在多个定时任务同时运行的情况。
3.无法有效地管理。即不能很方便地对存在的定时任务做查询,删除,修改操作
4.对cron表达式的支持有限制。Spring的定时任务的cron表达式是不支持“年”的,一般的cron表达式有7位,spring定时任务的cron表达式只支持6位
另外,Spring还提供动态添加定时任务的接口,只要实现SchedulingConfigurer接口:
@Component
@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public DynamicScheduleTask implements SchedulingConfigurer{
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar){
//从数据库读取定时任务,并且添加进taskRegistrar
}
}
按照以上代码,我们便可以实现动态配置的定时任务,并且通过对数据库的管理很方便地管理我们的定时任务。如果数据的格式存在错误,定时任务会立即停止,即使重新修改正确,也只能重启服务才行。
所以对于简单定时任务,单机部署的话,可以考虑spring提供的定时任务策略。
针对集群部署的服务,我们需要通过分布式锁解决任务并发的问题。
但是以上的实现方案并不能算是一个最终解决方案,关于单纯地做一个分布式锁的方案,会出现所有的机器都在抢占同一个分布式锁,也就是说,集群中某一时刻有且只有一台机器在有效地工作,其他的机器一直都在试图抢占锁。这样做并不是不可行,显然是对计算资源的巨大浪费。如果同一时刻有多个不同的定时任务需要运行,那就会对单机造成巨大负载。
1.是否可以对集群的定时任务进行分片呢?
假设目前有5个定时任务在数据库中,需要部署2台机器,第1台机器只读取并运行1,3,5任务,第2台机器运行2,4任务。
这样想貌似是可以的,但是细想一下,如果有一台机器宕机了,那是不是意味着有一部分任务是没有执行的,这绝对是不允许的。
如果机器之间能互相感应对方的存在和消亡,那我们的设想是不是还可以抢救一下......
2.服务的注册和监听
下面我以用redis实现注册的方案举例子:
2.1:机器1启动时使用setnx命令写入以NODE:{enviroment}:LOCATION:0,0作为key,ip作为value的数据,并且过期时间为30秒。ps: {enviroment}用作环境隔离,是自定义变量,所以用大括号。
2.2:机器1还需要隔一段时间(不超过30秒)续一下自己的key值,否则就做下线处理。其次机器1还需要订阅其他节点key的注册和过期事件,以便监听其他机器的注册和下线。
2.3:接下来机器1就开始读取数据库找到它需要处理的定时任务,对于第一台机器来说,它不知道后面是否会有其他机器加入,所以它需要把所有的定时任务都读取进来。
2.4:同样的机器2也要setnx注册,唯一不同的是以NODE:{enviroment}:LOCATION:0,1作为key。
2.5:机器2同样需要给自己的key续时间,并同时监听其他节点key的注册和过期事件。
2.6:然后机器1上的5个任务就像HashMap的扩容一样,机器2会把属于自己的任务通过数据库查询载入内存,并发出通知(通过中间件),机器1在接收到通知后会删除不属于自己的任务。
按照以上步骤,即可完成多机器部署的时候,任务自动分片。需要注意的是:每个任务执行的时候还是需要先获取分布式锁的,以便防止在任务转移的过程中出现多个机器上出现同一个任务的情况,并且这种情况是很常见的。
如果出现机器宕机的情况,注册的redis的key最多会在30秒后过期,并且这个过期事件会被其他在线的机器监听到,那么剩下的机器还是可以按照一定的算法分摊它所有的任务。这个也可以类似于HashMap的一个缩容算法。
如果以上设想成立,那么对于所有定时任务的管理就简单了,所有的增,删,改只需要通过任意节点对数据库直接操作,然后发出通知,查询只需要查数据库就可以。关于cron的完全支持,我们可以直接使用quartz框架作为定时任务的核心实现就能完美解决这个问题。
以上关于redis的具体实现也可以使用其他中间件,例如zookeeper等,发布订阅的实现可使用合适的消息中间件。
以上就是我关于分布式定时任务的相关思考,欢迎感兴趣的小伙伴参与讨论。
网友评论