全量数据同步:多线程性能优化

业务背景

公司在做客服迁移迁移系统,下来的需求就是将公司的用户信息全量同步到那边的数据库,总数据量大概300w+,将无效数据过滤掉以及不需要进行同步的用户(手机号码不存在),剩下大概200w。

开始采用的方案就是单线程分页查询,每批100条的数据量,定时任务写好放到测试环境里面跑了一下,预估大概每天能跑50w的数据量(两天反正很难完成任务)。

为什么这么慢呢,有个原因是因为那边没有提供批量插入的接口,只能单条插入,并且每秒有阈值,大概是十条左右。

另外就是数据量比较大,会导致分页查询越来越慢(其实我觉得也还好),因为每次查询都需要扫描整个结果集并跳过前面的记录以获取请求的页数。

优化思路

改动多线程将大任务分成子任务,并且子任务之间没有关联。

另外就是核心线程数的设置,我在我自己机器上测,核心数是4,我设置线程数10速度是比较快的,

和单线程相比较,大概能提升2.5倍,查询大概10分钟(仅仅是查询)。

代码实现

java 复制代码
@Slf4j
@RestController
@RequestMapping("/demo")
public class SynchronizeHistoricalDataController implements DisposableBean {

    private ExecutorService executor = Executors.newFixedThreadPool(10);  //newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

    @Autowired
    private ITest1Service test1Service;
    /**
     * 多线程同步通话记录历史数据
     *
     * @param params
     * @return
     * @throws Exception
     */
    @GetMapping("/syncHistoryData")
    public Response syncHistoryData(Map<String, Object> params) throws Exception {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    logicHandler(params);
                } catch (Exception e) {
                    log.warn("多线程同步用户信息历史数据才处理异常,errMsg={}", e.getMessage());
//                    DingDingMsgSendUtils.sendDingDingGroupMsg("【系统消息】" + profile + "环境,多线程同步稽查通话记录历史数据才处理异常,errMsg = " + e);
                }
            }
        });
        return Response.success("请求成功");
    }

    /**
     * 处理数据逻辑
     *
     * @param params
     * @throws Exception
     */
    private void logicHandler(Map<String, Object> params) throws Exception {
        /******返回结果:多线程处理完的最终数据******/
        List<Test1> result = new ArrayList<>();

        /******查询数据库总的数据条数******/
        int count = this.test1Service.count(new QueryWrapper<Test1>());

        /******限制每次查询的条数******/
        int num = 1000;

        /******计算需要查询的次数******/
        int times = count / num;
        if (count % num != 0) {
            times = times + 1;
        }
        
        /******每个线程开始查询的行数******/
        int offset = 0;

        /******添加任务******/
        List<Callable<List<Test1>>> tasks = new ArrayList<>();
        for (int i = 0; i < times; i++) {
            Callable<List<Test1>> qfe = new ThredQuery(test1Service, params, offset, num);
            tasks.add(qfe);
            offset = offset + num;
        }

        /******为避免太多任务的最终数据全部存在list导致内存溢出,故将任务再次拆分单独处理******/
        //将x页的数据分成10份,每一份是x/10页的数据量
        //也就是将x/10份任务交给10个线程来处理,每个
        List<List<Callable<List<Test1>>>> smallList = ListUtils.partition(tasks, 10);
        //遍历任务
        for (List<Callable<List<Test1>>> callableList : smallList) {
            if (CollectionUtils.isNotEmpty(callableList)) {
                try {
                    List<Future<List<Test1>>> futures = executor.invokeAll(callableList);
                    /******处理线程返回结果******/
                    if (!futures.isEmpty()) {
                        for (Future<List<Test1>> future : futures) {
                            List<Test1> test1List = future.get();
                            //将数据多线程发送到MongoDB
                            if (CollectionUtils.isNotEmpty(test1List)) {
                                executor.execute(new Runnable() {
                                    @Override
                                    public void run() {
                                        /******异步存储******/
                                        log.info("异步存储MongoDB开始:线程{}拆分处理开始...", Thread.currentThread().getName());
//                                        saveMongoDB(duyanCallRecordDetailList);
                                        log.info("异步存储MongoDB结束:线程{}拆分处理开始...", Thread.currentThread().getName());
                                    }
                                });
                            }
                            result.addAll(future.get());
                        }
                    }
                } catch (Exception e) {
                    log.warn("任务拆分执行异常,errMsg = {}", e);
//                    DingDingMsgSendUtils.sendDingDingGroupMsg("【系统消息】" + profile + "环境,任务拆分执行异常,errMsg = " + e);
                }
            }
        }
        System.out.println(System.currentTimeMillis());

    }
    @Override
    public void destroy() throws Exception {
        executor.shutdown();
    }

}

class ThredQuery implements Callable<List<Test1>> {
    /******需要通过构造方法把对应的业务service传进来 实际用的时候把类型变为对应的类型******/
    private ITest1Service myService;
    /******查询条件 根据条件来定义该类的属性******/
    private Map<String, Object> params;

    /******分页index******/
    private int offset;
    /******数量******/
    private int num;

    public ThredQuery(ITest1Service myService, Map<String, Object> params, int offset, int num) {
        this.myService = myService;
        this.params = params;
        this.offset = offset;
        this.num = num;
    }
    //分页查询
    @Override
    public List<Test1> call() throws Exception {
        /******通过service查询得到对应结果******/
        List<Test1> test1s = myService.list(new QueryWrapper<Test1>()
                .last("limit " + offset + ", " + num));
        return test1s;
    }

}

内存风险

为避免太多任务的最终数据全部存在list导致内存溢出,故将任务再次拆分单独处理。

相关推荐
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
励志成为嵌入式工程师2 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
记录成长java5 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
前端青山5 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
睡觉谁叫~~~5 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
音徽编程5 小时前
Rust异步运行时框架tokio保姆级教程
开发语言·网络·rust
观音山保我别报错5 小时前
C语言扫雷小游戏
c语言·开发语言·算法