项目中需要查出每个用户的使用总流量,要将每个子账户sum一下 如果用mysql还需要用到分组操作.很影响性能 ,在100W子账户数据的时候 耗时10S以上了


 select u.user_name,sum(psa.use_bill)sumbill from proxy_sub_account psa
    left join sys_user u on u.id = psa.account_id
     where  psa.type = #{type}
    GROUP BY psa.account_id
    ORDER BY sumbill desc

正好项目中流量使用情况也同步到redis里面了 故尝试从redis取值再进行排序
redis的流量存储类型是hash
格式为 dbc1649c-d8d2-4808-af02-5736cbc66ada:1:2022-05-12-->1234
用户id:类型:时间 --> 使用流量
因为会有很多条这样的记录,redis是单线程 如果大量数据的时候使用hget取出会导致redis做不了其他的操作了
大佬推荐我使用HSCAN

上代码
首先百度了一下hscan 浅浅了解了一下这个操作,大概意思就是分阶段性的去取出hash对象存储的值
然后再网上搜了一下jedis对该操作的封装

 public static Map<String, String> getAll(String hashKey, int iterSize,String match) {
        Jedis jedis = null;
        Map<String, String> mapList=new HashMap<String, String>();
        try {
            int cursor = 0;
            ScanParams scanParams = new ScanParams().match(match).count(iterSize > 0 ? iterSize : 1000);//此处match可以模糊查询  "*:type:*"
            ScanResult<Map.Entry<String, String>> scanResult;
            jedis = jedisPool.getResource();
            jedis.select(0);
            do {
                scanResult = jedis.hscan(hashKey, String.valueOf(cursor), scanParams);
                for (Map.Entry<String, String> en : scanResult.getResult()) {
                    mapList.put(en.getKey(),en.getValue());
                    //业务代码
                }
                //获取游标位置,若大于0,则代表还有数据,需要继续迭代
                cursor = Integer.parseInt(scanResult.getStringCursor());
            } while (cursor > 0);
        }catch (Exception e) {
            LogUtils.error("错误日志:" + e.getMessage());
            return null;
        }finally{
            // 返还到连接池
            if (jedis != null) {
                jedis.close();
            }
        }
        return mapList;
    }

使用的时候传redis里hash对象的名称就行, 传":type:" 是用于模糊查询

        Map<String, String> redisMap = JedisUtil.getAll("userFlowCounter", 50000,"*:"+type+":*");

接口代码如下

 public JSONObject getTrafficRanking1(Integer type){
        //redis存的格式是   dbc1649c-d8d2-4808-af02-5736cbc66ada:1:2022-05-12 -> 流量
        HashMap<String, String>  map = new HashMap<>();
        HashMap<String, String>  userNameMap = new HashMap<>();
        long start = System.currentTimeMillis();
        Map<String, String> redisMap = JedisUtil.getAll("userFlowCounter", 50000,"*:"+type+":*");
        long end = System.currentTimeMillis();
        System.err.println("=======================redis查询共耗时" + (end - start) + "毫秒=====================");//输出程序运行时间
        Set<String> keys = redisMap.keySet();   //此行可省略,直接将map.keySet()写在for-each循环的条件中
        //遍历redis的结果, 过滤key 只保留userId 和日流量再存入新的map里
        for(String key:keys){
            //截取:之前的userId
            String userId = StringUtils.substringBefore(key, ":");
            //判断map集合中有没有这个userId为名字的key
            //containsKey返回值为boolean类型
            //日流量
            BigDecimal userBillByDay = new BigDecimal(redisMap.get(key));
            if(map.containsKey(userId)){
                //根据key取出value再加上name相同的另一个参数的value
                BigDecimal mapBill = new BigDecimal(map.get(userId));
                BigDecimal addBill = userBillByDay.add(mapBill);//相加之后的流量
                map.put(userId,addBill.toString());
            }
            else{
                map.put(userId,userBillByDay.toString());
            }
        }
        Set<String> userName = map.keySet();   //获取用户名
        for(String key:userName){//先过滤:type:  指定类型的结果  1为动态2为海外机房3为4G
            SysUser byId = sysUserMapper.getById(key);
            if(ObjectUtil.isNotNull(byId.getUserName())){
                userNameMap.put(byId.getUserName(),map.get(key));
            }else {
                userNameMap.put(key,map.get(key));
            }
        }
        Map<String, String> stringDoubleMap = sortMapByValue(userNameMap);
        return success(stringDoubleMap);
    }

其中思路是 :
先从redis分段模糊查询取出所有数据
再字符串截取,获取到用户id 将id作为key存入到一个新的map 如果有一样的用户名的 则将当前新查出来map.get(key)对应的流量和原本的流量相加之后重新put进map里 因为map的key是唯一的.所以相加后的流量作为value会覆盖之前的流量value
在通过用户id去数据库查到用户名存入到一个新的map 此时map的格式为 用户名,总使用流量
对求和好的map数据再进行排序
这里是取map的value进行排序
用了自定义比较器

public class MapValueComparator implements Comparator<Map.Entry<String, String>> {
    @Override
    public int compare(Map.Entry<String, String> me1, Map.Entry<String, String> me2) {
//        return me1.getValue().compareTo(me2.getValue()); 升序
        return new BigDecimal(me2.getValue()).compareTo(new BigDecimal(me1.getValue()));//降序
    }
}

排序调用的方法

    /**
     * 使用 Map按value进行排序
     * @param map
     * @return
     */
  public static Map<String, String> sortMapByValue(Map<String, String> map) {
        if (map == null || map.isEmpty()) {
            return null;
        }
        Map<String, String> sortedMap = new LinkedHashMap<String, String>();
        List<Map.Entry<String, String>> entryList = new ArrayList<Map.Entry<String, String>>(map.entrySet());
        Collections.sort(entryList, new MapValueComparator());//自定义比较强
        Iterator<Map.Entry<String, String>> iter = entryList.iterator();
        Map.Entry<String, String> tmpEntry = null;
        while (iter.hasNext()) {
            tmpEntry = iter.next();
            sortedMap.put(tmpEntry.getKey(), tmpEntry.getValue());
        }
        return sortedMap;
    }
上一篇 下一篇