上一篇的做法,经测试,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;
    }
上一篇 下一篇