程序中的数据批处理

214 阅读3分钟

一、背景

日常增删改查,查询是常见的批量,一般会有分页。那如果需要计算、修改、插入大量数据怎么做呢?考虑用批处理+多线程!

二、批处理的场景

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个坑:

  1. 大量数据修改,会产生大量没用的消息事件;
  1. 应对数据库表更改有问题,会导致事件停止产生,这个是因为有个元数据文件是启动的时候加载的,修改之后会导致数据解析不了。

kettle

这个目前我们这是Bi在用,有一定的学习成本,不过界面操作相对简单。job定时:

  1. 第一种是kettle自带的Start控件,缺点是kettle程序必须始终运行,浪费内存。
  2. 第二种是使用系统的定时功能。使用Kitchen、Pan命令编写bat、sh脚本,然后使用windows任务计划或者linux的crotab实现定时执行执行脚本。

参考:

datax

这个还没用过~

大数据 job: hadoop MR 模型

大数据的任务分割,多个map,多个reduce。优点是可以处理大量数据,并做了可靠性保证。 缺点:维护的架构比较复杂