Spring定时任务设计方案

Author Avatar
邬程峰
发表:2026-04-17 16:04:37
修改:2026-04-22 11:52:44

在 Spring Cloud 微服务架构中,多个实例部署同一个微服务时,若需要执行定时任务(如每天凌晨清理日志、同步数据等),必须确保同一时间只有一个实例执行该任务,否则会造成重复执行、数据不一致等问题。为实现这一点,可以借助 Redis 分布式锁 来协调多个实例。

下面将详细介绍如何在 Spring Cloud 微服务中使用 Redis 实现分布式定时任务的方案。

一、整体思路

  • 使用 @Scheduled 注解定义定时任务。

  • 在定时任务执行前尝试获取 Redis 分布式锁。

  • 若成功获取锁,则执行业务逻辑;否则跳过本次执行。

  • 任务执行完成后释放锁。

  • 锁需设置合理的过期时间(防止死锁)。

  • 使用 Redis 的 SET key value NX PX 命令原子性地加锁。

二、核心组件设计

1. Redis 分布式锁工具类

查看项目,我的在 common 包中已经存在了可以使用的 redis 组件

建议去git仓库找开源的代码或者直接让ai写个工具类

其中存在 set() 方法可以很方便设置 redis 分布式事务锁

/**

 * 普通缓存放入并设置时间

 *

 * @param key   键

 * @param value 值

 * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期

 * @return true成功 false 失败

 * @author c02822

 */

public boolean set(String key, Object value, long time) {

    try {

        if (time > 0) {

            redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);

        } else {

            set(key, value);

        }

        return true;

    } catch (Exception e) {

        log.error("except.", e);

        return false;

    }

}

2. 定时任务服务类

需要的定时任务:每天在固定的某一个时间将所有产品,所有环境的所有产品的工作负载状态存储到一个数据库表中。

以上面这个需求为例,首先需要明确的是用于存储数据的数据表。为此你新建两个数据库,建表语句如下:

-- 1. 工作负载每日快照表

CREATE TABLE workload_daily_snapshot (

    id BIGINT AUTO_INCREMENT PRIMARY KEY,

    snapshot_date DATE NOT NULL, -- 快照日期

    namespace VARCHAR(100) NOT NULL,

    workload_name VARCHAR(255) NOT NULL,

    workload_type VARCHAR(50) NOT NULL, -- 'Deployment'/'StatefulSet'

    product VARCHAR(100), -- 产品线

    environment VARCHAR(50), -- 环境

    desired_replicas INT NOT NULL DEFAULT 0,

    available_replicas INT NOT NULL DEFAULT 0,

    image VARCHAR(500),

    cpu_request_m DECIMAL(10,2), -- CPU请求(毫核)

    memory_request_mi INT, -- 内存请求(MiB)

    cpu_limit_m DECIMAL(10,2), -- CPU限制(毫核)

    memory_limit_mi INT, -- 内存限制(MiB)

    created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    INDEX idx_date_namespace (snapshot_date, namespace),

    INDEX idx_product_env (product, environment),

    UNIQUE KEY uk_daily_workload (snapshot_date, namespace, workload_name, workload_type)

);

-- 2. Pod每日状态表

CREATE TABLE pod_daily_status (

    id BIGINT AUTO_INCREMENT PRIMARY KEY,

    snapshot_date DATE NOT NULL,

    namespace VARCHAR(100) NOT NULL,

    pod_name VARCHAR(255) NOT NULL,

    workload_name VARCHAR(255), -- 所属工作负载

    workload_type VARCHAR(50), -- 所属工作负载类型

    status VARCHAR(50) NOT NULL, -- Running/Pending/Failed等

    node_name VARCHAR(255),

    restart_count INT DEFAULT 0,

    product VARCHAR(100),

    environment VARCHAR(50),

    created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    INDEX idx_date_status (snapshot_date, status),

    INDEX idx_workload (namespace, workload_name),

    INDEX idx_node (node_name)

);

```

此外我写了个定时任务的模板

```java

// ScheduledTaskService.java

@Slf4j

@Service

public class ScheduledTaskService {

    /**

    * redis工具包

    */

    @Autowired

    @Qualifier("redisUtil")

    private RedisUtil redisUtil;

    // 每天凌晨 2 点执行

    @Scheduled(cron = "0 0 2 * * ?")

    public void dailyCleanupTask() {

        String lockKey = "lock:daily_cleanup_task";

        long expireTime = 60 * 10; // 10 分钟,根据任务最大执行时间设定

        if (!redisUtil.hasKey(lockKey)) {

           redisUtil.set(lockKey, 1 , expireTime);

           // 业务逻辑

           。。。

        }

}

```

## 3. 配置类(启用定时任务 & Redis)

```java

// AppConfig.java

package com.example.config;

import org.springframework.context.annotation.Configuration;

import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration

@EnableScheduling

public class AppConfig {

    // 启用 @Scheduled

}

三、注意事项

锁的过期时间:必须大于任务最大可能执行时间,避免任务未完成锁就过期,导致其他节点抢占锁造成并发执行。

锁的 key 命名规范:如 lock:service\_name:task\_name,便于管理和监控。

异常处理:务必在 finally 块中释放锁,防止因异常导致锁未释放。

避免主从切换下的锁失效:Redis 主从异步复制可能导致锁在主节点加锁后,主挂掉,从节点未同步锁信息就被提升为主,此时另一个客户端可能再次加锁。若对一致性要求极高,应使用 RedLock 算法或改用 ZooKeeper / etcd。但在大多数业务场景中,单 Redis 实例 + 合理过期时间已足够。

四、总结

通过 Redis 分布式锁,我们可以在 Spring Cloud 多实例环境下安全地执行定时任务,确保幂等性和唯一性。本方案轻量、易理解,适用于大多数业务场景。

评论