如何将几千万级数据刷新到redis的系统优化到秒级

项目背景:由于数据库数据量的日益增加,查询效率越来越慢,为增加数据查询效率,准备将数据转移至NOSQL,NOSQL根据公司实际情况选用了redis;

我是接手了这个项目,项目刷一次全量数据到redis用时1天半,而且系统还及其不稳定,各种bug,代码结构,业务逻辑比较混乱,代码质量不高。

经过对于业务的理解,鉴于项目问题太多,决定重构。

以下是原项目中存在的明显问题:

1.框架层:dao层框架选用的是jpa 

2.单线程进行redis刷新

3.业务层: 根据客户信息循环查询数据库进行数据封装,然后组装推送redis

4.业务层:全量查询数据,再判断是否需要刷新redis,在去刷新

5.单条命令推送到redis

6.定时任务用的是@Scheduled(fixedDelay={}),可以保证单线程执行,但是多个定时任务会相互等待,效率低下

 

为了解决以上对于效率的影响的问题:

1.框架层:经过测试使用jpa一次查询200W的数据需要的时间是12分钟,而mybatis使用的时候是19s,完全不是一个数量级。原因在于hibernates需要将查询结果转换成对象然后维护到hibernate的session缓存中,

这个非常耗时的一个过程。当大数据量查询的时候,mybatis明显是更优的选择。

       1> 选用的tk_mybatis,为了加速开发效率,加入了generator进行逆向工程

  2>mybatis的fetchSize默认值是100,当查询的数据达到百万级的时候,defaultFetchSize增大这个数字可以减少客户端与oracle的往返,减少响应时间; 本人设置的是10000

2. 业务逻辑:

     对于百万级的数据,我们需要加快内存计算的速度,必然要引入多线程。因为每次需要处理批次的数量不相同,需要动态的去做任务分配,所以我们选用的是 forkjoin,分而治之的概念。

 //forkjoin 示例代码

package com.msl.cedis.service.impl;

import com.common.base.utils.SpringContext;
import com.msl.cedis.eo.TclientItemsUw;
import com.msl.cedis.eo.TclientPolicyItemsUw;
import com.msl.cedis.service.ClientService;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Configurable;

import java.util.List;
import java.util.Map;
import java.util.concurrent.RecursiveTask;

/**
 * @Author tony_t_peng
 * @Date  2020-09-22 15:31
 */
@Configurable
public class SyncClientAndPolicyTask extends RecursiveTask<Integer> { //RecursiveTask RecursiveAction

    private final static Logger log = LoggerFactory.getLogger(ClientServiceImpl.class);


    private List<String> cliUwUids;
    private int odsProcessId;
    private Map<String, List<TclientItemsUw>> clientsItemsMap;
    private Map<String, List<TclientPolicyItemsUw>> clientsPolicyItemsMap ;
    private Map<String,Map<String,List<String>>> newPolicyInfoMap ;


    public SyncClientAndPolicyTask() {
    }

    public SyncClientAndPolicyTask(List<String> cliUwUids, Integer odsProcessId, Map<String, List<TclientItemsUw>> clientsItemsMap, Map<String, List<TclientPolicyItemsUw>> clientsPolicyItemsMap,Map<String,Map<String,List<String>>> newPolicyInfoMap) {
        this.cliUwUids = cliUwUids;
        this.odsProcessId= odsProcessId;
        this.clientsItemsMap=clientsItemsMap;
        this.clientsPolicyItemsMap=clientsPolicyItemsMap;
        this.newPolicyInfoMap=newPolicyInfoMap;
    }

    private ClientService clientService = SpringContext.getBean(ClientService.class);

    @Override
    protected Integer compute() {
        int count = 0;
        if (CollectionUtils.isEmpty(cliUwUids)) {
            return count;
        }
        if (cliUwUids.size() <= 2000) {
            clientService.refrushClientInfo2Redis(cliUwUids,clientsItemsMap,clientsPolicyItemsMap, newPolicyInfoMap);
            return cliUwUids.size();
        } else {
            List<String> left = cliUwUids.subList(0, cliUwUids.size() / 2);
            List<String> right = cliUwUids.subList(cliUwUids.size() / 2, cliUwUids.size());
            SyncClientAndPolicyTask leftJob = new SyncClientAndPolicyTask(left,odsProcessId,clientsItemsMap,clientsPolicyItemsMap, newPolicyInfoMap);
            SyncClientAndPolicyTask rightJob = new SyncClientAndPolicyTask(right,odsProcessId,clientsItemsMap,clientsPolicyItemsMap, newPolicyInfoMap);
            leftJob.fork();
            rightJob.fork();
            return leftJob.join() + rightJob.join();
        }
    }
}

  

  //调用forkjoin,其中count 就是处理的数据量

Integer count = forkJoinPool.invoke(new SyncUweCliTask(btchNo,cliUids,cliDtlMap,casCliClmMap,casCliCvgMap,casCliPolMap,glhCliClmMap,glhCliCvgMap,glhCliPolMap));

3和4:1.用批次号控制一次循环处理的数据量,我这边一次循环的处理量大概在200W数据以内。

  2.对于一次循环需要刷新的数据用sql条件先进行过滤。减少加载到内存的数据量。

  3.一次全量查询出一个批次内可能用到的各种数据并转换成map,然后直接交给内存运算

    目的:1.减少数据加载到内存的数据量,做到查询的结果就是需要刷新的数据 2. 批量查出一个批次所有需要的数据,业务逻辑全内存处理无sql等待。

 

5. 数据刷新到redis,使用管道批量刷新,减少连接获取,资源关闭的开销。 同时因为redis服务是单线程的,需要控制管道的命令量不要过分多,因为管道命令过多执行可能会导致redis线程阻塞,导致其他线程操作redis超时。所以需要控制管道的命令量,并且适当扩大redis的超时时间.    可以改为60s或者100秒应该足够了

 

6.定时任务改为quaze,同时对于同一个任务做到单线程启动。加上注解@DisallowConcurrentExecution

 

实现上面所有的功能点,项目有之前刷新redis的1天半已经可以跑到24分钟以内,一个批次200W以内的数据,都是秒级刷新redis,基本实现了项目的效率期往

 

posted @ 2020-10-19 12:28  火羽  阅读(1843)  评论(0编辑  收藏  举报