大批量数据读写

时间:2021-01-26 14:53:40

需求

大约200W条数据,批量从mysql中读取,然后根据主键再从hbase读数据进行关联,最后再update到数据库中

同步解决方案

同步解决方案,也是最接近人脑思考顺序的方案是,分页mysql读取id集合,每页1k条数据,然后拿着idList批量从nosql的hbase中进行数据的获取,进行数据的封装,然后逐条更新到数据库中。实验结果表明,如果要完成这项工作,估计要10小时以上。

先做个简单的优化,尽可能的降低io开销,将逐条更新回数据库修改成,延迟批量提交数据库。这样1k次io开销缩减成1次。利用ibatis的批量提交特性,具体代码如下

@Override
public void batchUpdate(final List<T> objectList) {
    this.getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
        public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
            executor.startBatch();
            for (T tmp : objectList) {
                executor.update(sqlmapNamespace + ".update" + , tmp);
            }
            return executor.executeBatch();
        }
    });
}

这样再次实验后,发现跟新1k条数据从原来的10s以上降低到300ms左右,还是有着非常大的提升的。整体的1k的分页任务完成,从原来的40s左右降低到1.5s左右。那么完成200w左右的数据,仍然需要接近1个小时,不能满足业务期望

异步解决方案

IO的开销,已经基本上没办法在低成本的角度去优化了。那么可以从cpu的角度进行提高,运行top命令后发现,cpu的java占比基本在%0.3以内。因此可以尝试采用多线程的异步方案进行并发处理。起线程的代码如下,起大约10个线程左右,起的太多,会造成数据库连接数超出,导致数据库连接异常

query.setCurrentPage(1);
query.setPageSize(100000);// 开10个线程左右,能覆盖200W的数据
Integer totalInteger = rUserAlipayDAO.countByQuery(query);
query.setTotalItem(totalInteger);
do {
    try {
        GetDataThread thread = new GetDataThread(query.getStartRow(), query.getEndRow());
        Thread t = new Thread(thread);
        t.start();
    } catch (Throwable t) {
        logger.error("update  error", t);
    }
} while (query.nextPage());

GetDataThread 代码如下,接受10W条左右的数据,进行任务的操作,构造函数如下,主要用于区分每个线程处理的起始位置

public GetDataThread (Integer startRow, Integer endRow){
    this.startRow=startRow;
    this.endRow=endRow;
}

下面是处理的run方法

@Override
public void run() {
    ...
    query.setStartRow(startRow);
    query.setPageSize(1000);//每页1K条数据
    logger.warn("do startRow"+startRow +" thread start...");
    Long startLong = System.currentTimeMillis();
    do{
        //TODO 进行相关的任务处理
        startRow= startRow+1000;
        query.setStartRow(startRow);
    }while(startRow<endRow);
    
    logger.warn("Thread startRow"+startRow +"  cost "+(System.currentTimeMillis()-startLong));
}

调整后,每个线程处理10W条数据,大概2分钟左右完成,但是由于是10个线程同时开工,总体任务的执行时间基本控制在3min以内,完全满足业务期望

总结

这个场景非常简单,写这篇文章的目的主要是找到一个案例,让初学者了解如何去分析一段代码存在的性能问题,并且如何针对这些问题进行代码改进。