项目修炼之路(5)高并发下优化Redis缓存效率

        最近,公司给了个优化任务,某个耗时的操作,在百亿的交易额下,处理异常缓慢,需要优化,以为每日发息做准备,在这里给大家介绍下我的优化思路,共同探讨下:


代码逻辑:

        通过用户id获取用户所在区域id,每次批量处理1千个用户,起20个线程处理。


第一步,加缓存

通过用户id获取用户所在区域id分两步实现(代码中已经标红),第一步通过用户获取城市id,第二部通过城市id获取区域id,使用上篇博客介绍的方法(项目修炼之路(4)aop+注解的自动缓存),给两个方法加入redis缓存。

@Override
	public PublicResult<HashMap<Integer, Integer>> getUserAreaFranchiseeIDS(List<Integer> uids) {
		PublicResult<HashMap<Integer, Integer>> result = new PublicResult<HashMap<Integer, Integer>>();
		HashMap<Integer, Integer> resultMap = new HashMap<Integer, Integer>();

		long time;
		for(Integer uid :uids){
			Integer areaId = Integer.valueOf(0);
			try {
				time=System.currentTimeMillis();
				UserAreaFranchisee area =getUserAreaFranchisee(uid).getResult();
				LOGGER.info("=getUserAreaFranchiseeIDS=>--.uid:["+uid+"].[get -- wmpsDayInterChange]getUserAreaFranchisee() -------------spen time:" + (System.currentTimeMillis()-time));
				time=System.currentTimeMillis();
				int id = 0;
				if (area != null && area.getCityid() != null && area.getCityid().intValue() > 0) {
					id = area.getCityid().intValue();
					 tpr = logicTongchengAreaService.getTongchengArea(Integer.valueOf(id));
					if (tpr != null && tpr.isSuccess() && tpr.getResult() != null && tpr.getResult().getId() != null && tpr.getResult().getId() > 0) {
						areaId = tpr.getResult().getId();
					}
				}
				LOGGER.info("=getUserAreaFranchiseeIDS=>--..uid:["+uid+"].[get -- wmpsDayInterChange]getLogicTongchengAreaService() -------------spen time:" + (System.currentTimeMillis()-time));

			}catch (Exception e){
				LOGGER.error("=getUserAreaFranchiseeIDS=>",e);
			}
			resultMap.put(uid,areaId);
		}


		result.setSuccess(true);
		result.setResult(resultMap);
		return result;
	}



第二步,合并结果

问题:加入缓存后,发现,当访问频繁时,两次访问加入的缓存不合理:1,value为对象,给每次取值增加反序列化过程,实际只需id即可;2,两次操作,最终只需一个结果,造成资源浪费。

优化后:二次缓存变为一次缓存,key与value均为简单string与Integer

@Override
	public PublicResult<String> getUserAreaFranchiseeIDS(ArrayList<Integer> uids) {
		PublicResult<String> result = new PublicResult<String>();
		HashMap<Integer, Integer> resultMap = new HashMap<Integer, Integer>();
		long time;
		for(Integer uid :uids){
			Integer areaId = Integer.valueOf(0);
			try {
				time=System.currentTimeMillis();
				areaId = userAreaFranchiseeService.getUserAreaIdByUid(uid);
				LOGGER.info("=getUserAreaFranchiseeIDS=>--.uid:[" + uid + "].[get -- wmpsDayInterChange]getUserAreaIdByUid() -------------spen time:" + (System.currentTimeMillis() - time));
			}catch (Exception e){
				LOGGER.error("=getUserAreaFranchiseeIDS=>",e);
			}
			resultMap.put(uid,areaId);
		}
		result.setSuccess(true);
		result.setResult(JSON.toJSONString(resultMap));
		return result;
	}


第三步:批量读取

问题:redis为单线程,批量数据访问时,单个从redis拿数据的时间被延长,造成时间上的浪费,而且,浪费在网络上的时间比读数据时间要长

优化后:批量从redis获取一次获取,多次io改为一次io,拿不到的数据,才从数据库中读取,同时缓存到redis。

@Override
	public PublicResult<String> getUserAreaFranchiseeIDS(ArrayList<Integer> uids) {
		PublicResult<String> result = new PublicResult<String>();
		HashMap<Integer, Integer> resultMap = new HashMap<Integer, Integer>();
		long time;
		ArrayList<String> uidKeys = new ArrayList<String>();
		for(int i=0;i<uids.size();i++){
			uidKeys.add(i,RedisKeyUtils.USER_AREA_ID+ uids.get(i));
		}
		List<Integer> listAreas = RedisUtils.mget(uidKeys.toArray(),Integer.class);
		for(int i=0 ;i<uids.size();i++){
			Integer uid = uids.get(i);
			Integer areaId = Integer.valueOf(0);
			if(listAreas.get(i)==null){
				try {
					time=System.currentTimeMillis();
					areaId = userAreaFranchiseeService.getUserAreaIdByUid(uid);
					LOGGER.info("=getUserAreaFranchiseeIDS=>--.uid:[" + uid + "].[get -- wmpsDayInterChange]getUserAreaIdByUid() -------------spen time:" + (System.currentTimeMillis() - time));

				}catch (Exception e){
					LOGGER.error("=getUserAreaFranchiseeIDS=>error uid:["+uid+"]",e);
				}
				listAreas.set(i,areaId);
			}
			areaId = listAreas.get(i);
			resultMap.put(uid,areaId);
		}
		result.setSuccess(true);
		result.setResult(JSON.toJSONString(resultMap));
		return result;
	}



第四步:批量添加

问题:设置缓存周期后,每隔一段时间,读取数据几乎全从数据库读取,加上增加到redis的时间,会造成周期性读取缓慢。

优化后:时间限制拉长,判断是否能从redis获取一半的数据,如果不能,批量将数据缓存到redis(一次io),再走逻辑

@Override
	public PublicResult<String> getUserAreaFranchiseeIDS(ArrayList<Integer> uids) {
		PublicResult<String> result = new PublicResult<String>();
		HashMap<Integer, Integer> resultMap = new HashMap<Integer, Integer>();
		long time;
		ArrayList<String> uidKeys = new ArrayList<String>();
		for(int i=0;i<uids.size();i++){
			uidKeys.add(i,RedisKeyUtils.USER_AREA_ID+ uids.get(i));
		}
		List<Integer> listAreas = RedisUtils.mget(uidKeys.toArray(),Integer.class);

		try {
			if (ListUtil.countNullNumber(listAreas) > listAreas.size() / 2) {
				initRedisByUids(uids);
				listAreas = RedisUtils.mget(uidKeys.toArray(), Integer.class);
			}
		}catch (Exception e){
			LOGGER.error("=getUserAreaFranchiseeIDS=>initRedisByUids error",e);
		}

		for(int i=0 ;i<uids.size();i++){
			Integer uid = uids.get(i);
			Integer areaId = Integer.valueOf(0);
			if(listAreas.get(i)==null){
				try {
					time=System.currentTimeMillis();
					areaId = userAreaFranchiseeService.getUserAreaIdByUid(uid);
					LOGGER.info("=getUserAreaFranchiseeIDS=>--.uid:[" + uid + "].[get -- wmpsDayInterChange]getUserAreaIdByUid() -------------spen time:" + (System.currentTimeMillis() - time));

				}catch (Exception e){
					LOGGER.error("=getUserAreaFranchiseeIDS=>error uid:["+uid+"]",e);
				}
				listAreas.set(i,areaId);
			}
			areaId = listAreas.get(i);
			resultMap.put(uid,areaId);
		}
		result.setSuccess(true);
		result.setResult(JSON.toJSONString(resultMap));
		return result;
	}

	private boolean initRedisByUids(ArrayList<Integer> uids){
		boolean isSuccess = false;
		HashMap<String, Integer> resultMap =null;
		try {
			resultMap = ListUtil.getMaxAndMinInterger(uids);
			if(resultMap!=null && !resultMap.isEmpty()){

				List<UserAreaUidVo> listResult = userAreaFranchiseeService.getUserAreaIdPageByUid(resultMap.get(ListUtil.minNumKey), resultMap.get(ListUtil.maxNumKey));
				if(listResult!=null && !listResult.isEmpty()){
					HashMap<String ,List> hashMapForUid =uidToRedisKeyAndVlues(listResult);
					RedisUtils.mset(hashMapForUid.get(RedisKeys).toArray(),hashMapForUid.get(RedisValues).toArray(),RedisKeyUtils.USER_AREA_ID_TIME);
					isSuccess=true;
				}
			}
		}catch(Exception e){
			LOGGER.error("=initRedisByUids=>",e);
		}

		return isSuccess;
	}

	private HashMap<String ,List> uidToRedisKeyAndVlues(List<UserAreaUidVo> listUserArea){
		HashMap<String ,List> hashMapForUid = new HashMap<String ,List>();
		List<String> keys = new ArrayList<String>(listUserArea.size());
		List<Integer> values = new ArrayList<Integer>(listUserArea.size());
		for(int i=0;i<listUserArea.size();i++){
			keys.add( RedisKeyUtils.USER_AREA_ID + listUserArea.get(i).getUid());
			values.add(listUserArea.get(i).getAreaid() == null ? 0 : listUserArea.get(i).getAreaid());
		}
		hashMapForUid.put(RedisKeys,keys);
		hashMapForUid.put(RedisValues,values);
		return hashMapForUid;
	}

总结:

        在工作中,我们会遇到各种难题,实际这些难题,帮助我们提升了自己的解决问题能力外,还帮助我们制造了一种奇妙的东西,叫思路,或者叫框架,就是再有类似问题时,我们会映射过来,我是不是解决过,不仅仅局限在代码端,在生活和处理社会问题时,实际是相通的!

        所以,代码积累的不仅仅是工作经验,还有生活经验!


附录:工具类:

public class ListUtil {

    public static String maxNumKey ="max";
    public static String minNumKey ="min";
    /**
     * 按照某大小对list分页
     * @param targe
     * @param size
     * @return
     */
    public static List<List>  splitList(List targe,int size) {
        List<List> listArr = new ArrayList<List>();
        //获取被拆分的数组个数
        int arrSize = targe.size()%size==0?targe.size()/size:targe.size()/size+1;
        for(int i=0;i<arrSize;i++) {
            List  sub = new ArrayList();
            //把指定索引数据放入到list中
            for(int j=i*size;j<=size*(i+1)-1;j++) {
                if(j<=targe.size()-1) {
                    sub.add(targe.get(j));
                }
            }
            listArr.add(sub);
        }
        return listArr;
    }

    /**
     * 统计list中为null的元素个数
     * @param listTest
     * @return
     */
   public static long countNullNumber(List listTest){
       long  count=0;
       for(int i=0;i<listTest.size();i++){
           if(listTest.get(i)==null){
               count++;
           }
       }
       return count;
   }

    /**
     * 统计list中为null的元素个数
     * @param listTest
     * @return
     */
    public static HashMap getMaxAndMinInterger(List<Integer> listTest)throws Exception{
        if(listTest==null || listTest.isEmpty()){
            throw new Exception("=ListUtil.getMaxAndMinInterger=> listTest is null");
        }
        HashMap<String,Integer> result = new HashMap<String,Integer>();
        Integer  maxNum=null;
        Integer minNum=null;
        for(int i=0;i<listTest.size();i++){
            if(!(listTest.get(i)==null)){
                if(maxNum==null){
                    maxNum=listTest.get(i);
                }

                if(maxNum<listTest.get(i)){
                    maxNum=listTest.get(i);
                }

                if(minNum==null){
                    minNum=listTest.get(i);
                }
                if(minNum>listTest.get(i)){
                    minNum=listTest.get(i);
                }
            }
        }
        if(maxNum==null || minNum == null){
            throw new Exception("=ListUtil.getMaxAndMinInterger=> listTest is null");
        }
        result.put(maxNumKey,maxNum);
        result.put(minNumKey,minNum);
        return result;
    }


}



评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值