mysql数据多线程写入redis_多线程实现项目初始化完成大规模数据写入redis缓存
项目中,经常碰见需要在项目初始化时完成从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);
}
}
到这里,基本的逻辑全部完成,大家有兴趣可以简单的复制到自己的程序中测试一下,由于涉及到的项目具体的业务这里不做演示了,本人经过测试了,可放心使用!
更多推荐

所有评论(0)