SpringBoot 异步任务持久化方案:崩溃重启不丢任务的完整实现

在日常开发中,我们经常会遇到需要异步处理的场景,比如批量处理患者病历、生成影像报告、同步检查单数据等。SpringBoot 提供的 @Async 注解能快速实现异步任务,但默认情况下,任务仅存储在内存中------一旦系统崩溃或重启,未执行完成的任务会全部丢失,这在医疗、金融等对数据可靠性要求极高的领域是无法接受的。

本文将分享一套工业级的 SpringBoot 异步任务持久化方案,核心解决「任务不丢失、崩溃可恢复、不同任务差异化处理」三大问题,从设计思路到代码实现全程拆解,新手也能轻松落地。

一、核心痛点与设计原则

1. 先明确核心痛点

  • 内存任务易丢失:默认异步任务存储在线程池队列中,系统重启/崩溃直接清空;

  • 任务状态不可追溯:无法知道任务是待执行、执行中还是失败;

  • 不同任务难适配:批量处理、报告生成等任务数据结构不同,需差异化处理;

  • 重试与幂等性问题:任务重试易重复执行,影响业务结果。

2. 方案设计核心原则

针对以上痛点,方案设计需遵循 5 大原则:

  • 「先落地,后执行」:所有任务提交时先写入持久化存储(数据库),确保任务不会因内存清空丢失;

  • 状态全生命周期追踪:为任务标记清晰状态(待执行/执行中/成功/失败),重启后可精准筛选未完成任务;

  • 差异化任务解耦:通过策略模式拆分不同类型任务的业务逻辑,便于维护;

  • 幂等性保障:通过唯一幂等键防止任务重复执行;

  • 失败可重试:区分「执行中崩溃」和「业务失败」,分别处理重启恢复和主动重试。

二、整体架构设计

方案整体分为 5 个核心模块,形成「提交-持久化-执行-恢复-监控」的完整闭环:

复制代码
用户/业务系统 → 任务提交模块(幂等校验+持久化)→ 异步执行模块(线程池+差异化处理)
                          ↓(崩溃重启后)
                     任务恢复模块(扫描未完成任务)→ 重新提交至异步执行模块
                          ↓
                     结果处理模块(更新状态+记录日志)→ (可选)监控告警模块

技术选型(兼顾稳定性与易用性):

  • 持久化存储:MySQL(优先,支持事务与复杂查询;小体量场景可选用 Redis);

  • 异步框架:Spring @Async + 自定义线程池(避免默认线程池缺陷);

  • 重启恢复:CommandLineRunner(项目启动后自动执行恢复逻辑);

  • 差异化处理:策略模式(任务处理器工厂);

  • 分布式适配:Redisson(分布式锁,防止多实例重复执行任务)。

三、核心实现步骤(附完整代码)

以下实现基于 SpringBoot 2.7.x + MyBatis-Plus 3.5.x + MySQL 8.0,完整覆盖从表设计到任务恢复的全流程。

步骤 1:设计任务表(核心:持久化与状态追踪)

任务表是方案的基础,需存储任务类型、差异化数据、状态等核心信息,采用 JSON 字段存储不同任务的差异化数据(无需为每种任务建表):

sql 复制代码
CREATE TABLE `async_task` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID',
      `task_type` varchar(50) NOT NULL COMMENT '任务类型(如:PATIENT_DATA、IMAGING_REPORT)',
      `task_data` json NOT NULL COMMENT '任务数据(JSON格式,适配不同任务)',
      `task_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态:0-待执行 1-执行中 2-成功 3-失败',
      `executor_times` int(11) NOT NULL DEFAULT 0 COMMENT '已执行次数',
      `max_retry_times` int(11) NOT NULL DEFAULT 3 COMMENT '最大重试次数',
      `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      `exec_start_time` datetime DEFAULT NULL COMMENT '执行开始时间',
      `exec_end_time` datetime DEFAULT NULL COMMENT '执行结束时间',
      `error_msg` varchar(1000) DEFAULT NULL COMMENT '失败信息',
      `task_idempotent_key` varchar(64) NOT NULL COMMENT '幂等键(唯一标识任务)',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_idempotent_key` (`task_idempotent_key`), -- 幂等校验索引
      KEY `idx_task_status_create_time` (`task_status`, `create_time`) -- 恢复任务查询索引
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '异步任务表';
    

步骤 2:定义核心实体与枚举

对应任务表设计实体类,同时通过枚举统一管理任务状态(避免硬编码):

java 复制代码
import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;
    import java.time.LocalDateTime;
    
    @Data
    @TableName("async_task")
    public class AsyncTask {
        @TableId(type = IdType.AUTO)
        private Long id;
        /** 任务类型:PATIENT_DATA/IMAGING_REPORT/TEST_FORM */
        private String taskType;
        /** 任务数据(JSON字符串) */
        private String taskData;
        /** 任务状态:0-待执行 1-执行中 2-成功 3-失败 */
        private Integer taskStatus;
        /** 已执行次数 */
        private Integer executorTimes;
        /** 最大重试次数 */
        private Integer maxRetryTimes;
        private LocalDateTime createTime;
        private LocalDateTime updateTime;
        private LocalDateTime execStartTime;
        private LocalDateTime execEndTime;
        /** 失败错误信息 */
        private String errorMsg;
        /** 幂等键(防止重复执行) */
        private String taskIdempotentKey;
    
        // 状态枚举(统一管理,避免硬编码)
        public enum Status {
            PENDING(0, "待执行"),
            EXECUTING(1, "执行中"),
            SUCCESS(2, "执行成功"),
            FAILED(3, "执行失败");
    
            private final int code;
            private final String desc;
    
            Status(int code, String desc) {
                this.code = code;
                this.desc = desc;
            }
    
            public int getCode() {
                return code;
            }
        }
    }
    

步骤 3:配置自定义异步线程池

Spring 默认异步线程池核心线程数少、队列无界,易导致 OOM,需自定义线程池适配业务场景:

java 复制代码
import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    import java.util.concurrent.Executor;
    
    @Configuration
    @EnableAsync // 开启异步支持
    public class AsyncConfig {
    
        /**
         * 自定义异步线程池
         * 核心参数:根据 CPU 核心数 + 业务场景调整
         */
        @Bean("asyncTaskExecutor")
        public Executor asyncTaskExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            // 核心线程数(CPU核心数 * 2,兼顾并发与资源)
            int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
            executor.setCorePoolSize(corePoolSize);
            // 最大线程数(峰值负载时扩容)
            executor.setMaxPoolSize(20);
            // 队列容量(缓冲任务,避免线程频繁创建销毁)
            executor.setQueueCapacity(1000);
            // 线程名称前缀(便于日志排查)
            executor.setThreadNamePrefix("async-task-");
            // 线程空闲超时时间(60秒,空闲线程自动销毁)
            executor.setKeepAliveSeconds(60);
            // 拒绝策略:队列满后抛异常(结合任务重试,避免任务静默丢失)
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
            // 初始化线程池
            executor.initialize();
            return executor;
        }
    }
    

步骤 4:任务提交模块(先持久化,再执行)

封装任务提交服务,核心逻辑:幂等校验 → 任务数据JSON序列化 → 持久化到数据库 → 提交至异步线程池。确保「先落地,后执行」:

java 复制代码
import com.alibaba.fastjson.JSON;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    import javax.annotation.Resource;
    
    @Service
    @Slf4j
    public class AsyncTaskSubmitService {
    
        @Resource
        private AsyncTaskMapper asyncTaskMapper;
        @Resource
        private AsyncTaskExecutorService asyncTaskExecutorService;
    
        /**
         * 通用任务提交方法(适配所有类型任务)
         * @param taskType 任务类型(如 PATIENT_DATA)
         * @param taskData 任务数据(任意对象,自动转JSON)
         * @param idempotentKey 幂等键(如 任务类型+业务ID)
         * @param maxRetryTimes 最大重试次数
         * @param <T> 任务数据类型
         */
        public <T> void submitTask(String taskType, T taskData, String idempotentKey, int maxRetryTimes) {
            // 1. 幂等校验:防止重复提交(如接口重试导致的重复任务)
            AsyncTask existTask = asyncTaskMapper.selectByTaskIdempotentKey(idempotentKey);
            if (existTask != null) {
                log.warn("任务已存在,幂等键:{}", idempotentKey);
                return;
            }
    
            // 2. 任务数据转JSON(适配不同类型任务的差异化数据)
            String taskDataJson = JSON.toJSONString(taskData);
    
            // 3. 构建任务对象并持久化到数据库
            AsyncTask task = new AsyncTask();
            task.setTaskType(taskType);
            task.setTaskData(taskDataJson);
            task.setTaskStatus(AsyncTask.Status.PENDING.getCode()); // 初始状态:待执行
            task.setExecutorTimes(0);
            task.setMaxRetryTimes(maxRetryTimes);
            task.setTaskIdempotentKey(idempotentKey);
            asyncTaskMapper.insert(task);
            log.info("任务持久化成功,任务ID:{},类型:{}", task.getId(), taskType);
    
            // 4. 提交到异步线程池执行
            asyncTaskExecutorService.executeTask(task.getId());
        }
    }
    

步骤 5:异步执行模块(差异化处理+状态更新)

核心模块:负责任务执行、状态更新、异常捕获与重试判断。通过「任务处理器工厂」实现不同类型任务的差异化处理(策略模式)。

首先定义任务处理器接口(统一规范):

java 复制代码
/**
     * 任务处理器接口(不同类型任务实现此接口)
     * @param <T> 任务数据类型
     */
    public interface TaskProcessor<T> {
        /** 获取任务类型(与提交时的taskType对应) */
        String getTaskType();
        /** 获取任务数据的Class(用于JSON反序列化) */
        Class<T> getTaskDataClass();
        /** 核心任务执行逻辑 */
        void process(T taskData);
    }
    

实现具体任务处理器(以病历处理为例):

java 复制代码
import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    
    // 病历处理任务处理器
    @Component
    @Slf4j
    public class PatientDataProcessor implements TaskProcessor<PatientDataDTO> {
    
        @Override
        public String getTaskType() {
            return "PATIENT_DATA"; // 与任务提交时的taskType一致
        }
    
        @Override
        public Class<PatientDataDTO> getTaskDataClass() {
            return PatientDataDTO.class; // 任务数据类型
        }
    
        @Override
        public void process(PatientDataDTO taskData) {
            // 这里是核心业务逻辑:如解析病历、同步数据、生成报表等
            log.info("开始处理病历任务,患者ID:{},病历编号:{}", taskData.getPatientId(), taskData.getMedicalRecordNo());
            // 模拟业务处理(实际场景替换为真实逻辑)
            // medicalRecordService.process(taskData);
        }
    }
    

实现任务处理器工厂(自动注入所有处理器,根据类型匹配):

java 复制代码
import org.springframework.stereotype.Component;
    import javax.annotation.Resource;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    @Component
    public class TaskProcessorFactory {
        // 存储 任务类型 → 处理器 的映射
        private final Map<String, TaskProcessor<?>> processorMap;
    
        // 构造函数自动注入所有TaskProcessor实现类
        public TaskProcessorFactory(List<TaskProcessor<?>> processors) {
            this.processorMap = processors.stream()
                    .collect(Collectors.toMap(TaskProcessor::getTaskType, processor -> processor));
        }
    
        /** 根据任务类型获取对应的处理器 */
        public TaskProcessor<?> getProcessor(String taskType) {
            TaskProcessor<?> processor = processorMap.get(taskType);
            if (processor == null) {
                throw new IllegalArgumentException("未知任务类型:" + taskType);
            }
            return processor;
        }
    }
    

最后实现异步执行服务(核心逻辑):

java 复制代码
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
    
@Service
@Slf4j
public class AsyncTaskExecutorService {
    
    @Resource
    private AsyncTaskMapper asyncTaskMapper;
    @Resource
    private TaskProcessorFactory taskProcessorFactory;
    
    /**
     * 异步执行任务(指定自定义线程池)
     */
    @Async("asyncTaskExecutor")
    public void executeTask(Long taskId) {
        // 1. 查询任务信息(并校验状态)
        AsyncTask task = asyncTaskMapper.selectById(taskId);
        if (task == null) {
            log.error("任务不存在,ID:{}", taskId);
            return;
        }
        // 只有「待执行」状态的任务才能执行(避免重复执行)
        if (!AsyncTask.Status.PENDING.getCode().equals(task.getTaskStatus())) {
            log.warn("任务状态异常,无法执行。任务ID:{},当前状态:{}", taskId, task.getTaskStatus());
            return;
        }
    
        // 2. 乐观锁更新状态为「执行中」(防止多线程/多实例并发执行)
        int updateCount = asyncTaskMapper.updateStatusToExecuting(taskId, AsyncTask.Status.PENDING.getCode());
        if (updateCount == 0) {
            log.warn("任务状态更新失败(可能已被其他线程执行),ID:{}", taskId);
            return;
        }
    
        // 3. 执行任务(核心逻辑)
        try {
            log.info("开始执行任务,ID:{},类型:{}", taskId, task.getTaskType());
            task.setExecStartTime(LocalDateTime.now());
            
            // 3.1 匹配对应的任务处理器
            TaskProcessor<?> processor = taskProcessorFactory.getProcessor(task.getTaskType());
            // 3.2 JSON反序列化任务数据(适配不同类型任务)
            Object taskData = JSON.parseObject(task.getTaskData(), processor.getTaskDataClass());
            // 3.3 执行具体业务逻辑
            ((TaskProcessor<Object>) processor).process(taskData);
    
            // 4. 执行成功:更新状态为「成功」
            asyncTaskMapper.updateTaskSuccess(taskId, LocalDateTime.now());
            log.info("任务执行成功,ID:{}", taskId);
        } catch (Exception e) {
            // 5. 执行失败:更新状态+判断是否重试
            int currentExecTimes = task.getExecutorTimes() + 1;
            String errorMsg = String.format("任务执行失败:%s,已执行次数:%d", e.getMessage(), currentExecTimes);
            log.error(errorMsg, e);
    
            // 5.1 确定新状态:未达最大重试次数→待执行(重启/重新提交可重试),否则→失败
            Integer newStatus = currentExecTimes >= task.getMaxRetryTimes()
                    ? AsyncTask.Status.FAILED.getCode()
                    : AsyncTask.Status.PENDING.getCode();
    
            // 5.2 更新任务状态与失败信息
            asyncTaskMapper.updateTaskFail(
                    taskId, newStatus, currentExecTimes, errorMsg, LocalDateTime.now()
            );
    
            // 5.3 未达最大重试次数:立即重试(可选,也可仅依赖重启恢复)
            if (newStatus == AsyncTask.Status.PENDING.getCode()) {
                executeTask(taskId);
            }
        }
    }
}
    

步骤 6:重启恢复模块(核心:崩溃后不丢任务)

通过 CommandLineRunner 接口,项目启动后自动扫描「待执行」和「执行中」的任务(执行中任务可能是崩溃导致的),重新提交执行:

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
    
@Component
@Slf4j
public class AsyncTaskRecoveryRunner implements CommandLineRunner {
    
    @Resource
    private AsyncTaskMapper asyncTaskMapper;
    @Resource
    private AsyncTaskExecutorService asyncTaskExecutorService;
    
    @Override
    public void run(String... args) throws Exception {
        log.info("===== 开始恢复未完成的异步任务 =====");
        
        // 1. 查询未完成任务:待执行(0)+ 执行中(1)(执行中可能是崩溃导致的)
        List<Integer> unFinishedStatus = Arrays.asList(
                AsyncTask.Status.PENDING.getCode(),
                AsyncTask.Status.EXECUTING.getCode()
        );
        List<AsyncTask> unFinishedTasks = asyncTaskMapper.selectUnFinishedTasks(unFinishedStatus);
        
        if (unFinishedTasks.isEmpty()) {
            log.info("无未完成任务,恢复结束");
            return;
        }
        
        log.info("共发现 {} 个未完成任务,开始重新提交", unFinishedTasks.size());
        
        // 2. 逐个恢复任务
        for (AsyncTask task : unFinishedTasks) {
            // 2.1 若任务状态是「执行中」,先重置为「待执行」(崩溃导致的执行中)
            if (AsyncTask.Status.EXECUTING.getCode().equals(task.getTaskStatus())) {
                asyncTaskMapper.updateStatusToPending(task.getId());
                log.warn("任务 {} 因崩溃处于执行中状态,已重置为待执行", task.getId());
            }
            // 2.2 重新提交任务执行(避免瞬间提交过多压垮系统,加微小延迟)
            asyncTaskExecutorService.executeTask(task.getId());
            TimeUnit.MILLISECONDS.sleep(10);
        }
        
        log.info("===== 未完成任务恢复提交完成 =====");
    }
}
    

步骤 7:核心 Mapper 方法(MyBatis-Plus)

补充任务表的核心查询与更新方法(基于 MyBatis-Plus 扩展):

java 复制代码
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
import java.util.List;
    
public interface AsyncTaskMapper extends BaseMapper<AsyncTask> {
    
    /** 根据幂等键查询任务(幂等校验) */
    AsyncTask selectByTaskIdempotentKey(@Param("idempotentKey") String idempotentKey);
    
    /** 乐观锁更新状态为执行中(防并发) */
    int updateStatusToExecuting(@Param("taskId") Long taskId, @Param("oldStatus") Integer oldStatus);
    
    /** 更新任务为成功状态 */
    int updateTaskSuccess(@Param("taskId") Long taskId, @Param("endTime") LocalDateTime endTime);
    
    /** 更新任务为失败状态 */
    int updateTaskFail(@Param("taskId") Long taskId,
                       @Param("newStatus") Integer newStatus,
                       @Param("execTimes") Integer execTimes,
                       @Param("errorMsg") String errorMsg,
                       @Param("endTime") LocalDateTime endTime);
    
    /** 查询未完成任务(待执行+执行中) */
    List<AsyncTask> selectUnFinishedTasks(@Param("statusList") List<Integer> statusList);
    
    /** 将执行中任务重置为待执行(崩溃恢复用) */
    int updateStatusToPending(@Param("taskId") Long taskId);
}
    

四、进阶优化(分布式+监控+死信处理)

以上基础方案可满足单体应用需求,若需适配分布式场景或提升可用性,需补充以下优化:

1. 分布式锁(多实例部署必备)

多实例部署时,需防止同个任务被多个实例重复执行,通过 Redisson 分布式锁优化:

java 复制代码
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
    
// 在 AsyncTaskExecutorService 的 executeTask 方法中添加锁逻辑
RLock lock = redissonClient.getLock("async_task_lock_" + taskId);
try {
    // 尝试加锁(5秒超时,30秒自动释放,防止死锁)
    if (!lock.tryLock(5, 30, TimeUnit.SECONDS)) {
        log.warn("任务 {} 已被其他实例锁定执行", taskId);
        return;
    }
    // 原有任务执行逻辑...
} finally {
    // 释放锁(确保当前线程持有锁时才释放)
    if (lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

2. 任务超时控制

部分任务可能因业务异常卡住(如调用外部接口超时),需添加超时扫描机制:

java 复制代码
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
    
@Component
@Slf4j
public class AsyncTaskTimeoutScanner {
    
    @Resource
    private AsyncTaskMapper asyncTaskMapper;
    
    // 每5分钟扫描一次超时任务(执行中超过30分钟视为超时)
    @Scheduled(fixedRate = 5 * 60 * 1000)
    public void scanTimeoutTasks() {
        List<AsyncTask> timeoutTasks = asyncTaskMapper.selectTimeoutExecutingTasks(30);
        for (AsyncTask task : timeoutTasks) {
            // 重置为待执行状态,等待重试
            asyncTaskMapper.updateStatusToPending(task.getId());
            log.warn("任务 {} 执行超时(超过30分钟),已重置为待执行", task.getId());
        }
    }
}
    

3. 死信任务处理

达到最大重试次数仍失败的任务(死信任务),需单独存储便于人工介入:

java 复制代码
-- 死信任务表
CREATE TABLE `async_task_dead_letter` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `task_id` bigint(20) NOT NULL COMMENT '原任务ID',
  `task_type` varchar(50) NOT NULL COMMENT '任务类型',
  `task_data` json NOT NULL COMMENT '任务数据',
  `dead_reason` varchar(1000) NOT NULL COMMENT '失败原因',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_task_id` (`task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '异步任务死信表';
    

在任务执行失败且达到最大重试次数时,插入死信表:

java 复制代码
// 任务执行失败逻辑中添加
if (newStatus == AsyncTask.Status.FAILED.getCode()) {
    // 插入死信表
    AsyncTaskDeadLetter deadLetter = new AsyncTaskDeadLetter();
    deadLetter.setTaskId(task.getId());
    deadLetter.setTaskType(task.getTaskType());
    deadLetter.setTaskData(task.getTaskData());
    deadLetter.setDeadReason(errorMsg);
    asyncTaskDeadLetterMapper.insert(deadLetter);
    log.error("任务 {} 已加入死信表,原因:{}", taskId, errorMsg);
}
    

4. 监控告警

通过 SpringBoot Actuator 暴露任务状态指标,结合 Prometheus+Grafana 可视化,失败任务数超过阈值时触发钉钉/邮件告警:

java 复制代码
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
    
// 自定义监控端点,暴露任务状态统计
@Component
@Endpoint(id = "asyncTaskStatus")
public class AsyncTaskStatusEndpoint {
    
    @Resource
    private AsyncTaskMapper asyncTaskMapper;
    
    @ReadOperation
    public Map<String, Object> getTaskStatus() {
        Map<String, Object> statusMap = new HashMap<>();
        statusMap.put("pendingCount", asyncTaskMapper.countByStatus(AsyncTask.Status.PENDING.getCode()));
        statusMap.put("executingCount", asyncTaskMapper.countByStatus(AsyncTask.Status.EXECUTING.getCode()));
        statusMap.put("successCount", asyncTaskMapper.countByStatus(AsyncTask.Status.SUCCESS.getCode()));
        statusMap.put("failedCount", asyncTaskMapper.countByStatus(AsyncTask.Status.FAILED.getCode()));
        return statusMap;
    }
}
    

五、使用示例(业务层调用)

最后展示业务层如何提交任务(以病历处理为例):

java 复制代码
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
    
@Service
public class PatientDataService {
    
    @Resource
    private AsyncTaskSubmitService asyncTaskSubmitService;
    
    /**
     * 提交病历处理任务
     */
    public void processPatientData(PatientDataDTO dto) {
        // 1. 构建幂等键(任务类型+患者ID+病历编号,确保唯一)
        String idempotentKey = "PATIENT_DATA_" + dto.getPatientId() + "_" + dto.getMedicalRecordNo();
        // 2. 提交任务(类型:PATIENT_DATA,最大重试3次)
        asyncTaskSubmitService.submitTask("PATIENT_DATA", dto, idempotentKey, 3);
    }
}
    

六、核心总结

本文方案的核心价值在于「把不稳定的内存任务,变成可追溯、可恢复的持久化任务」,关键要点:

  • 「先落地,后执行」是任务不丢失的核心;

  • 状态追踪+重启扫描是崩溃恢复的基础;

  • 策略模式(任务处理器)实现不同任务的差异化解耦;

  • 幂等性+分布式锁是分布式场景的必备保障。

该方案已在医疗系统中落地验证,适配批量处理、报告生成等多种异步场景,稳定性拉满。如果你的业务也有异步任务可靠性需求,可直接基于本文代码改造使用~

相关推荐
我是koten14 小时前
K8s启动pod失败,日志报非法的Jar包排查思路(Invalid or corrupt jarfile /app/xxxx,jar)
java·docker·容器·kubernetes·bash·jar·shell
Andy工程师14 小时前
Filter 的加载机制 和 Servlet 容器(如 Tomcat)的请求处理流程
spring boot
WX-bisheyuange14 小时前
基于Spring Boot的库存管理系统的设计与实现
java·spring boot·后端
明天好,会的14 小时前
分形生成实验(三):Rust强类型驱动的后端分步实现与编译时契约
开发语言·人工智能·后端·rust
J_liaty14 小时前
Docker 部署 Spring Boot 项目完整指南:从零到生产环境
spring boot·docker·容器
YanDDDeat14 小时前
【JVM】类初始化和加载
java·开发语言·jvm·后端
码农水水14 小时前
阿里Java面试被问:单元测试的最佳实践
java·面试·单元测试
indexsunny14 小时前
互联网大厂Java面试实战:Spring Cloud微服务与Redis缓存在电商场景中的应用
java·spring boot·redis·spring cloud·微服务·消息队列·电商
hunter145014 小时前
2026.1.4 html简单制作
java·前端·笔记·html