一、背景
日常增删改查,查询是常见的批量,一般会有分页。那如果需要计算、修改、插入大量数据怎么做呢?考虑用批处理+多线程!
二、批处理的场景
1.初始化数据+增量
服务增加了一个字段,数据来源于旧数据。需要将旧数据初始化,再配合一个增量同步,比如监听mq或者binlog。初始化有时候比较简单,一个sql就可以update,但是微服务有大量数据是跨库跨应用的,此时可能需要接口同步,为了提交效率和避免OOM,要考虑把任务分批处理。
2.冗余数据,优化接口
本来计算的逻辑是单独使用的,某个新的需求要列表或者做聚合计算。为了提高查询性能,在写端将数据提前准备好,需要定时批处理。
三、应对
考虑到任务可能耗时比较久,所以对任务做分割,比如1000条一个批次,记录批次下标,每个批次加上事务,失败回滚+重试。
分批之后,做到批次之间无依赖,可以使用多线程。
1.多任务
java提供了fork-join处理多任务。日常用的parallelStream底层也是fork-join
2.线程池
结合future/CompletableFuture和线程池也可以处理多任务
四、实现
demo:批量数据计算落库
graph LR
A[准备数据]-->|分割|B[计算]-->C[存储]
@Slf4j
public abstract class DataBatch<T, K> {
public int getBatchSize() {
return 1000;
}
public void syncData(Executor executor,List<T> intData) {
int batchSize = getBatchSize(); // 每批次执行的任务数量
Assert.isTrue(batchSize > 0, "batchSize 要大于0");
Assert.notNull(executor);
// 分批
List<CompletableFuture<Void>> futures = new LinkedList<>();
for (int i = 0; i < intData.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, intData.size());
final List<T> batchEntries = intData.subList(i, endIndex);
final int id = i;
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// 计算
List<K> results = handle(batchEntries);
// 存储
save(results);
}, executor).exceptionally((e) -> {
log.info("batch id:{}", id);
e.printStackTrace();
return null;
});
futures.add(future);
}
// 并发
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
// 存储
public abstract void save(List<K> results);
// 计算
public abstract List<K> handle(List<T> intData);
}
spring batch
上面的demo是手动实现数据分割、计算、存储。有没有开源方案呢,调研发现有个spring batch。 任务做了可靠性处理,但是单节点的。设计思路差不多,它分成:
graph LR
X[准备数据]-->|分割|A[数据读取]-->B[数据处理:计算]-->C[数据写入:存储]
@Bean
public Job importUserJob() {
return jobBuilderFactory.get("importUserJob")
.incrementer(new RunIdIncrementer())
.flow(step1())
.end()
.build();
}
// 取数--计算--更新
private Step step1() {
return stepBuilderFactory.get("step1")
.<User, User>chunk(getBatchSize())
.reader(reader())
.processor(processor())
.writer(writer())
.build();
}
参考:
数据同步框架
canal
这个是监听binlog的,适合增量数据解析、实时监听,正常情况会有的时差是几秒钟,碰到主从库有延迟、MQ消费慢,时长会增加。之前使用过,有2个坑:
- 大量数据修改,会产生大量没用的消息事件;
- 应对数据库表更改有问题,会导致事件停止产生,这个是因为有个元数据文件是启动的时候加载的,修改之后会导致数据解析不了。
kettle
这个目前我们这是Bi在用,有一定的学习成本,不过界面操作相对简单。job定时:
- 第一种是kettle自带的Start控件,缺点是kettle程序必须始终运行,浪费内存。
- 第二种是使用系统的定时功能。使用Kitchen、Pan命令编写bat、sh脚本,然后使用windows任务计划或者linux的crotab实现定时执行执行脚本。
参考:
datax
这个还没用过~
大数据 job: hadoop MR 模型
大数据的任务分割,多个map,多个reduce。优点是可以处理大量数据,并做了可靠性保证。 缺点:维护的架构比较复杂