批量处理框架 (Batch Processing Framework)
一个基于游标的通用批量数据处理框架,采用策略模式设计,支持串行和并行处理模式,内置重试机制和窗口分页处理,适用于各种需要批量处理数据的业务场景。
📖 框架简介
批量处理框架提供了一个高度可扩展的批量数据处理解决方案,通过游标机制实现高效的数据分页,支持多种数据类型的游标(如自增ID、时间戳、业务编码等)。框架采用策略模式,将业务逻辑与框架逻辑完全分离,让开发者专注于业务实现。
✨ 核心特性
1. 灵活的游标分页处理
- 基于泛型游标的分页机制,支持多种数据类型(ID、时间戳、业务编码等)
- 支持断点续传,处理中断后可继续
- 可配置的窗口大小(默认5000条)
- 自动管理处理进度和游标更新
2. 串行与并行双模式
- 串行模式:适合数据量小、需要严格顺序处理的场景
- 并行模式:支持多线程并发处理,大幅提升处理效率
- 智能线程池管理,支持外部线程池注入或自动创建
3. 强大的重试机制
- 可配置的重试次数和退避时间
- 串行和并行处理均支持重试
- 自动处理异常,提高处理成功率
- 支持中断异常的正确处理
4. 策略模式设计
- 高度可扩展的架构设计
- 策略工厂统一管理,支持多策略并存
- 业务逻辑与框架逻辑完全分离
- 支持自定义参数类
5. 智能的保存/更新机制
- 自动区分新数据和更新数据
- 支持批量保存和批量更新
- 可自定义判断逻辑
6. 线程池管理优化
- 支持外部线程池注入,复用项目已有线程池
- 自动根据CPU核心数计算合理的线程数
- 使用有界队列,避免OOM风险
🎯 适用场景
1. 数据迁移与同步
将数据从一个系统迁移到另一个系统,或保持多个系统间的数据同步。
优势:
- 支持断点续传,迁移中断后可继续
- 并行处理大幅提升迁移速度
- 重试机制保证数据完整性
示例场景:
- 数据库迁移(MySQL → PostgreSQL)
- 数据仓库ETL处理
- 多数据源数据同步
2. 批量数据加工处理
对大量数据进行批量计算、转换、清洗等操作。
优势:
- 窗口分页避免内存溢出
- 并行处理提升处理效率
- 支持长时间运行的批处理任务
示例场景:
- 订单数据批量计算
- 用户画像批量更新
- 报表数据批量生成
- 数据清洗和标准化
3. 定时任务批量处理
定时执行批量任务,如每日对账、批量推送等。
优势:
- 可配置最大处理轮次,控制执行时间
- 支持失败重试,提高任务成功率
- 详细的日志记录,便于问题排查
示例场景:
- 每日对账任务
- 批量消息推送
- 批量数据校验
- 定时数据归档
4. 数据修复与补偿
修复历史数据问题,或补偿失败的业务操作。
优势:
- 基于游标机制,可精确控制处理范围
- 支持增量处理,只处理需要修复的数据
- 重试机制确保修复成功
示例场景:
- 历史订单状态修复
- 用户积分补偿
- 数据一致性修复
- 业务数据回滚
🏗️ 架构设计
核心组件
BatchProcessingFactory
策略工厂 - 统一管理策略
AbstractCursorBatchTaskStrategy
抽象策略 - 核心处理逻辑
process
统一入口
processSerialWindow
串行处理
processParallelWindow
并行处理
processOneBatch
单批次处理
RetryExecutor
重试执行器
ExecutorHolder
线程池管理
处理流程
是
否
串行模式
并行模式
是
否
开始
创建参数对象
BaseBatchParam<K>
注册策略到工厂
调用 process 方法
窗口分页循环
获取Key列表
loadKeysWithCursor
数据为空?
结束
判断处理模式
processSerialWindow
串行处理
processParallelWindow
并行处理
RetryExecutor.execute
重试执行
batchProcessTasks
批量处理任务
saveOrUpdateProcessedData
保存/更新数据
多线程并行处理
ExecutorHolder
每个线程执行
RetryExecutor.execute
batchProcessTasks
批量处理任务
saveOrUpdateProcessedData
保存/更新数据
汇总结果
更新游标
resolveNextCursor
达到最大轮次?
🚀 快速开始
1. 添加依赖
方式一:直接下载(最简单)
- 访问项目 Gitee 页面
- 点击右上角 克隆/下载 → 下载 ZIP
- 解压到本地目录
方式二:Git 克隆(推荐)
bash
# HTTPS 方式
git clone https://gitee.com/ItmeiCode/batch-processing-framework.git
步骤 1:安装到本地 Maven 仓库
下载项目后,在项目根目录执行:
bash
mvn clean install
步骤 2:在你的项目中添加依赖
在你的项目的 pom.xml 中添加:
xml
<dependency>
<groupId>com.itmei</groupId>
<artifactId>batch-processing-framework</artifactId>
<version>1.0.0</version>
</dependency>
步骤 3:编译你的项目
bash
mvn clean compile
2. 实现策略类
java
@Component
public class OrderProcessStrategy
extends AbstractCursorBatchTaskStrategy<Order, Long, BaseBatchParam<Long>> {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderService orderService;
// 必须实现:根据游标获取Key列表
@Override
protected List<Long> loadKeysWithCursor(Long lastCursor, BaseBatchParam<Long> param) {
// lastCursor 为上一次处理完成的游标(首次为 null)
int windowSize = param.getWindowSize();
return orderMapper.selectOrderIdsByCursor(lastCursor, windowSize);
}
// 必须实现:批量处理任务
@Override
protected List<Order> batchProcessTasks(List<Long> orderIds, BaseBatchParam<Long> param) {
// 1. 查询订单数据
List<Order> orders = orderMapper.selectByIds(orderIds);
// 2. 业务处理逻辑
return orders.stream()
.map(order -> {
order.setStatus("PROCESSED");
order.setProcessTime(new Date());
return order;
})
.collect(Collectors.toList());
}
// 可选:判断是否为新订单(用于区分保存和更新)
@Override
protected boolean isNewTask(Order order) {
return order.getId() == null;
}
// 可选:批量保存
@Override
protected boolean saveBatch(List<Order> orders) {
return orderService.saveBatch(orders);
}
// 可选:批量更新
@Override
protected boolean updateBatchById(List<Order> orders) {
return orderService.updateBatchById(orders);
}
}
3. 注册策略并执行
java
@Service
public class OrderBatchService {
@Autowired
private OrderProcessStrategy orderStrategy;
public void processOrders() {
// 1. 注册策略
BatchProcessingFactory.registerStrategy(orderStrategy);
// 2. 创建参数(使用泛型,支持多种游标类型)
BaseBatchParam<Long> param = new BaseBatchParam<>();
param.setWindowSize(1000);
param.setMaxRounds(100);
param.setMaxRetryTimes(3);
param.setRetryBackoffMs(500L);
// 3. 串行处理
int total = orderStrategy.process(param);
System.out.println("处理完成,共处理: " + total + " 条订单");
// 4. 或者并行处理
param.enableParallel(8, 2000); // 8个线程,窗口大小2000
int total2 = orderStrategy.process(param);
System.out.println("并行处理完成,共处理: " + total2 + " 条订单");
}
}
📝 参数配置说明
BaseBatchParam 参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| lastCursor | K | null | 上一次处理完成的游标(首次为null) |
| windowSize | Integer | 5000 | 每次处理的记录数量(窗口大小) |
| maxRounds | Integer | Integer.MAX_VALUE | 最大处理轮次,防止无限循环 |
| parallel | boolean | false | 是否启用并行处理 |
| parallelThreads | Integer | 6 | 并行处理使用的线程数(最大为CPU核心数×2) |
| minParallelBatchSize | Integer | 500 | 并行处理时,单线程最小处理记录数 |
| maxParallelBatchSize | Integer | 3000 | 并行处理时,单线程最大处理记录数 |
| maxRetryTimes | Integer | 3 | 单个批次允许的最大重试次数 |
| retryBackoffMs | Long | 500L | 重试之间的等待时间(毫秒) |
配置示例
java
// 串行处理配置
BaseBatchParam<Long> param = new BaseBatchParam<>();
param.setWindowSize(1000); // 每次处理1000条
param.setMaxRounds(50); // 最多处理50轮
param.setMaxRetryTimes(3); // 失败重试3次
param.setRetryBackoffMs(1000L); // 重试间隔1秒
// 并行处理配置
BaseBatchParam<Long> param = new BaseBatchParam<>();
param.enableParallel(8, 5000); // 8个线程,每次处理5000条
param.setMaxRetryTimes(5); // 失败重试5次
param.setRetryBackoffMs(500L); // 重试间隔500毫秒
🔧 线程池管理
方式一:使用外部线程池(推荐)
如果项目已有线程池,建议复用,避免重复创建:
java
@Configuration
public class BatchProcessingConfig {
@Bean
public ExecutorService batchExecutor() {
// 创建项目统一的线程池
return new ThreadPoolExecutor(
10, 10, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
@PostConstruct
public void init() {
// 注入到框架中
ExecutorHolder.setExecutor(batchExecutor());
}
}
方式二:框架自动创建(默认)
框架会自动根据CPU核心数计算合理的线程数(CPU核心数 × 2):
java
// 直接使用,框架会自动创建线程池
ExecutorService executor = ExecutorHolder.get();
线程池关闭
java
// 关闭框架创建的线程池(不会关闭外部注入的线程池)
ExecutorHolder.shutdown();
// 检查是否已关闭
boolean isShutdown = ExecutorHolder.isShutdown();
// 检查是否使用了外部线程池
boolean isExternal = ExecutorHolder.isExternalExecutor();
💡 最佳实践
1. 窗口大小选择
- 小窗口(100-1000):适合处理逻辑复杂、内存占用大的场景
- 中等窗口(1000-5000):适合大多数场景,平衡性能和内存
- 大窗口(5000+):适合处理逻辑简单、需要高吞吐的场景
2. 并行处理建议
- 线程数设置:框架会自动限制为 CPU核心数 × 2,建议使用默认值
- 批次大小:并行模式下,单线程批次建议在500-3000之间
- 适用场景:数据量大、处理逻辑独立、无严格顺序要求
- 线程池管理:优先使用外部线程池,便于统一管理
3. 重试策略
- 重试次数:根据业务重要性设置,一般3-5次
- 退避时间:根据外部依赖的恢复时间设置,建议500ms-2s
- 适用场景:网络调用、数据库操作、外部服务调用
4. 游标管理
- 游标类型:支持多种类型(Long、Date、String等),根据业务选择
- 游标更新:框架会自动更新游标,无需手动管理
- 断点续传 :将
lastCursor持久化到数据库,实现断点续传 - 重置游标 :使用
param.resetOffset()方法重置处理进度
5. 异常处理
- 业务异常 :在
batchProcessTasks()中处理业务异常 - 系统异常:框架会自动重试,重试失败后抛出异常
- 日志记录:框架已内置日志,建议在业务代码中也添加日志
6. 自定义参数类
java
public class OrderBatchParam extends BaseBatchParam<Long> {
private String orderStatus; // 订单状态过滤
private Date startDate; // 开始日期
private Date endDate; // 结束日期
// getter/setter...
}
🔧 高级用法
自定义游标解析
默认情况下,框架使用窗口的最后一个Key作为下一个游标。如果业务有特殊需求,可以重写:
java
@Override
protected Long resolveNextCursor(List<Long> windowKeys, BaseBatchParam<Long> param) {
// 自定义游标计算逻辑
// 例如:取最大值
return windowKeys.stream().max(Long::compareTo).orElse(null);
}
策略工厂使用
java
// 注册策略
BatchProcessingFactory.registerStrategy(orderStrategy);
// 获取策略(需要先注册)
OrderProcessStrategy strategy = BatchProcessingFactory.getStrategy(OrderProcessStrategy.class);
// 执行处理
int result = strategy.process(param);
断点续传实现
java
@Service
public class OrderBatchService {
public void processOrdersWithResume() {
// 1. 从数据库读取上次处理的游标
Long lastCursor = orderCursorRepository.getLastCursor();
// 2. 创建参数并设置游标
BaseBatchParam<Long> param = new BaseBatchParam<>();
param.setLastCursor(lastCursor);
param.setWindowSize(1000);
// 3. 执行处理
int total = orderStrategy.process(param);
// 4. 保存当前游标(如果需要)
Long currentCursor = param.getLastCursor();
orderCursorRepository.saveLastCursor(currentCursor);
}
}