Spring Scheduled + Redis 实现分布式定时器(二)

本篇文章在《Spring Scheduled + Redis 实现分布式定时器(一)》上演变!

新版本将引入以下内容:

  • 加入Mysql做持久化管理;(作用:动态管理所有任务)
  • 内置心跳检测定时器;(作用:监听有变更的任务)

流程:

  1. 列表内容
  2. 启动项目实例;
  3. 加载所有的自定义注解和bean信息;
  4. 查询数据库目前存在的任务信息;
  5. 将新的自定义注解加入数据库中;
  6. 执行心跳检测定时器;
  7. 心跳检测数据库中的任务是否有变更;有:执行对应动作!

说明以下要用到的类和作用:

  • KyScheduledFuture: 用于记录当前正在运行的任务;
  • KyTaskDetails: 关联数据库ky_task_details表的实体类,记录所有载入的任务状态;
  • KyTriggerDetails: 关联数据库ky_trigger_details表的实体类,主要是记录定时规则;用于跟KyTaskDetails配对;
  • KyTrigger: 自定义触发器类;里面记录了cron、syncLock、获取下一次执行时间的信息;
  • KyScheduledExecution: 自定义注解的实现类;
  • KyScheduledController: 动态管理任务的控制类;
  • KyScheduled: 自定义注解;
  • HeartBeatJob:心跳任务;

**用户只需要控制ky_task_details和ky_trigger_details两张表即可。 心跳定时器会帮我们自动触发相应的动作;
当然,如果不想使用心跳检测的方式,也可以用提供接口的方式。每次修改ky_task_details信息时,主动去调用一下接口。**

创建mysql表语句:

CREATE TABLE `ky_task_details` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `taskName` varchar(255) COLLATE utf8_bin NOT NULL,
  `beanName` varchar(255) COLLATE utf8_bin NOT NULL,
  `state` int(1) NOT NULL,
  `triggerId` int(11) unsigned zerofill NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE `ky_trigger_details` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_bin NOT NULL,
  `cron` varchar(255) COLLATE utf8_bin NOT NULL,
  `state` varchar(255) COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

接下来分解代码:(借鉴代码的时候可能要根据项目实际情况做些改动)
KyScheduled:新增name和synclock属性;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface KyScheduled {
    /**
     * A cron-like expression, extending the usual UN*X definition to include
     * triggers on the second as well as minute, hour, day of month, month
     * and day of week.  e.g. {@code "0 * * * * MON-FRI"} means once per minute on
     * weekdays (at the top of the minute - the 0th second).
     * @return an expression that can be parsed to a cron schedule
     */
    String cron() default "";
    /**
     * 自定义的定时器任务名称,为空时使用对应的方法名作为任务名;
     * @return ""
     */
    String name() default "";
    /**
     * 是否使用同步锁, 默认:使用
     * @return
     */
    boolean synclock() default true;
}

KyScheduledFuture:

public class KyScheduledFuture {
    private String cron;
    private ScheduledFuture future;
    public KyScheduledFuture(String cron, ScheduledFuture future) {
        this.cron = cron;
        this.future = future;
    }
    public String getCron() {
        return cron;
    }
    public void setCron(String cron) {
        this.cron = cron;
    }
    public java.util.concurrent.ScheduledFuture getFuture() {
        return future;
    }
    public void setFuture(java.util.concurrent.ScheduledFuture future) {
        this.future = future;
    }
}

KyTaskDetails:与数据库ky_task_details表关联的实体类;

public class KyTaskDetails {
    private long id; //id
    private String taskName; //用户自定义的任务名
    private String beanName; //redis的lockName, 也是唯一值
    private KyTrigger trigger; //触发器
    private KyScheduledExecution.Job job; //任务
    private int state = 2; //当前任务是否正在执行。1:执行,2:未执行
    private long triggerId;//触发器id
    private KyTriggerDetails triggerDetails;
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getTaskName() {
        return taskName;
    }
    public void setTaskName(String taskName) {
        this.taskName = taskName;
    }
    public String getBeanName() {
        return beanName;
    }
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
    }
    public long getTriggerId() {
        return triggerId;
    }
    public void setTriggerId(long triggerId) {
        this.triggerId = triggerId;
    }
    public KyTrigger getTrigger() {
        return trigger;
    }
    public void setTrigger(KyTrigger trigger) {
        this.trigger = trigger;
    }
    public KyScheduledExecution.Job getJob() {
        return job;
    }
    public void setJob(KyScheduledExecution.Job job) {
        this.job = job;
    }
    public KyTriggerDetails getTriggerDetails() {
        return triggerDetails;
    }
    public void setTriggerDetails(KyTriggerDetails triggerDetails) {
        this.triggerDetails = triggerDetails;
    }
}

KyTriggerDetails:与数据库ky_trigger_details表关联的实体类;

public class KyTriggerDetails {
    private long id;
    private String name;
    private String cron;
    private int state;
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getCron() {
        return cron;
    }
    public void setCron(String cron) {
        this.cron = cron;
    }
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
    }
}

KyTrigger:自定义触发器,记录自定义注解的属性信息和提供一个获取下一次执行任务时间的方法;

/**
 * 自定义触发器对象
 */
public class KyTrigger implements Trigger, Serializable {
    private String cron;
    private boolean syncLock;
    public KyTrigger(KyScheduled kyScheduled){
        if(kyScheduled.cron() != null && !"".equals(kyScheduled.cron())) {
            this.cron = kyScheduled.cron();
        }
        this.syncLock = kyScheduled.synclock();
    }
    public boolean getSyncLock(){
        return  this.syncLock;
    }
    public String getCron() {
        return cron;
    }
    public void setCron(String cron) {
        if(cron != null && !"".equals(cron)) {
            this.cron = cron;
        }
    }
    @Override
    public Date nextExecutionTime(TriggerContext triggerContext) {
        CronTrigger cronTrigger = new CronTrigger(this.cron);
        return cronTrigger.nextExecutionTime(triggerContext);
    }
}

KyScheduledController:持久化任务的控制器, 主要是记录当前正在运行的任务,以及任务的启动、停止、变更;

@Component
@EnableScheduling
public class KyScheduledController {
    @Autowired
    private KyScheduledExecution scheduledExecution;
    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;
    private Map futureMap = new HashMap<>();
    @Bean
    public ThreadPoolTaskScheduler KyScheduledController() {
        return new ThreadPoolTaskScheduler();
    }
    /**
     * 创建新定时器
     * @param kyTaskDetails KyTaskDetails
     * @return String
     */
    public String startTask(KyTaskDetails kyTaskDetails) {
        String beanName = kyTaskDetails.getBeanName();
        if (futureMap.get(beanName) == null) {
            KyTaskDetails kyTaskInfo = scheduledExecution.getTaskDetailMap().get(beanName);
            if(kyTaskInfo != null) {
                String cron = kyTaskInfo.getTrigger().getCron();
                ScheduledFuture future = threadPoolTaskScheduler.schedule(kyTaskInfo.getJob(), new CronTrigger(cron));
                futureMap.put(kyTaskInfo.getBeanName(), new KyScheduledFuture(cron, future));
            }
            return "startTask";
        }
        return "";
    }
    /**
     * 修改定时器
     * @param kyTaskDetails KyTaskDetails
     * @return String
     */
    public String changeTask(KyTaskDetails kyTaskDetails) {
        String beanName = kyTaskDetails.getBeanName();
        String cron = kyTaskDetails.getTrigger().getCron();
        stopTask(beanName);
        if (futureMap.get(beanName) == null) {
            KyTaskDetails kyTaskInfo = scheduledExecution.getTaskDetailMap().get(beanName);
            kyTaskInfo.getTrigger().setCron(cron);
            ScheduledFuture future = threadPoolTaskScheduler.schedule(kyTaskInfo.getJob(), new CronTrigger(kyTaskInfo.getTrigger().getCron()));
            futureMap.put(kyTaskInfo.getBeanName(), new KyScheduledFuture(cron, future));
            return "changeTask";
        }
        return "";
    }
    /**
     * 关闭定时器
     * @param name String
     * @return String
     */
    public String stopTask(@PathVariable("name") String name) {
        KyScheduledFuture kyFuture = futureMap.get(name);
        if (kyFuture != null) {
            kyFuture.getFuture().cancel(true);
            futureMap.remove(name);
            return "stopTask";
        }
        return "";
    }
    public Map getFutureMap() {
        return futureMap;
    }
}

HeartBeatJob:心跳检测任务不计入持久化范围,主要是监听数据库的变化和执行定时器操作;

@Component
public class HeartBeatJob {
    @Autowired
    private KyScheduledController scheduledController;
    @Autowired
    private KyScheduledExecution scheduledExecution;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @KyScheduled(cron = "0/5 * * * * ?", name = "heartbeatCheck", synclock = false)
    public void heartbeatCheck() {
        Map futureMap = scheduledController.getFutureMap();
        List dbTaskDetailsList = new ArrayList<>();
        List> rows = jdbcTemplate.queryForList("select * from ky_task_details");
        Iterator taskIt = rows.iterator();
        while (taskIt.hasNext()) {
            Map map = (Map) taskIt.next();
            KyTaskDetails ktd = scheduledExecution.getTaskDetailMap().get(map.get("beanName").toString());
            if (ktd != null) {
                ktd.setId(Long.parseLong(map.get("id").toString()));
                ktd.setState(Integer.parseInt(map.get("state").toString()));
                ktd.setTriggerId(Long.parseLong(map.get("triggerId").toString()));
                dbTaskDetailsList.add(ktd);
            }
        }
        for (KyTaskDetails task : dbTaskDetailsList) {
            if (task.getTriggerId() != 0L) {
                Map map;
                try {
                    map = jdbcTemplate.queryForMap("select * from ky_trigger_details where id = " + task.getTriggerId());
                } catch (Exception ex) {
                    map = null;
                }
                if(map != null) {
                    KyTriggerDetails triggerDetails = new KyTriggerDetails();
                    triggerDetails.setId(Long.parseLong(map.get("id").toString()));
                    triggerDetails.setName(map.get("name").toString());
                    triggerDetails.setCron(map.get("cron").toString());
                    triggerDetails.setState(Integer.parseInt(map.get("state").toString()));
                    task.getTrigger().setCron(map.get("cron").toString());
                    task.setTriggerDetails(triggerDetails);
                }
            }
        }
        for (KyTaskDetails task : dbTaskDetailsList) {
            //1、判断任务状态
            KyScheduledFuture future = futureMap.get(task.getBeanName());
            if (future == null && task.getState() == 1) {//新增
                scheduledController.startTask(task);
            } else {
                int closeTask = 2;
                if (task.getState() == closeTask) {
                    scheduledController.stopTask(task.getBeanName());
                } else {
                    String dbTriggerCron = task.getTriggerDetails() != null ?task.getTriggerDetails().getCron() : "";
                    if (!dbTriggerCron.equals(future.getCron()) && !"".equals(dbTriggerCron)) {
                        scheduledController.changeTask(task);
                    }
                }
            }
        }
        //4、关闭定时器
        for (String beanName : futureMap.keySet()) {
            boolean isDel = true;
            for (KyTaskDetails task : dbTaskDetailsList) {
                if (task.getBeanName().equals(beanName)) {
                    isDel = false;
                    break;
                }
            }
            if (isDel) {
                scheduledController.stopTask(beanName);
            }
        }
        System.out.println(new Date() + ", 心跳检测....  ");
    }
}

KyScheduledExecution:主要更变在configureTasks方法中,加入了创建持久化对象;

@Component
public class KyScheduledExecution implements BeanPostProcessor, SchedulingConfigurer, ApplicationContextAware {
    private Log log = LogFactory.getLog(getClass());
    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private int redisPort;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    private Jedis jedis;
    private Map taskDetailMap = new HashMap();
    //记录任务集合
    private List kyTaskList = new ArrayList<>();
    private ApplicationContext applicationContext;
    public Map getTaskDetailMap() {
        return taskDetailMap;
    }
    /**
     * 创建redis客户端
     */
    private void createRedisClient() {
        if (jedis == null) {
            jedis = new Jedis(redisHost, redisPort);
        }
    }
    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        createRedisClient();
        this.applicationContext = context;
    }
    private Object getBean(Class classname) {
        try {
            return this.applicationContext.getBean(classname);
        } catch (Exception e) {
            log.error(e);
            return "";
        }
    }
    public class KyTask {
        private KyScheduled kyScheduled;
        private Method kyMethod;
        public KyScheduled getKyScheduled() {
            return kyScheduled;
        }
        public void setKyScheduled(KyScheduled kyScheduled) {
            this.kyScheduled = kyScheduled;
        }
        public Method getKyMethod() {
            return kyMethod;
        }
        public void setKyMethod(Method kyMethod) {
            this.kyMethod = kyMethod;
        }
    }
    /**
     * 任务对象
     */
    public class Job implements Serializable, Runnable {
        private Method method;
        private String lockName;
        private Object invokeMethod;
        private KyTrigger trigger;
        public String getLockName() {
            return lockName;
        }
        Job(Method m, KyTrigger t) {
            this.trigger = t;
            this.invokeMethod = getBean(m.getDeclaringClass());//获取bean实例
            this.lockName = m.getDeclaringClass().getName() + "." + m.getName();//构造LockName
            this.method = m;
        }
        @Override
        public void run() {
            synchronized (this) {
                if (this.trigger.getSyncLock()) {
                    //获取下次执行时间(秒)
                    long nextTime = (this.trigger.nextExecutionTime(new SimpleTriggerContext()).getTime() - new Date().getTime()) / 1000;
                    //抢占分布式锁
                    String result = setnxLock(this.lockName, (int) nextTime);
                    if (result != null && !"".equals(result)) {
                        try {
                            //执行自定义注解的方法
                            System.out.println("nexttime: " + nextTime);
                            this.method.invoke(this.invokeMethod);
                        } catch (IllegalAccessException | InvocationTargetException e) {
                            e.printStackTrace();
                            log.error(e);
                        }
                    }
                } else {
                    try {
                        //执行自定义注解的方法
                        this.method.invoke(this.invokeMethod);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                        log.error(e);
                    }
                }
            }
        }
    }
    /**
     * 配置定时器
     *
     * @param taskRegistrar ScheduledTaskRegistrar
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        if (taskRegistrar != null) {
            for (KyTask kt : kyTaskList) {
                Method method = kt.getKyMethod();
                //创建触发器
                KyTrigger trigger = new KyTrigger(kt.getKyScheduled());
                //创建任务
                Job job = new Job(method, trigger);
                //自动将心跳任务加入调度器中
                if ("heartbeatCheck".equals(kt.getKyScheduled().name())) {
                    taskRegistrar.addTriggerTask(job, trigger);
                } else {
                    String beanName = method.getDeclaringClass().getName() + "." + method.getName();
                    //创建任务对象
                    KyTaskDetails taskDetails = new KyTaskDetails();
                    taskDetails.setTaskName(!"".equals(kt.getKyScheduled().name()) ? kt.getKyScheduled().name() : method.getName());
                    taskDetails.setBeanName(beanName);
                    taskDetails.setTrigger(trigger);
                    taskDetails.setTriggerId(0L);
                    taskDetails.setJob(job);
                    //创建持久化对象
                    Map objectMap;
                    try {
                        objectMap = jdbcTemplate.queryForMap("select * from ky_task_details where beanName = '" + beanName + "'");
                    } catch (Exception ex) {
                        objectMap = null;
                    }
                    if(objectMap == null) {
                        //插入数据
                        String insertSql = "insert into ky_task_details (taskName, beanName, state, triggerId) values (?,?,?,?)";
                        KeyHolder keyHolder = new GeneratedKeyHolder();
                        jdbcTemplate.update(new PreparedStatementCreator() {
                            public PreparedStatement createPreparedStatement(
                                    Connection connection) throws SQLException {
                                PreparedStatement ps = connection.prepareStatement(insertSql,
                                        new String[] { "id" });
                                ps.setString(1, taskDetails.getTaskName());
                                ps.setString(2, taskDetails.getBeanName());
                                ps.setInt(3, taskDetails.getState());
                                ps.setLong(4, taskDetails.getTriggerId());
                                return ps;
                            }
                        }, keyHolder);
                        taskDetails.setId(keyHolder.getKey().intValue());
                    }
                    taskDetailMap.put(taskDetails.getBeanName(), taskDetails);
                }
            }
        }
    }
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    /**
     * 获取所有自定义注解,并记录注解和方法的信息
     *
     * @param bean     bean
     * @param beanName beanName
     * @return Object
     * @throws BeansException BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());
        if (methods != null) {
            for (Method method : methods) {
                KyScheduled annotation = AnnotationUtils.findAnnotation(method, KyScheduled.class);
                if (annotation != null) {
                    KyTask at = new KyTask();
                    at.setKyScheduled(annotation);
                    at.setKyMethod(method);
                    kyTaskList.add(at);
                }
            }
        }
        return bean;
    }
    /**
     * 获取分布式锁
     *
     * @param lockName 锁名称
     * @param second   加锁时间(秒)
     * @return 如果获取到锁,则返回lockId值,否则为null
     */
    private String setnxLock(String lockName, int second) {
        synchronized (this) {
            //生成随机的Value值
            String lockId = UUID.randomUUID().toString();
            //抢占锁
            Long lock = this.jedis.setnx(lockName, lockId);
            if (lock == 1) {
               //拿到Lock,设置超时时间
               this.jedis.expire(lockName, second - 1);
               return lockId;
            }
            return null;
        }
    }
}

你可能感兴趣的:(Java)