IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 基于spring schedule和zookeeper实现的分布式定时任务工具 -> 正文阅读

[Java知识库]基于spring schedule和zookeeper实现的分布式定时任务工具

自己用Java写了个可视化的定时任务工具,取名sundial(日晷)。定时任务的原理就是spring schedule;分布式锁基于zookeeper实现,客户端采用Netflix开源的Curator。JDK用的17,最新的长期支持版本(LTS),数据库是MySQL,同样是最新的MySQL8。

所谓分布式锁,即独立于整个分布式环境之外的全局且唯一的锁的添加、释放的机制。简单的分布式锁可由数据库实现,比如MySQL。但其性能显然不如基于内存的redis、zookeeper快,且不支持锁超时,公平锁功能。用redis也可以做,redis是基于内存且支持持久化的键值对数据库,6.0版本之后更是支持多线程,加上传统技能IO多路复用及底层采用的跳表等高性能的数据结构,性能更上一层楼。redis加锁本质上就是调用其set命令来对同一key设置键值对,value的话可以用当前线程的线程id,在解锁时对value做校验以避免释放其他线程的锁,再给键值对设置一个过期时间以实现锁超时功能。但有可能到了过期时间,持有锁的线程还没执行完成,这时锁已被释放,其他线程获取了锁,再对同一共享资源进行操作,就会出现bug,而且线程安全问题相关的bug难以排查。可以用Redisson搭配Lua脚本来实现锁超时功能,基于其watchdog,每隔一定时间,默认为30秒。如果某客户端持有锁超过30秒,watchdog就会每隔10秒再把key的过期时间再设为30秒。这样,某线程执行的慢,一直持有锁,其他线程也不会获取到当前线程持有的锁。

而zookeeper实现分布式锁,就是利用其临时有序节点特性。在某一指定目录下创建节点,并判断节点序号是否为当前目录下最小,若是,则视为创建锁成功。否则,就对前一个节点添加一个监听事件。如果锁释放,会通知后一个节点,后一个节点再判断自己序号是否最小,最小就获得锁。这里的逻辑类似JUC里的AQS队列同步器的公平锁模式,AQS里有个双向链表,持有锁的线程在头节点,其他的等着抢锁的线程就在连在后面,持有锁的线程释放锁后会通知后一个节点的线程来竞争锁。

囿于篇幅,下面只介绍主要的类和数据库设计。查看详细代码,请移步GitHub,GitHub - riveryue/sundial,这个分支updateCronActivrImmediately。

使用时需要在任务类上加上自定义注解SundialTask,注解的name属性在整个工程是需保持唯一。

下图为示例,线程休眠5秒是为了模拟真实业务系统中的定时任务,测试分布式环境下同一任务只被执行一次。如果不休眠5秒,就不会存在锁竞争,导致每台服务器上都会执行一遍定时任务,造成一种zook分布式锁不生效的假象。

package sundial;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import sundial.annotation.SundialTask;

import java.util.Date;

/**
 * @author yao
 */
@Component
@Slf4j
public class Task2 implements SundialExecute {

    @SundialTask(name = "test2")
    @Override
    public void execute() {
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            log.error("error in schedule ", e);
        }
        log.info("task2 {}", new Date());
        log.info("schedule execute successfully");
    }
}

下图为上述注解的源码:

package sundial;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import sundial.annotation.SundialTask;
import sundial.config.CuratorFrameworkConfig;
import sundial.constant.TaskStatus;
import sundial.dto.TaskConfDTO;
import sundial.service.TaskConfService;
import sundial.utils.SpringUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @author yao
 */
public interface SundialExecute extends Runnable {

    void execute();

    /**
     * valid status of job if available
     */
    @Override
    default void run() {
        TaskConfService taskConfService = SpringUtils.getBean(TaskConfService.class);
        Method[] declaredMethods = this.getClass().getDeclaredMethods();
        String annoVal = StringUtils.EMPTY;
        for (Method declaredMethod : declaredMethods) {
            boolean annotationPresent = declaredMethod.isAnnotationPresent(SundialTask.class);
            if (annotationPresent) {
                SundialTask methodAnno = declaredMethod.getAnnotation(SundialTask.class);
                annoVal = methodAnno.name();
            }
        }
        TaskConfDTO taskConfDTO = taskConfService.queryByTaskName(annoVal);
        if (taskConfDTO != null && taskConfDTO.getStatus().equals(TaskStatus.DISABLE)) {
            return;
        }

        String ZK_LOCK_PATH = "/distributeLock";
        CuratorFramework client = SpringUtils.getBean(CuratorFrameworkConfig.class).curatorFramework();
        final InterProcessMutex mutex = new InterProcessMutex(client, ZK_LOCK_PATH);
        try {
            //获取锁资源
            boolean flag = mutex.acquire(1, TimeUnit.SECONDS);
            if (flag) {
                execute();
            }
        } catch (Exception e) {

        } finally {
            try {
                mutex.release();
            } catch (Exception e) {

            }
        }

    }
}

这是数据库表设计:

CREATE database if NOT EXISTS `sundial` default character set utf8mb4 collate utf8mb4_unicode_ci;
use `sundial`;

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for task_conf
-- ----------------------------
DROP TABLE IF EXISTS `task_conf`;
CREATE TABLE `task_conf` (
  `id` int NOT NULL AUTO_INCREMENT,
  `task_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `status` tinyint DEFAULT NULL COMMENT '1 available, 2 unavailable',
  `cron` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_task_name` (`task_name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ----------------------------
-- Records of task_conf
-- ----------------------------
INSERT INTO `task_conf` VALUES ('1', 'test1', '1', '*/4 * * * * ?');
INSERT INTO `task_conf` VALUES ('2', 'test2', '1', '*/9 * * * * ?');

下面为前端页面,毕竟不是专业前端,所以做的丑,但菜就是菜,菜就是原罪,以后再改改。

我的MySQL和zookeeper装在本地,然后虚拟机就装个jdk,MySQL客户端,数据库安装在本地物理机,所以虚拟机里没必要装完整的MySQL服务。虚拟机里起三个服务以模拟分布式环境。

启动zookeeper,

虚拟机里启动定时任务服务,只贴了一台机器的截图,

?在页面上启用定时任务2,

虚拟机里可以看到只有一台机器打出了任务2的日志,

自此,定时任务只在集群中的一台机器上执行,且执行成功。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-01-25 10:26:52  更:2022-01-25 10:27:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:33:01-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码