在系统设计中,遵循“开闭原则”是良好实践。随着业务不断演化和更新,核心代码的频繁改动不仅可能引入更多错误风险,还可能影响整体系统稳定性。尽管新增的功能大多是对现有功能的扩展,但如何在保证性能和质量的前提下平稳实现这些更新,成为了一大挑战。为了解决这一问题,我设计了一套通用的异步处理SDK,能够高效地实现各种异步任务的处理需求。
通过该SDK,不仅可以确保方法正常执行、不影响主业务流程,还能通过可靠的兜底方案保障数据不丢失,实现最终一致性。
设计优势
- 无侵入性:独立的数据库、定时任务、消息队列及人工操作界面(支持统一登录认证)。
- 事务安全:基于Spring的事务事件机制,异步策略解析失败时不影响主业务流程。
- 完善的兜底方案:即使异步策略解析失败(如事务提交后或回滚后),也有多层次的补偿机制(除非数据库或队列存在问题)。
工作原理
- 注解缓存:容器初始化时,扫描并缓存所有带有@AsyncExec注解的方法。
- AOP切面:方法执行时通过AOP切面发布事件。
- 事务监听:通过@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 前端界面,并结合策略模式、模板方法和动态代理实现通用异步处理功能。
项目结构
核心模块
- 任务状态枚举 (TaskStatus):定义任务的生命周期状态。
- 任务实体 (AsyncTask):数据库表对应的实体类,记录任务的执行信息。
- 任务处理策略 (AsyncStrategy):定义任务的执行逻辑,支持动态扩展。
- 任务调度器 (AsyncTaskScheduler):调度任务执行并管理任务状态。
- 任务监控器 (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 中高效实现异步任务管理,还可以学到如何编写更加清晰、可维护、易扩展的代码。希望这篇文章能够为您在实际开发中提供参考,并助力您设计出更优雅的任务管理系统!