本文目录导读:

- 方案一:基于“模板方法”模式(最基础、最常用)
- 方案二:基于“策略模式 + 容器”(更灵活、可扩展)
- 方案三:使用现成的批处理框架(最强大、最专业)
- 方案四:响应式流(Reactive Streams,适合高吞吐、异步场景)
- 关键统一点总结(无论选哪种方案)
- 最终建议
这是一个非常核心的Java后端设计问题。“统一批量处理”的核心目标是:用一套标准化的框架/模式,来处理所有符合特定模式的批量任务,从而避免为每个业务写重复的“循环+分页+异常处理+日志”代码。
要实现统一,通常有以下几种主流方案,按成熟度和适用场景排序:
基于“模板方法”模式(最基础、最常用)
这是最直接的方式,通过定义一个抽象模板类,将批处理的固定流程(校验、分页、处理、回调)封装好,子类只需实现具体的业务逻辑。
核心代码结构:
public abstract class AbstractBatchProcessor<IN, OUT> {
// 模板方法:定义了批量处理的固定流程
public final BatchResult<OUT> process(BatchRequest<IN> request) {
// 1. 参数校验(统一)
validate(request);
// 2. 拆分为小批次(统一分页逻辑)
List<List<IN>> batches = partition(request.getDataList(), request.getBatchSize());
// 3. 逐批处理(核心逻辑交给子类)
List<OUT> results = new ArrayList<>();
for (List<IN> batch : batches) {
try {
// 子类实现:处理这一批数据
List<OUT> batchResult = doProcessBatch(batch);
results.addAll(batchResult);
// 4. 统一回调(例如记录进度、发送通知)
afterBatchProcess(batch, batchResult);
} catch (Exception e) {
// 5. 统一异常处理(记录失败队列、重试或终止)
handleBatchError(batch, e);
// 是否继续?由子类决定
if (shouldStopOnError()) {
throw new BatchProcessException("批处理终止", e);
}
}
}
// 6. 统一后处理
return buildResult(results);
}
// 子类只需实现这个核心方法
protected abstract List<OUT> doProcessBatch(List<IN> batch);
// 以下方法提供默认实现,子类可覆盖
protected void validate(BatchRequest<IN> request) { /* 默认校验 */ }
protected void afterBatchProcess(List<IN> batch, List<OUT> result) { /* 空实现 */ }
protected boolean shouldStopOnError() { return false; } // 默认遇到错误继续
protected void handleBatchError(List<IN> batch, Exception e) { /* 记录日志 */ }
protected BatchResult<OUT> buildResult(List<OUT> results) { /* 构建结果 */ }
// 分页工具方法
private List<List<IN>> partition(List<IN> list, int size) {
// 使用 Lists.partition(list, size) 或手动实现
}
}
业务使用示例:
@Component
public class UserImportProcessor extends AbstractBatchProcessor<UserImportDTO, ImportResult> {
@Override
protected List<ImportResult> doProcessBatch(List<UserImportDTO> batch) {
// 这里只关心:如何导入100个用户
return userService.batchInsert(batch);
}
}
优点:简单、无侵入、易理解。 缺点:依赖继承,不够灵活;无法处理异步、流式等复杂场景。
基于“策略模式 + 容器”(更灵活、可扩展)
适用于需要动态切换不同处理方式(同步/异步、单线程/多线程)的场景,将“处理策略”与“批处理框架”解耦。
核心组件:
- 批处理上下文 (BatchContext):存储批次ID、当前进度、状态、参数等。
- 处理策略 (BatchStrategy):定义如何处理一个批次(单线程、线程池、ForkJoin等)。
- 数据读取器 (DataReader):定义如何获取数据(从DB分页、从文件流读取等)。
- 数据处理者 (DataProcessor):定义对单条或一批数据的处理逻辑。
- 执行器 (BatchExecutor):统一编排上述组件。
// 定义接口
public interface BatchStrategy {
<T, R> List<R> execute(List<T> batch, DataProcessor<T, R> processor);
}
// 线程池策略
public class ThreadPoolStrategy implements BatchStrategy {
private ExecutorService executor;
@Override
public <T, R> List<R> execute(List<T> batch, DataProcessor<T, R> processor) {
// 将批次再拆分为子任务,提交到线程池
// 使用 CompletableFuture 异步执行
}
}
// 统一执行器
public class BatchExecutor {
public <T, R> BatchResult<R> execute(BatchRequest<T> request,
DataReader<T> reader,
DataProcessor<T, R> processor,
BatchStrategy strategy) {
// 1. Reader 读取数据(可能分页)
// 2. 构建批次列表
// 3. Strategy 执行每一批
// 4. 统一异常、重试、日志
}
}
优点:高内聚低耦合;不同的批处理任务可以使用不同的策略组合。 缺点:设计复杂度较高。
使用现成的批处理框架(最强大、最专业)
如果项目规模较大(有独立的数据抽取、转换、加载流程,需要监控、重启、事务管理),建议直接引入专业框架。
-
Spring Batch(Java批处理的事实标准)
- 提供
Job、Step、Chunk、Reader、Processor、Writer标准组件。 - 内置重启、跳过、重试、事务管理、指标监控。
- 适用场景:海量数据ETL、报表生成、定时数据同步。
- 提供
-
EasyBatch(轻量级,适合普通业务)
- 基于注解,配置简单。
- 能自动处理分页、并发、失败重试。
-
自定义注解 + AOP(轻量级“框架”)
- 定义一个
@BatchProcess注解。 - 通过AOP拦截,自动实现分页、事务、日志、重试。
- 适用场景:不想引入太重框架,但想统一管理。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface BatchProcess { int batchSize() default 500; boolean retryOnFailure() default false; } @Aspect @Component public class BatchProcessAspect { @Around("@annotation(batchProcess)") public Object handleBatch(ProceedingJoinPoint pjp, BatchProcess batchProcess) { // 获取参数列表,拆分成批次 // 循环调用 pjp.proceed(batch) // 注意:这里需要根据实际情况拆解参数 // 统一异常处理 // 统一日志 } } - 定义一个
响应式流(Reactive Streams,适合高吞吐、异步场景)
如果系统需要处理无限数据流(如实时日志、消息队列),或者需要背压控制,可以使用响应式编程。
- 技术栈:Project Reactor (
Flux,Mono) 或 RxJava。 - 统一方式:利用
Flux.buffer()自动分批次,利用flatMap并行处理,利用retryWhen/onErrorContinue统一错误处理。
// 统一批处理流水线
Flux<DataItem> stream = dataSource.streamAll(); // 假设是无限流
stream
.buffer(1000) // 每1000个元素为一批
.flatMap(batch ->
Mono.fromCallable(() -> processBatch(batch)) // 同步处理
.subscribeOn(Schedulers.parallel()) // 多线程并行
.retryWhen(Retry.max(3)) // 统一重试
.onErrorContinue((err, obj) -> logError(err, obj)) // 统一跳过错误
)
.subscribe();
关键统一点总结(无论选哪种方案)
要实现“统一”,必须统一以下几个方面:
- 分页/拆分逻辑:所有批处理必须按固定大小(例如500条)拆分,框架统一实现
Lists.partition。 - 错误处理策略:全局定义是“遇到错误立即停止”(金融转账场景),还是“跳过错误继续”(数据清洗场景)。
- 重试机制:重试次数、重试间隔(指数退避)、是否只重试失败的部分。
- 事务边界:是每批次一个事务,还是整体一个事务?统一在框架层通过
@Transactional(propagation = Propagation.REQUIRES_NEW)控制。 - 监控与日志:自动记录每个批次的处理时长、成功/失败数量、当前进度百分比。
- 资源限制:统一控制最大并发数、内存中缓冲的数据量。
最终建议
- 简单CRUD、数据量<10万:使用 模板方法模式(方案一),代码量少,维护简单。
- 复杂业务、高并发、多种策略:使用 策略+容器(方案二)。
- 大型企业级应用、海量数据(百万级以上):直接上 Spring Batch(方案三)。
- 流式数据、高吞吐、低延迟:使用响应式流(方案四)。
核心思想是:“把变的东西(业务逻辑)与不变的东西(分页、重试、事务)彻底分离”。