问题背景

在使用多线程进行对redis的value做累加时导致出错,使用伪代码进行原因分析,不能保证操作redis的原子性

for(int i=0;i<10;i++){
	//提交10个线程,每个params装有10条数据
	asyncServiceExecutor.submit(() -> submitSingle(globalId, params));
}


public Map<String,String> submitSingle(String globalId, JSONArray params) {
    try {
        log.info("params: {}, size: {}", params, params.size());
        Map<String,String> resultVOlist = QueryController.doQuery(params);
        //由于是多核,可以做到真正意义上同时间的并发,因此可能会出现有5个线程同时到达了这条语句
        //第一次同时达到,redis里面就是没有这个globalId关键字,因此5个线程都是为null
        Object commitObject = redisUtils.get(globalId);
        //当有5个为null的线程进来之后,就会导致多次创建了同一个键,累加数据相当于丢失了4次,也就是40条
        if (commitObject == null) {
            boolean flag = redisUtils.set(globalId, params.size());
        } else {
            redisUtils.increment(globalId, (long) params.size());
        }
        return resultVOlist;
    } catch (Exception e) {
        log.error("QueryController not found error", e);
        return new HashMap<>();
    } 
}

解决方案

1 既然是多线程产生的原因,那么我们可以把写redis的写操作移出到主线程,redis的底层写操作是单线程的

for(int i=0;i<10;i++){
		//提交10个线程,每个params装有10条数据
	Future<Map<String,String>> futureResult = asyncServiceExecutor.submit(() -> submitSingle(globalId, params));
	Map<String,String> resultVOList = futureResult.get();
	if(resultVOList != null){
		Object commitObject = redisUtils.get(globalId);
		if (commitObject == null) {
		    boolean flag = redisUtils.set(globalId, params.size());
		} else {
		    redisUtils.increment(globalId, (long) params.size());
		}
	}
}

public Map<String,String> submitSingle(String globalId, JSONArray params) {
    try {
        log.info("params: {}, size: {}", params, params.size());
        Map<String,String> resultVOlist = QueryController.doQuery(params);
        return resultVOlist;
    } catch (Exception e) {
        log.error("QueryController not found error", e);
        return new HashMap<>();
    } 
}

2 可以在线程里面把写redis的逻辑进行整体加锁

public Map<String,String> submitSingle(String globalId, JSONArray params) {
    try {
        log.info("params: {}, size: {}", params, params.size());
        Map<String,String> resultVOlist = QueryController.doQuery(params);
		if(redission.lock(uuid)){
	        Object commitObject = redisUtils.get(globalId);
	        if (commitObject == null) {
	            boolean flag = redisUtils.set(globalId, params.size());
	        } else {
	            redisUtils.increment(globalId, (long) params.size());
	        }
        }
        return resultVOlist;
    } catch (Exception e) {
        log.error("QueryController not found error", e);
        return new HashMap<>();
    } 
}

总结

  • 问题只会越来越多,但也要耐心解决




作为程序员第 115 篇文章,每次写一句歌词记录一下,看看人生有几首歌的时间,wahahaha …

Lyric: 如果难过请你忘了我

这是第4首歌,已经完结了,你们猜出歌名了吗?

  • 歌名:借口
  • 歌手:周杰伦
  • 歌词:周杰伦
  • 专辑:七里香
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐