定时任务

@Scheduled

@Scheduled(fixedDelay = 5000) 每隔5s执行一次

定时任务在现代项目中非常常见,尤其是在需要定期执行一些后台任务的场景下。@Scheduled 注解是 Spring Framework 中提供的一种非常方便的定时任务调度工具,它可以帮助我们自动化地完成很多重复性、时间驱动的操作。以下是一些典型场景和常用的扩展用法。

常见场景

  1. 数据同步
    • 场景描述:例如,定期将本地数据库的数据同步到外部系统,或者从外部系统拉取数据进行处理。这些操作通常不需要用户干预,且执行频率相对固定。

    • 示例:每 30 分钟从第三方 API 拉取数据并更新本地数据库。

    • 使用的定时任务配置

      @Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
      public void syncDataFromApi() {
          // 拉取数据并更新数据库
      }
      
  2. 日志清理
    • 场景描述:定期清理过期的日志文件或者旧的记录。为了防止日志文件无限增长,通常会设定定期清理机制。

    • 示例:每周清理 30 天前的日志文件。

    • 使用的定时任务配置

      @Scheduled(cron = "0 0 0 * * 1") // 每周一凌晨 12 点清理过期日志
      public void cleanOldLogs() {
          // 删除30天前的日志文件
      }
      
  3. 缓存清理与刷新
    • 场景描述:为了提高系统性能,可以使用缓存来存储频繁访问的数据,但缓存的内容需要定期更新或清理。

    • 示例:每隔 1 小时清理缓存并刷新。

    • 使用的定时任务配置

      @Scheduled(fixedRate = 3600000) // 每1小时清理一次缓存
      public void refreshCache() {
          // 刷新或清理缓存
      }
      
  4. 发送定时邮件通知
    • 场景描述:例如,系统需要定期发送邮件通知用户,如每月的账单通知、促销活动提醒等。

    • 示例:每月的 1 号凌晨 1 点发送账单邮件。

    • 使用的定时任务配置

      @Scheduled(cron = "0 0 1 1 * ?") // 每月的1号凌晨1点
      public void sendMonthlyBilling() {
          // 发送账单邮件
      }
      
  5. 任务调度和队列处理
    • 场景描述:在分布式系统中,可能会使用定时任务来定期检查任务队列、处理未完成的任务或重试失败的任务。

    • 示例:每 10 分钟检查一次任务队列,处理待执行的任务。

    • 使用的定时任务配置

      @Scheduled(fixedDelay = 600000) // 每10分钟执行一次
      public void processTaskQueue() {
          // 检查并处理队列中的任务
      }
      
  6. 健康检查与监控
    • 场景描述:定期检查系统的健康状态,如数据库连接、服务接口可用性等,或者定期生成系统监控报告。

    • 示例:每 5 分钟执行一次健康检查,检测服务是否正常。

    • 使用的定时任务配置

      @Scheduled(fixedRate = 300000) // 每5分钟执行一次
      public void healthCheck() {
          // 执行健康检查,检查数据库连接、外部API等服务状态
      }
      

扩展用法

1. 使用 initialDelay 来延迟任务的第一次执行

有时候你可能不希望任务立即开始执行,而是希望延迟一段时间后才开始,特别是在系统启动时。initialDelay 参数可以让你设定首次执行任务之前的延迟。

@Scheduled(fixedRate = 10000, initialDelay = 5000) // 延迟5秒后开始执行,每10秒执行一次
public void performTaskWithInitialDelay() {
    System.out.println("延迟任务执行时间: " + System.currentTimeMillis());
}

2. 并发问题

Spring 的 @Scheduled 默认是单线程执行的,这意味着即使有多个定时任务,如果一个任务执行时间较长,其他任务会等待。如果有多个任务可能同时执行,需要确保线程安全,或考虑使用 @Async 配合定时任务来处理并发。

@Scheduled(fixedRate = 5000)
@Async  // 使用@Async标记为异步执行,避免阻塞
public void performAsyncTask() {
    System.out.println("异步任务执行时间: " + System.currentTimeMillis());
}

3. 使用 cron 表达式进行复杂的调度

cron 表达式提供了更灵活的调度方式,允许你设定更复杂的时间规则。例如,可以设置在每天的特定时间执行,或者每月的某些日期执行等。

  • 例子 1:每天凌晨 1 点执行任务
  @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
  public void dailyTask() {
      System.out.println("每日任务执行时间: " + System.currentTimeMillis());
  }
  • 例子 2:每周一和周三的下午 2 点执行任务
  @Scheduled(cron = "0 0 14 ? * MON,WED") // 每周一和周三下午2点执行
  public void weeklyTask() {
      System.out.println("每周任务执行时间: " + System.currentTimeMillis());
  }

4. 处理任务失败和重试

对于定时任务中的任务失败处理,可以通过集成 Spring 的 @Retryable 注解来实现失败后重试逻辑,或者使用某些框架(如 Quartz)来实现任务失败后的重试策略。

5. 动态控制定时任务的开启与关闭

有时候可能需要动态控制某些定时任务的开启与关闭,可以结合 @Scheduled@EnableScheduling,通过在代码中动态判断是否开启任务。

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class DynamicTaskScheduler {

    private boolean isTaskEnabled = true; // 控制任务是否执行

    @Scheduled(fixedRate = 10000)
    public void conditionalTask() {
        if (isTaskEnabled) {
            System.out.println("任务执行时间: " + System.currentTimeMillis());
        }
    }

    public void toggleTask(boolean enable) {
        this.isTaskEnabled = enable;
    }
}

通过这种方式,你可以在代码中动态开启或停止某些定时任务。


总结

定时任务是很多项目中的常见需求,特别是用于后台自动化处理的场景。通过 @Scheduled 注解,我们可以非常方便地创建定时任务,常见用法包括定时同步数据、清理日志、刷新缓存等。在使用时,根据具体场景选择合适的调度策略,如 fixedRatefixedDelaycron 等,并确保处理好并发和任务失败的场景。

根据项目的不同需求,定时任务的灵活性和可扩展性可以进一步增强,保证系统的高效性和可维护性。


项目分区导航:⬅️ 14-组队功能 | 15-定时任务 | ➡️ 16-缓存