SpringBoot 7种定时任务解决方案

科技   2024-11-11 08:01   河北  


前言

在现代后端开发中,定时任务是一个常见且重要的功能需求。从订单自动取消到定时通知,再到数据汇总,定时任务几乎无处不在。本文将介绍在SpringBoot中实现定时任务的7种不同解决方案,涵盖单点定时任务和分布式定时任务两大类。

定时任务,顾名思义,就是按照预定的时间间隔或特定的时间点执行的任务。在Java生态系统中,有多种方式可以实现定时任务,每种方式都有其特定的应用场景和优缺点。

单点定时任务

1. JDK原始方案

自JDK 1.5起,Java提供了ScheduledExecutorService接口,用于替代老旧的Timer类。ScheduledExecutorService提供了更可靠和灵活的定时任务执行能力。

操作步骤

  1. 创建一个ScheduledExecutorService实例。

  2. 使用scheduleAtFixedRatescheduleWithFixedDelay方法安排任务。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.Date;

public class SomeScheduledExecutorService {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("执行任务:" + new Date());
        }, 1, 30, TimeUnit.SECONDS);
    }
}

2. Spring Task

Spring Framework提供了内置的定时任务支持,通过@Scheduled注解和@EnableScheduling注解可以非常方便地配置定时任务。

操作步骤

  1. 在启动类上添加@EnableScheduling注解。

  2. 在需要定时执行的方法上添加@Scheduled注解,并配置cron表达式。

代码示例

import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;

@Component
@EnableScheduling
public class SomeJob {
    @Scheduled(cron = "0 0/1 * * * ? *")
    public void someTask() {
        System.out.println("每分钟执行一次任务: " + LocalDateTime.now());
    }}

3. 基于Redis实现

Redis也可以用来实现定时任务,通过利用Redis的ZSet和键空间通知功能,可以实现高效的定时任务调度。

操作步骤

  1. 使用Redis的ZSet存储定时任务。

  2. 使用Redis的键空间通知功能监听任务过期事件。

代码示例

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.time.Instant;
import java.util.Set;

@Configuration
@EnableScheduling
public class RedisJob {
    public static final String JOB_KEY = "redis.job.task";
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 添加任务
    public void addTask(String task, Instant instant) {
        stringRedisTemplate.opsForZSet().add(JOB_KEY, task, instant.getEpochSecond());
    }

    // 定时任务队列消费
    @Scheduled(cron = "0 0/1 * * * ? *")
    public void doDelayQueue() {
        long nowSecond = Instant.now().getEpochSecond();
        Set<String> tasks = stringRedisTemplate.opsForZSet().range(JOB_KEY, 0, nowSecond);
        for (String task : tasks) {
            System.out.println("执行任务: " + task);
        }
        stringRedisTemplate.opsForZSet().remove(JOB_KEY, 0, nowSecond);
    }

    // 自定义监听器
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter listenerAdapter)
 
{

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@*__:expired"));
        return container;
    }

    @Bean
    MessageListenerAdapter listenerAdapter(KeyExpiredListener receiver) {
        return new MessageListenerAdapter(receiver, "onMessage");
    }

    @Bean
    KeyExpiredListener keyExpiredListener() {
        return new KeyExpiredListener();
    }
}

class KeyExpiredListener extends KeyExpirationEventMessageListener {
    public KeyExpiredListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 处理过期任务
        String expiredKey = message.getBody();
        System.out.println("Redis键过期: " + new String(expiredKey));
    }
}

分布式定时任务

在分布式系统中,单个节点执行定时任务可能会遇到数据不一致、任务重复执行等问题。因此,需要使用分布式定时任务框架。

4. Quartz

Quartz是一个功能强大的开源任务调度框架,支持复杂的定时规则和任务管理。

操作步骤

  1. 添加Quartz依赖。

  2. 配置Quartz Scheduler。

  3. 编写Job类和Trigger。

代码示例(略,具体配置和代码可以参考Quartz官方文档)。

5. elastic-job-lite

elastic-job-lite是当当网开源的一个分布式任务调度框架,支持分片、容错等功能。

操作步骤

  1. 添加elastic-job-lite依赖。

  2. 配置作业中心和注册中心。

  3. 编写作业实现类。

6. LTS

LTS(Light Task Scheduler)是一个分布式任务调度框架,支持任务的高可用、可扩展和可监控。

操作步骤(略,具体配置和代码可以参考LTS官方文档)。

7. XXL-JOB

XXL-JOB是一个分布式任务调度平台,支持任务的高可用、动态管理、任务失败重试等功能。

操作步骤

  1. 部署XXL-JOB管理后台。

  2. 添加XXL-JOB客户端依赖。

  3. 配置XXL-JOB执行器。

  4. 编写任务执行逻辑。

总结

本文介绍了在SpringBoot中实现定时任务的7种不同解决方案,包括JDK原始方案、Spring Task、基于Redis实现、Quartz、elastic-job-lite、LTS和XXL-JOB。每种方案都有其特定的应用场景和优缺点,开发者可以根据实际需求选择合适的方案。在实际项目中,选择适合的定时任务解决方案,不仅可以提高开发效率,还可以确保系统的稳定性和可靠性。


Java技术前沿
专注分享Java技术,包括但不限于 SpringBoot,SpringCloud,Docker,消息中间件等。
 最新文章