上一篇的做法,经测试,redis存储20W条数据的时候,使用hscan取出的数据需要30秒往上了,如果要做到及时响应的效果还是需要优化.
优化思路: 新增字段,用来存储每个流量类型的用户使用流量总计.然后每隔一段时间更新这些数据 .取数据的时候直接取新字段.
首先将上一篇的接口方法封装成service 用于调用, 这里微调了一下,不排序了,因为就算排序好了,用hash类型存入redis之后 再取出来是无序的
//根据type获取到用户流量数据排行
public Map<String, String> getTrafficRank(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 = userMapper.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); 不排序 因为存入redis的时候又无序了 接口取数据的时候重新排序就行了
return userNameMap;
}
用定时器每隔1个小时调用一下这个方法刷新流量总计数据, 因为是hash类型,key存在的话就会更新value. 但是如果项目刚启动的时候不存在这些数据会导致获取的时候是null,所以在springboot项目初始化的时候调用一下,生成这些用户流量总计
这里写了两个方法.initTrafficRank() 在项目启动的时候调用,先判断redis有没有记录用户流量总计的key 如果没有则调用getTrafficRank()方法初始化这些key,此时项目启动的时候会比较缓慢.
package com.yh.proxy.sys.configcache;
import com.yh.proxy.basic.utils.LogUtils;
import com.yh.proxy.config.JedisUtil;
import com.yh.proxy.sys.constant.SysConstants;
import com.yh.proxy.sys.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Map;
/**
* @title: ConfigCache
* @Author kunkun
* @Date: 2022/1/4 14:37
* @Version 1.0
*/
@Component
@EnableScheduling
public class ConfigCache1 {
@Autowired
private SysUserService userService;
@PostConstruct
public void init(){
initTrafficRank();
}
@PreDestroy
public void destroy() {
//系统运行结束
}
/**
1. @Description: 项目启动的时候判断redis是否有排行榜数据 如果没有则初始化
2. @Author: kunkun
3. @Date: 2022/5/13 16:25
4. @Param:
5. @Return:
*/
public void initTrafficRank(){
//判断redis是否存在流量排行的key
boolean exists = JedisUtil.exists(SysConstants.TRAFFIC_RANK);
boolean existsJF = JedisUtil.exists(SysConstants.TRAFFIC_RANK_JF);
boolean exists4G = JedisUtil.exists(SysConstants.TRAFFIC_RANK_4G);
if(!exists && !existsJF && !exists4G){
LogUtils.info("=========redis不存在用户流量排行榜开始初始化=========");
getTrafficRank();
}
LogUtils.info("=========redis存在用户流量排行榜无需初始化=========");
}
/**
1. @Description: redis更新用户使用流量排行
2. @Author: kunkun
3. @Date: 2022/5/13 16:24
4. @Param:
5. @Return:
*/
@Scheduled(cron = "0 0 0/1 * * ? ")//一个小时执行一次
public void getTrafficRank(){
LogUtils.info("==============redis更新用户使用流量排行开始=================");
try {
long start = System.currentTimeMillis();
Map<String, String> trafficRank = userService.getTrafficRank(1);//动态住宅
Map<String, String> trafficRankjf = userService.getTrafficRank(2);//海外机房住宅
Map<String, String> gtrafficRank4g = userService.getTrafficRank(3);//4G住宅
//分别存入redis
JedisUtil.hmset(SysConstants.TRAFFIC_RANK,trafficRank);
JedisUtil.hmset(SysConstants.TRAFFIC_RANK_JF,trafficRankjf);
JedisUtil.hmset(SysConstants.TRAFFIC_RANK_4G,gtrafficRank4g);
long end = System.currentTimeMillis();
LogUtils.info("==============redis更新用户使用流量排行成功共耗时"+ (end - start) +"毫秒=================");
} catch (Exception e) {
e.printStackTrace();
LogUtils.info("==============更新redis用户流量排行榜失败=================");
}
}
}
这里用到了redis的exists命令 用于判断redis是否存在这个key
hmset命令 设置 key 指定的哈希集中指定字段的值 批量设置hash类型的值
public static boolean exists(String key) {
Jedis jedis = null;
Boolean exists = false;
try {
jedis = jedisPool.getResource();//获取一个jedis实例
exists = jedis.exists(key);
} catch (Exception e) {
LogUtils.error("错误日志:" + e.getMessage());
} finally {
jedis.close();
}
return exists;
}
用法
boolean exists = JedisUtil.exists(SysConstants.TRAFFIC_RANK);
public static void hmset( String key, Map<String,String>map) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();//获取一个jedis实例
jedis.hmset(key,map);
} catch (Exception e) {
LogUtils.error("错误日志:" + e.getMessage());
} finally {
jedis.close();
}
}
用法
JedisUtil.hmset(SysConstants.TRAFFIC_RANK,trafficRank);
controller 调用的数据不需要进行计算了 基本上是秒相应. 但是需要对取出来的数据进行一个排序操作 排序方法用的上一篇的不变
public JSONObject getTrafficRankingByType(Integer type){
Map<String, String> map = null;
Map<String, String> sortMap = null;
if(type == 1){
map = JedisUtil.hGetAll(SysConstants.TRAFFIC_RANK);
sortMap = sortMapByValue(map);
}
if(type == 2){
map = JedisUtil.hGetAll(SysConstants.TRAFFIC_RANK_JF);
sortMap = sortMapByValue(map);
}
if(type == 3){
map = JedisUtil.hGetAll(SysConstants.TRAFFIC_RANK_4G);
sortMap = sortMapByValue(map);
}
return success(sortMap);
}
这里用到了redis的hgetall命令 返回 key 指定的哈希集中所有的字段和值。 这里注意如果该字段存储的数据很大 切勿使用,因为redis是单线程 取出大量数据的时候会导致redis被这个操作阻塞
public static Map<String, String> hGetAll(String key){
Jedis jedis = null;
Map<String, String> map = null;
try {
jedis = jedisPool.getResource();
map = jedis.hgetAll(key);
} catch (Exception e) {
LogUtils.error("错误日志:" + e.getMessage());
return null;
} finally {
// 返还到连接池
if (jedis != null) {
jedis.close();
}
}
return map;
}