Spring Boot 打造全能异步处理方案,简单高效!

开发 前端
通过本文,开发者不仅可以掌握如何在 Spring Boot 中高效实现异步任务管理,还可以学到如何编写更加清晰、可维护、易扩展的代码。

在系统设计中,遵循“开闭原则”是良好实践。随着业务不断演化和更新,核心代码的频繁改动不仅可能引入更多错误风险,还可能影响整体系统稳定性。尽管新增的功能大多是对现有功能的扩展,但如何在保证性能和质量的前提下平稳实现这些更新,成为了一大挑战。为了解决这一问题,我设计了一套通用的异步处理SDK,能够高效地实现各种异步任务的处理需求。

通过该SDK,不仅可以确保方法正常执行、不影响主业务流程,还能通过可靠的兜底方案保障数据不丢失,实现最终一致性。

设计优势

  1. 无侵入性:独立的数据库、定时任务、消息队列及人工操作界面(支持统一登录认证)。
  2. 事务安全:基于Spring的事务事件机制,异步策略解析失败时不影响主业务流程。
  3. 完善的兜底方案:即使异步策略解析失败(如事务提交后或回滚后),也有多层次的补偿机制(除非数据库或队列存在问题)。

工作原理

  1. 注解缓存:容器初始化时,扫描并缓存所有带有@AsyncExec注解的方法。
  2. AOP切面:方法执行时通过AOP切面发布事件。
  3. 事务监听:通过@TransactionalEventListener实现事务事件监听,处理异步策略。

事务事件监听核心代码

@TransactionalEventListener(fallbackExecution = true, phase = TransactionPhase.AFTER_COMPLETION)
  • fallbackExecution = true:即使没有事务,也能处理事件。
  • TransactionPhase.AFTER_COMPLETION:事务提交或回滚后均会处理事件。

核心组件

  • 消息队列:Kafka
  • 定时任务:XXL Job
  • 数据库:MySQL
  • 前端界面:基于Vue实现
  • 设计模式:策略模式、模板方法、动态代理

以下是一个完整的 Spring Boot 项目示例,展示如何使用 Kafka 消息队列、XXL Job 定时任务、MySQL 数据库和 Vue 前端界面,并结合策略模式、模板方法和动态代理实现通用异步处理功能。

项目结构

核心模块

  1. 任务状态枚举 (TaskStatus):定义任务的生命周期状态。
  2. 任务实体 (AsyncTask):数据库表对应的实体类,记录任务的执行信息。
  3. 任务处理策略 (AsyncStrategy):定义任务的执行逻辑,支持动态扩展。
  4. 任务调度器 (AsyncTaskScheduler):调度任务执行并管理任务状态。
  5. 任务监控器 (TaskMonitorService):实时监控任务状态,提供告警能力。

数据库设计

任务表 DDL

CREATE TABLE async_task (
    id BIGINTAUTO_INCREMENTPRIMARYKEY,
    task_name VARCHAR(255)NOTNULLCOMMENT'任务名称',
    task_status INTNOTNULLDEFAULT0COMMENT'任务状态',
    task_param TEXTCOMMENT'任务参数',
    retry_count INTDEFAULT0COMMENT'重试次数',
    create_time DATETIMEDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
    update_time DATETIMEDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间'
);

核心代码实现

任务状态枚举

package com.icoderoad.async.enums;
public enum TaskStatus {
    INITIAL(0, "初始化"),
    PROCESSING(1, "执行中"),
    SUCCESS(2, "执行成功"),
    FAILED(3, "执行失败");


    private final int code;
    private final String description;


    TaskStatus(int code, String description) {
        this.code = code;
        this.description = description;
    }


    public int getCode() {
        return code;
    }


    public String getDescription() {
        return description;
    }


    public static TaskStatus fromCode(int code) {
        for (TaskStatus status : values()) {
            if (status.code == code) {
                return status;
            }
        }
        throw new IllegalArgumentException("未知任务状态代码:" + code);
    }
}

任务实体

package com.icoderoad.async.entity;
@Data
@TableName("async_task")
public class AsyncTask {
    private Long id;
    private String taskName;
    private TaskStatus taskStatus;
    private String taskParam;
    private Integer retryCount;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

任务策略接口

package com.icoderoad.async.strategy;
public interface AsyncStrategy {
    void execute(String taskParam);
}

任务执行服务

package com.icoderoad.async.service;


import com.icoderoad.async.entity.AsyncTask;
import com.icoderoad.async.enums.TaskStatus;
import com.icoderoad.async.repository.AsyncTaskRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;


@Service
public class AsyncTaskService {


    private final AsyncTaskRepository asyncTaskRepository;


    @Autowired
    public AsyncTaskService(AsyncTaskRepository asyncTaskRepository) {
        this.asyncTaskRepository = asyncTaskRepository;
    }


    /**
     * 获取所有异步任务
     */
    public List<AsyncTask> getAllTasks() {
        return asyncTaskRepository.findAll();
    }


    /**
     * 重试任务
     *
     * @param taskId 任务ID
     * @return 是否重试成功
     */
    @Transactional
    public boolean retryTask(Long taskId) {
        Optional<AsyncTask> optionalTask = asyncTaskRepository.findById(taskId);
        if (optionalTask.isPresent()) {
            AsyncTask task = optionalTask.get();


            // 检查任务是否允许重试
            if (task.getTaskStatus() == TaskStatus.FAILED) {
                task.setTaskStatus(TaskStatus.INITIAL); // 将状态重置为初始化
                task.setRetryCount(task.getRetryCount() + 1); // 增加重试次数
                task.setUpdateTime(LocalDateTime.now());
                asyncTaskRepository.save(task);
                return true;
            }
        }
        return false; // 任务不存在或状态异常
    }


    /**
     * 创建新异步任务
     *
     * @param taskName  任务名称
     * @param taskParam 任务参数
     */
    @Transactional
    public void createAsyncTask(String taskName, String taskParam) {
        AsyncTask newTask = new AsyncTask();
        newTask.setTaskName(taskName);
        newTask.setTaskParam(taskParam);
        newTask.setTaskStatus(TaskStatus.INITIAL);
        newTask.setRetryCount(0);
        newTask.setCreateTime(LocalDateTime.now());
        newTask.setUpdateTime(LocalDateTime.now());
        asyncTaskRepository.save(newTask);
    }


    /**
     * 获取待执行的任务(供调度器使用)
     */
    public List<AsyncTask> getPendingTasks() {
        return asyncTaskRepository.findByTaskStatus(TaskStatus.INITIAL);
    }


    /**
     * 更新任务状态
     *
     * @param task 更新后的任务
     */
    @Transactional
    public void updateTask(AsyncTask task) {
        task.setUpdateTime(LocalDateTime.now());
        asyncTaskRepository.save(task);
    }
}

任务调度器

@Component
public class AsyncTaskScheduler {


    private final AsyncTaskService asyncTaskService;
    private final TaskMonitorService taskMonitorService;


    @Scheduled(fixedRate = 5000)
    public void scheduleTasks() {
        List<AsyncTask> tasks = asyncTaskService.getPendingTasks();
        tasks.forEach(task -> {
            try {
                asyncTaskService.executeTask(task);
                task.setTaskStatus(TaskStatus.SUCCESS);
            } catch (Exception e) {
                task.setTaskStatus(TaskStatus.FAILED);
                taskMonitorService.alertOnFailedTask(task);
            }
            asyncTaskService.updateTask(task);
        });
    }
}

配置文件

application.yml

spring:
  kafka:
    bootstrap-servers: localhost:9092
    consumer:
      group-id: async-tasks-group
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer

Controller 类代码

package com.icoderoad.async.controller;


import com.icoderoad.async.dto.AsyncTaskDto;
import com.icoderoad.async.entity.AsyncTask;
import com.icoderoad.async.service.AsyncTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;


import java.util.List;
import java.util.stream.Collectors;


@RestController
@RequestMapping("/api/tasks")
public class AsyncTaskController {


    private final AsyncTaskService asyncTaskService;


    @Autowired
    public AsyncTaskController(AsyncTaskService asyncTaskService) {
        this.asyncTaskService = asyncTaskService;
    }


    /**
     * 获取所有异步任务
     */
    @GetMapping
    public ResponseEntity<List<AsyncTaskDto>> getAllTasks() {
        List<AsyncTask> tasks = asyncTaskService.getAllTasks();
        List<AsyncTaskDto> taskDtos = tasks.stream()
                .map(task -> new AsyncTaskDto(task.getId(), task.getTaskName(), task.getTaskStatus(), task.getTaskParam()))
                .collect(Collectors.toList());
        return ResponseEntity.ok(taskDtos);
    }


    /**
     * 根据 ID 重试任务
     */
    @PostMapping("/{id}/retry")
    public ResponseEntity<String> retryTask(@PathVariable Long id) {
        boolean result = asyncTaskService.retryTask(id);
        return result ? ResponseEntity.ok("任务重试成功") : ResponseEntity.badRequest().body("任务重试失败,任务可能不存在或状态异常");
    }


    /**
     * 创建新异步任务
     */
    @PostMapping
    public ResponseEntity<String> createTask(@RequestParam String taskName, @RequestParam String taskParam) {
        asyncTaskService.createAsyncTask(taskName, taskParam);
        return ResponseEntity.ok("异步任务创建成功");
    }
}

DTO 类代码

用于返回给前端的任务数据传输对象。

package com.icoderoad.async.dto;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@AllArgsConstructor
@NoArgsConstructor
public class AsyncTaskDto {
    private Long id;
    private String taskName;
    private Integer taskStatus;
    private String taskParam;
}

前端界面 (Vue.js)

异步任务列表
<template>
  <div>
    <h1>异步任务管理</h1>
    <table>
      <thead>
        <tr>
          <th>任务名称</th>
          <th>状态</th>
          <th>参数</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="task in tasks" :key="task.id">
          <td>{{ task.taskName }}</td>
          <td>{{ task.taskStatus }}</td>
          <td>{{ task.taskParam }}</td>
          <td><button @click="retryTask(task.id)">重试</button></td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tasks: [],
    };
  },
  methods: {
    fetchTasks() {
      axios.get('/api/tasks').then(response => {
        this.tasks = response.data;
      });
    },
    retryTask(taskId) {
      axios.post(`/api/tasks/${taskId}/retry`);
    },
  },
  mounted() {
    this.fetchTasks();
  },
};
</script>

总结

本文深入探讨了基于 Spring Boot 开发异步任务管理功能的实现方法,涵盖了从控制器设计到服务层逻辑优化的全过程。通过清晰的代码示例和详细的讲解,读者可以轻松掌握以下关键内容:

1.异步任务管理的核心功能:

  • 实现了任务的增删改查、状态管理,以及失败任务的重试机制,确保异步任务生命周期的完整性。

2.面向业务场景的逻辑优化:

  • 针对任务状态进行了明确的校验与约束,通过 TaskStatus 枚举提升代码的可读性和维护性。
  • 重试逻辑中考虑了任务的状态异常场景,避免因错误操作导致任务状态混乱。

3.面向开发实践的细节增强:

  • 使用 Spring 的 @Transactional 注解确保数据操作的事务安全,避免并发修改导致数据不一致。
  • 在任务的创建、更新、重试操作中添加 createTime 和 updateTime 字段的动态更新,确保时间戳的准确性。
  • 提供了调度器支持的扩展方法,为后续的任务调度和自动化运行奠定基础。

4.代码解耦与扩展性设计:

  • 通过服务层和数据层的职责分离,实现了业务逻辑和数据访问的解耦。
  • 使用 Spring Data JPA 提供的数据仓库方法,使代码更简洁高效,并易于扩展其他查询需求。

5.可靠性与易用性兼备:

  • 在功能实现的同时,确保代码的健壮性和高可维护性。无论是单独的功能测试,还是集成到更复杂的业务流程中,都能稳定运行。

通过本文,开发者不仅可以掌握如何在 Spring Boot 中高效实现异步任务管理,还可以学到如何编写更加清晰、可维护、易扩展的代码。希望这篇文章能够为您在实际开发中提供参考,并助力您设计出更优雅的任务管理系统!

责任编辑:武晓燕 来源: 路条编程
相关推荐

2024-03-26 08:08:08

SpringBPMN模型

2023-04-28 15:15:39

数据库JPA

2020-12-01 08:32:12

Spring Boot

2024-10-15 10:28:43

2024-07-31 15:57:41

2019-01-15 11:40:14

开发技能代码

2024-12-17 00:00:00

Spring线程

2022-04-08 16:27:48

SpringBoot异常处理

2020-01-02 16:30:02

Spring BootJava异步请求

2023-09-13 08:56:51

2022-09-29 09:19:04

线程池并发线程

2009-06-17 16:39:03

Spring JMS

2024-09-26 09:28:06

内存Spring

2023-04-17 23:49:09

开发代码Java

2024-01-01 14:19:11

2020-03-16 17:20:02

异常处理Spring Boot

2024-10-30 08:05:01

Spring参数电子签章

2023-12-08 12:12:21

2024-10-18 11:32:15

2024-11-11 10:02:37

Spring搜索数据
点赞
收藏

51CTO技术栈公众号