项目中需要查出每个用户的使用总流量,要将每个子账户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;
}