项目中,经常碰见需要在项目初始化时完成从mysql的数据写入到缓存如redis中的操作,例如ssm架构项目中,需要某个类实现一个initializeBean的接口,在这个类里面完成初始化的操作,将mysql的数据写到redis或其他缓存即可

说起来倒不难,但是真正执行的时候要考虑的问题点却很多,比如说,如果你的数据量是百万级甚至千万级的,单纯是项目初始化这个过程可能就要花费很久,可能你说可以利用异步线程来解决,但如果这样做了,假如项目启动完毕了,用户正在访问,这个时候缓存还没有写完,而访问的接口正好又是特别耗时的,那么这个缓存就没什么卵用了;

有没有解决办法呢?

经过仔细的考虑和探索,发现问题的关注点其实是尽可能缩短项目初始化时候将数据写入到redis的这个操作,这样我们就很自然的联想到了线程池;

但问题又来了,大规模数据通过不同的线程去执行,不同的线程好歹需要知道自己线程要处理的数据吧,也就是我这个线程要处理从哪一条到哪一条的数据呢?这就需要我们对表的数据进行切分,这样不同分片的数据交给不同的线程去做,然后提交给线程池,不就可以完美的解决这个问题吗?

有了这个思路,我们来看看具体的代码吧,首先需要一个类,或者说是bean,可以在项目初始化的时候进行业务逻辑处理,这里我用的是注解 @PostConstruct,大家也可以用其他的,

1、定义一个切分数据量的方法,即根据数据总量将不同分区的数量放在不同的线程做处理,这里的参数即表的数据总量,

//根据数据分量为不同执行线程分配不同数量的处理数据

public static List> getThreadNum(int count){

List> resMap = new ArrayList<>();

Mapmap = new HashMap<>();

int numOffset = count/threadNum;

//每一份数据的数量是numOffset

for(int i=1;i<=threadNum;i++){

if(i==1){

map.put(i,i + ":" + numOffset * i);

resMap.add(map);

}

else if(i>1 && i<= threadNum){

map.put(i,(numOffset * (i-1) + 1) + ":" + numOffset * i);

resMap.add(map);

}else{

map.put(i,(numOffset * (i-1) + 1) + ":" + count);

resMap.add(map);

}

}

return resMap;

}

2、具体执行业务逻辑的线程,我们将上面切分到的数据存入map,这样每个线程可以根据取到的i,知道具体处理数据的起始位置,

//执行数据写入缓存的线程

class doCacheJob implements Runnable{

private Mapmap ;

private int j;

public doCacheJob(Mapmap,int j) {

this.map = map;

this.j = j;

}

@Override

public void run() {

//执行数据的写入操作

String dataOffects = map.get(j);

String[] splits = dataOffects.split(":");

int start = Integer.valueOf(splits[0]);

int end = Integer.valueOf(splits[1]);

//执行数据分片写入缓存

FieldAnalysisServiceImpl fieldAnalysisService = SpringContextUtil.getBean("fieldAnalysisService");

RedisUtil redisUtil = SpringContextUtil.getBean("redisUtil");

//执行分区数量的数据写入

ListcurrThreadCount = fieldAnalysisService.getCurrentThreadData(start,end);

for(MdRelation single : currThreadCount){

redisUtil.set("blood:" + single.getTargetIden(),single.getTargetIden());

}

}

}

3、线程池提交任务,这里设置了一个状态位,将其设置到redis中,这样就可以在程序启动完毕后,其他接口调用的地方获取到当前缓存写入的操作是否完成,

private static Logger logger = LoggerFactory.getLogger(DoRelationCache.class);

private static final int threadNum = 10;

private static ExecutorService executor = Executors.newFixedThreadPool(threadNum);

//@PostConstruct

public void doCache(){

ExecutorService executor = Executors.newFixedThreadPool(10);

FieldAnalysisServiceImpl fieldAnalysisService = SpringContextUtil.getBean("fieldAnalysisService");

RedisUtil redisUtil = SpringContextUtil.getBean("redisUtil");

String isFinished = "false"; //用于设置表示位,方便程序启动完毕其他程序进行判断

redisUtil.set("isFinished",isFinished);

int countNum = fieldAnalysisService.getTotalCount(); //获取某表的数据总量

List> lists = getThreadNum(countNum);

int j = 0;

try{

logger.info("缓存写入数据开始 .......");

for(int i=0;i<10;i++){

j ++;

Mapsingle = lists.get(i);

executor.execute(new doCacheJob(single,j));

}

logger.info("缓存写入数据结束 .......");

redisUtil.delete("isFinished");

redisUtil.set("isFinished","true");

}catch (Exception e){

e.printStackTrace();

logger.info("线程执行缓存写入数据出错,请及时处理 .......");

}

executor.shutdown();

}

工具类SpringContextUtil顺便附上,可以在程序的任何位置获取需要的bean,就不用注入了,

/**

* 全局使用bean工具类

*/

@Component("springContextUtil")

public class SpringContextUtil implements ApplicationContextAware {

private static ApplicationContext applicationContext; // Spring应用上下文环境

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

SpringContextUtil.applicationContext = applicationContext;

}

public static ApplicationContext getApplicationContext() {

return applicationContext;

}

@SuppressWarnings("unchecked")

public static T getBean(String name) throws BeansException {

return (T) applicationContext.getBean(name);

}

@SuppressWarnings("unchecked")

public static T getBean(Class> clz) throws BeansException {

return (T) applicationContext.getBean(clz);

}

}

到这里,基本的逻辑全部完成,大家有兴趣可以简单的复制到自己的程序中测试一下,由于涉及到的项目具体的业务这里不做演示了,本人经过测试了,可放心使用!

更多推荐