MST

星途 面试题库

面试题:Java的Spring Boot定时任务在集群环境下的分布式调度实现

当Spring Boot应用部署在集群环境中,如何实现定时任务的分布式调度,确保同一任务在集群中只执行一次。请详细描述实现方案,包括可能用到的技术(如Zookeeper、Redis等),以及设计分布式调度算法的思路和关键步骤。
44.0万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

1. 使用Zookeeper实现分布式调度

  • 技术原理:Zookeeper是一个分布式协调服务,利用其临时有序节点和watch机制来实现分布式锁。
  • 设计思路
    • 每个应用实例启动时,尝试在Zookeeper指定路径下创建一个临时有序节点。
    • 拥有最小序号的节点获得执行定时任务的权利。
    • 其他节点通过watch机制监听序号比自己小一位的节点,当该节点消失(任务执行完毕或节点故障)时,重新竞争执行权。
  • 关键步骤
    • 引入依赖:在Spring Boot项目的pom.xml中添加Zookeeper客户端依赖,如Curator。
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>4.2.0</version>
    </dependency>
    
    • 创建Zookeeper客户端:在Spring配置类中创建Zookeeper客户端实例。
    @Configuration
    public class ZookeeperConfig {
        @Value("${zookeeper.connectString}")
        private String connectString;
        @Value("${zookeeper.sessionTimeoutMs}")
        private int sessionTimeoutMs;
        @Value("${zookeeper.connectionTimeoutMs}")
        private int connectionTimeoutMs;
    
        @Bean
        public CuratorFramework curatorFramework() {
            return CuratorFrameworkFactory.builder()
                  .connectString(connectString)
                  .sessionTimeoutMs(sessionTimeoutMs)
                  .connectionTimeoutMs(connectionTimeoutMs)
                  .build();
        }
    }
    
    • 实现分布式锁和任务调度:在定时任务类中,利用Curator的InterProcessMutex实现分布式锁。
    import org.apache.curator.framework.CuratorFramework;
    import org.apache.curator.framework.recipes.locks.InterProcessMutex;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    @Component
    public class DistributedTask {
        private static final String LOCK_PATH = "/distributed-lock/task";
        @Autowired
        private CuratorFramework curatorFramework;
    
        @Scheduled(cron = "0 0 0 * * *") // 每天0点执行
        public void executeTask() throws Exception {
            InterProcessMutex mutex = new InterProcessMutex(curatorFramework, LOCK_PATH);
            try {
                if (mutex.acquire(10, TimeUnit.SECONDS)) {
                    try {
                        // 执行任务逻辑
                        System.out.println("执行定时任务...");
                    } finally {
                        mutex.release();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

2. 使用Redis实现分布式调度

  • 技术原理:Redis的SETNX(SET if Not eXists)命令可以实现原子性的设置操作,利用此特性来实现分布式锁。
  • 设计思路
    • 每个应用实例在执行定时任务前,尝试使用SETNX命令在Redis中设置一个特定的键值对,键为任务的唯一标识,值可以是任意内容。
    • 如果设置成功,说明该实例获得了执行任务的权利;如果设置失败,说明已有其他实例在执行该任务。
    • 为防止死锁,需要给锁设置一个过期时间。
  • 关键步骤
    • 引入依赖:在pom.xml中添加Spring Data Redis依赖。
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    • 配置Redis:在application.properties中配置Redis连接信息。
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    
    • 实现分布式锁和任务调度:在定时任务类中,使用RedisTemplate实现分布式锁。
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    @Component
    public class DistributedTask {
        private static final String LOCK_KEY = "distributed:task:lock";
        private static final String LOCK_VALUE = "task-lock-value";
        private static final long EXPIRE_TIME = 10 * 1000; // 10秒过期时间
        @Autowired
        private RedisTemplate<String, String> redisTemplate;
    
        @Scheduled(cron = "0 0 0 * * *")
        public void executeTask() {
            Boolean result = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, LOCK_VALUE, EXPIRE_TIME);
            if (result != null && result) {
                try {
                    // 执行任务逻辑
                    System.out.println("执行定时任务...");
                } finally {
                    redisTemplate.delete(LOCK_KEY);
                }
            }
        }
    }
    

分布式调度算法思路总结

无论是使用Zookeeper还是Redis,核心思路都是通过一种分布式协调机制(锁)来保证同一时刻只有一个实例能够执行定时任务。在设计分布式调度算法时,需要考虑锁的获取与释放机制,避免死锁和重复执行的情况,同时要处理好节点故障、网络波动等异常情况,确保系统的稳定性和可靠性。