如何设计一个分布式定时任务系统

以前在美团有crane可用,现在得自己考虑以下两种场景了:

1、定时任务指定集群中的一台机器执行

2、如何修改cron参数,且修改永久有效

当然直接用quartz来实现肯定最棒,但设计的配置太多,小公司没那个需求;
关于第1个,我开始选择得做法是:

读取zk固定节点path的值:
若值为空,当前机器A写入自己的ip到path下,并获得执行资格;
若值不为空,且值==A的ip,获得执行资格;
          且值 !=A的ip,放弃执行;

这种做法缺点是:若拥有执行资格的机器挂了,它并不会清除zk上的操作记录,将导致其他机器也无法执行;

解决方案:写入zk的值,改为ip+当前日期(根据定时任务的执行频率来判断需要写入:年、月、日、时、分、秒),若拥有执行权利的机器挂了,那么它不会再写入新的日期到zk中,其他机器在读取zk这个path值的时候,虽然发现已经有值,但是值中的日期并不是当天的,那么其可以修改值为自己的ip+日期,并获得执行资格;

关于第2个,我开始选择得做法是:
定时任务实现SchedulingConfigurer接口,关于此接口的详细可以百度搜搜;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

@Component
public class DynamicScheduledTask implements SchedulingConfigurer {
  private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

  private static final String DEFAULT_CRON = "0/5 * * * * ?";
  private String cron = DEFAULT_CRON;


  @Override
  public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    taskRegistrar.addTriggerTask(new Runnable() {
      @Override
      public void run() {
         // 定时任务的业务逻辑在此执行
    System.out.println("动态修改定时任务cron参数,当前时间:" + dateFormat.format(new Date()));
      }
    }, new Trigger() {
      @Override
      public Date nextExecutionTime(TriggerContext triggerContext) {
    // 定时任务触发,可修改定时任务的执行周期
    CronTrigger trigger = new CronTrigger(cron);
    Date nextExecDate = trigger.nextExecutionTime(triggerContext);
    return nextExecDate;
      }
    });
  }

  public void setCron(String cron) {
    this.cron = cron;
  }
}

新增一个restful接口用于修改cron:cron通过参数传递,想怎么改怎么传

@Autowired
DynamicScheduledTask dynamicScheduledTask;

// 更新动态任务时间
@RequestMapping("/updateDynamicScheduledTask")
@ResponseBody
public String updateDynamicScheduledTask(@RequestParam("cron") String cron) {
  dynamicScheduledTask.setCron(cron);
  return "ok";
}

但这种做法的缺点是:项目如果重启了,那么设置的值又变成默认的了,达不到永久生效的效果;

解决方案:还是需要把cron的获取逻辑通过固定的查询逻辑来显示,比如zk,在提供一个restful接口用于修改zk路径下的cron值

总结:以上还只是考虑最基本的两个要求,没有好的中间件,自己来造轮子,肯定还是不方便的,效果也不好~

@Scheduled(cron="0/10 * *  * * ? ")   //每10秒执行一次  

@Scheduled(cron = "0 0/10 * * * ?") // 每10分钟执行一次

很迷茫是不是?

进入正题:如何设计一个分布式定时任务系统?

首先,我们我们考虑下传统定时任务存在的问题以及分布式定时任务系统需要考虑的因素:

  1. 只在一台服务器上执行,存在:单点风险、资源分配不均衡、服务器负载能力有限;
  2. 通过zk、数据库等进行任务属性配置、任务的分配,但操作困难,增加运维成本;
  3. 通过全局“锁”互斥执行:解决多节点重复执行任务

一个好的分布式定时任务系统需要考虑哪些因素(也是任何分布式系统需要考虑的因素):

  1. 高可用性:集群部署,避免单点风险;
  2. 可伸缩性:支持弹性伸缩,可以动态增加、删除节点;
  3. 负载均衡:避免一台机器负载过高,而其他机器一直空闲;
  4. 失效转移:任务都可以持久化到数据库或者文件系统,避免宕机导致的数据丢失,有完善的任务失败重试机制和详细的任务追踪及告警策略

对于分布式定时任务系统来说,最重要的是分布式锁,实现方式有三种:

  1. 基于数据库的实现方式:唯一索引原理,比如一个定时任务一天执行一次,我们就以日期作为唯一索引,谁第一个把当天日期插入成功,谁就有资格执行;
  2. 基于Redis的实现方式:https://blog.csdn.net/zhengchao1991/article/details/87558948
  3. 基于zk的实现方式:https://blog.csdn.net/zhengchao1991/article/details/86509986
     

TODO

发布了142 篇原创文章 · 获赞 345 · 访问量 45万+

猜你喜欢

转载自blog.csdn.net/zhengchao1991/article/details/90768363