1. 背景

公司有一个配置中心系统,使用MySQL存储了大量的配置,但现在不清楚哪些配置正在线上使用,哪些已经废弃了,所以需要实现一个统计模块,实现以下两个功能:

  1. 查看总体配置的数量以及活跃的数量
  2. 查看每一条配置的使用量

2. 分析

2.1 总体配置的数量

直接在MySQL中count即可得到

2.2 每一条配置的使用量

实现方式有很多,经过选择之后,选取了用Redis的Zset来实现

2.2.1 HashMap

使用HashMap, 当获取到配置的使用,那配置的key获取value加1即可

可能存在的问题,并发问题,集群中每个节点的数据怎么聚合

2.2.2 MySQL增加字段

在MySQL的配置表中增加一个使用次数字段,当每次获取配置时,更新此字段

可能存在的问题,性能问题,频繁更新必然会影响MySQL的性能,这个功能相比提供配置来说,算作是一个辅助的、可有可无的功能,不能影响主要的业务

2.2.3 Redis存储

Redis存储性能比较高,可以使用string的INCR或者Zset的INCR命令对执行ID的配置进行计数,我选择了Zset, 原因是查询的时候方便聚合

3. 代码

以下代码是从线上代码中排除业务相关代码的示例

3.1 基本结构

经典的三层结构

  1. 存储层,也就是DAO,主要使用RedisTemplate和Redis进行交互
  2. 服务层,也就是Service, 主要用来实现具体的业务
  3. 控制层,业绩是Controller, 主要用来通过HTTP接口收集数据和展示数据

3.2 DAO代码

  1. 覆盖收集数据,永久保存不过期,用来收集存储配置总数类似的数据
	/**
* 覆盖收集数据,永久保存
*
* @param key 数据分类(类似MySQL表)
* @param metricToCount 指标-数量
*/
public void collect( String key, Map<String, Integer> metricToCount ){ key = makeKey( key );
String finalKey = key;
metricToCount.forEach( ( oneMetric, value ) -> {
redisTemplate.opsForZSet().add( finalKey, oneMetric, value );
} );
}
  1. 按天存储,并保存30天,用来收集每条配置用量的数据
        /**
* 按天增量收集数据,保存30天
*
* @param key 数据分类(类似MySQL表)
* @param metricToCount 指标-数量
*/
public void collectDaily( String key, Map<String, Integer> metricToCount ){ key = makeDailyKey( key );
String finalKey = key; metricToCount.forEach( ( oneMetric, value ) -> {
redisTemplate.opsForZSet().incrementScore( finalKey, oneMetric, value );
} ); Long expire = redisTemplate.getExpire( finalKey ); if( expire != null && expire == -1 ){
redisTemplate.expire( finalKey, 30, TimeUnit.DAYS );
}
}
  1. 查询单个数据
private Map<String, Integer> queryDirectly( String key ){

		Map<String, Integer> rs = new HashMap<>();

		Set<ZSetOperations.TypedTuple<String>> mertricToCountTuple = redisTemplate.opsForZSet().rangeWithScores( key, 0, -1 );

		if( mertricToCountTuple != null ){
for( ZSetOperations.TypedTuple<String> oneMetricCount : mertricToCountTuple ){
if( oneMetricCount.getScore() != null ){
rs.put( oneMetricCount.getValue(), oneMetricCount.getScore().intValue() );
}
}
} return rs;
} /**
* 根据数据分类查询数据
*
* @param key 数据分类
* @return 指标-数量
*/
public Map<String, Integer> query( String key ){ key = this.makeKey( key );
return queryDirectly( key );
}
  1. 查询时间聚合数据, 其中使用Redis管道操作来提高性能
        /**
* 根据数据分类和指定时间段查询数据
*
* @param key 数据分类
* @param start 开始时间
* @param end 结束时间
* @return 指标-数量
*/
public Map<String, Map<String, Integer>> queryTimeRange( String key, LocalDate start, LocalDate end ){ Map<String, Map<String, Integer>> rs = new HashMap<>(); List<LocalDate> keys = new ArrayList<>(); List<Object> tupleSets = redisTemplate.executePipelined( ( RedisCallback<Object> )redisConnection -> { redisConnection.openPipeline();
LocalDate dayInRange = start;
for( ; dayInRange.isBefore( end ); dayInRange = dayInRange.plusDays( 1 ) ){
String dayKey = makeDailyKey( key, dayInRange );
keys.add( dayInRange );
redisConnection.zRangeWithScores( dayKey.getBytes( StandardCharsets.UTF_8 ), 0, -1 ); }
return null;
} ); for( int i = 0; i < keys.size(); i++ ){
@SuppressWarnings( "unchecked" )
Set<DefaultTypedTuple<String>> tupleSet = ( Set<DefaultTypedTuple<String>> )tupleSets.get( i );
Map<String, Integer> metricToCount = new HashMap<>(); for( DefaultTypedTuple<String> tuple : tupleSet ){
if( tuple.getScore() != null ){
metricToCount.put( tuple.getValue(), tuple.getScore().intValue() ); }
}
rs.put( keys.get( i ).toString(), metricToCount );
}
return rs; }

3.3 Service代码

这里的代码是和业务相关的,因为不方便展示线上的代码,所以稍微调整了一下

  1. 收集和展示系统信息指标
    @PostConstruct
public void collectEveryConfigNum() { Map<String, Integer> metricToCount = new HashMap<>(); metricToCount.put(MetricKey.CPU_NUM.name(), Runtime.getRuntime().availableProcessors());
metricToCount.put(MetricKey.FREE_MEM.name(), (int) Runtime.getRuntime().freeMemory());
metricToCount.put(MetricKey.MAX_MEM.name(), (int) Runtime.getRuntime().maxMemory());
metricToCount.put(MetricKey.JVM_MEM.name(), (int) Runtime.getRuntime().totalMemory()); statisticDAO.collect(StatKey.SYSTEM_INFO.name(), metricToCount);
} public List<ConfigStat> configStat() {
List<ConfigStat> rs = new ArrayList<>();
Map<String, Integer> typeToTotalNum = statisticDAO.query(StatKey.SYSTEM_INFO.name());
for (String type : typeToTotalNum.keySet()) {
ConfigStat configStat = new ConfigStat(); configStat.setType(type);
configStat.setNum(typeToTotalNum.get(type));
rs.add(configStat);
}
return rs;
}
  1. 统计一个月内某个配置的使用量
 public Map<String, Integer> lastMonthUseCount(String key) {

        try {
Map<String, Integer> rs = new HashMap<>(); LocalDate now = LocalDate.now();
LocalDate lastMonthDate = now.minusDays(29);
LocalDate endDate = now.plusDays(1); Map<String, Map<String, Integer>> dateToUseCount = statisticDAO.queryTimeRange(key, lastMonthDate, endDate); for (Map<String, Integer> metricToCount : dateToUseCount.values()) {
for (Map.Entry<String, Integer> entry : metricToCount.entrySet()) {
rs.merge(entry.getKey(), entry.getValue(), Integer::sum);
} }
return rs;
} catch (Exception e) {
LOGGER.error("StatisticManager lastMonthUseCount error", e);
return new HashMap<>();
}
}
  1. 按天收集特定指标, 可以用于每条配置的使用量统计,也可以用做其他,例如,前端页面访问量统计
   public void collect(String key, Map<String, Integer> metricToCount) {

        statisticDAO.collectDaily(key, metricToCount);
}

3.3 Controller层代码

主要是通过对Serivce代码的调用,对外层提供收集和展示服务,在这就不展示了,可以到文尾的源码中查看

4. 成果

  1. 收集好的数据在Redis中是这样存储的

127.0.0.1:6379> keys *
1) "CC_STATISTIC:2022-03-08:API"
2) "CC_STATISTIC:SYSTEM_INFO" 127.0.0.1:6379> zrange CC_STATISTIC:SYSTEM_INFO 0 -1 withscores
1) "MAX_MEM"
2) "-477102080"
3) "CPU_NUM"
4) "8"
5) "FREE_MEM"
6) "349881120"
7) "JVM_MEM"
8) "376963072"
  1. 前端的展示如图

5. 源码

Github 中的redisStatistic模块是此文章的源码

Redis Zset实现统计模块的更多相关文章

  1. 实现排行榜神器——redis zset

    需求:假如现在需要搞个 “运动消耗卡路里排行榜”,例似微信步数排名,显示排名前20人的信息和消耗的卡里路,怎样实现排序? 一般思路:存储信息,然后数据库查询,排序?(假如有几十万人参与排名,这样查my ...

  2. 延时任务-基于redis zset的完整实现

    所谓的延时任务给大家举个例子:你买了一张火车票,必须在30分钟之内付款,否则该订单被自动取消.订单30分钟不付款自动取消,这个任务就是一个延时任务. 我之前已经写过2篇关于延时任务的文章: <完 ...

  3. REdis zset和double

    平台:x86_64 结论:Zset的最大分数不要超过18014398509481982(17位数字,54位二进制),否则不会得到期望的值. REdis:5.0.4 Zset采用double存储分数值( ...

  4. 将mysql表数据批量导入redis zset结构中

    工作中有这样一个需求,要将用户的魅力值数据做排行,生成榜单展示前40名,每隔5分钟刷新一次榜单.这样的需求用redis的zset是很方便实现的.但是数据存在mysql的表中,有400多万条,怎么将其快 ...

  5. Redis ZSet 有序集合

    有序集合类型与集合类型的区别就是他是有序的.有序集合是在集合的基础上为每一个元素关联一个分数,这就让有序集合不仅支持插入,删除,判断元素是否存在等操作外,还支持获取分数最高/最低的前N个元素.有序集合 ...

  6. redis zset底层实现原理

    一.Zset编码的选择 1.有序集合对象的编码可以是ziplist或者skiplist.同时满足以下条件时使用ziplist编码: 元素数量小于128个 所有member的长度都小于64字节 其他: ...

  7. Redis ZSet Type

    Redis有序集合的操作命令和对应的api如下: zadd [zset] sco 'value' JedisAPI:public Long zadd(final String key, final d ...

  8. Redis学习第六课:Redis ZSet类型及操作

    Sorted set是set的一个升级版本,它在set的基础上增加了一个顺序属性,这一属性在添加修改元素时候可以指定,每次指定后,zset会自动重新按新的值调整顺序.可以理解为有两列字段的数据表,一列 ...

  9. (PHP)redis Zset(有序集合 sorted set)操作

    /** * * Zset操作 * sorted set操作 * 有序集合 * sorted set 它在set的基础上增加了一个顺序属性,这一属性在修改添加元素的时候可以指定,每次指定后,zset会自 ...

  10. Redis zset数据类型

    zadd():添加元素 zcard :返回元素个数

随机推荐

  1. Containerd和Docker的关系

    联系 容器运行时(Container Runtime)是Kubernetes(k8s)最重要的组件之一,负责管理镜像和容器的生命周期.Kubelet通过Container Runtime Interf ...

  2. SEO知识点

    SEO中的长尾理论 长尾关键词就是包含关键信息,但是搜索量比较少的句子或词组. 每一个长尾关键词都可能会为网站带来流量.一般一个较大的网站,流量的主要来源可能都由长尾关键词构成,因为网站除了目标关键词 ...

  3. CentOS yum如何安装php7.4

    centos系统下使用yum安装php7.4正式版,当前基于WLNMP提供的一键安装包来安装 1.添加epel源 yum install epel-release 2.添加WLNMP一键安装包源 rp ...

  4. 一文读懂Apache Geode缓存中间件

    目录 一.对缓存中间件的诉求 1.1 我们为什么需要缓存中间件 1.2 缓存的分类 1.1.1 弱势缓存 1.1.2 强势缓存 二.什么是Apache Geode 2.1 Apache Geode的架 ...

  5. 【pytest官方文档】解读- 插件开发之hooks 函数(钩子)

    上一节讲到如何安装和使用第三方插件,用法很简单.接下来解读下如何自己开发pytest插件. 但是,由于一个插件包含一个或多个钩子函数开发而来,所以在具体开发插件之前还需要先学习hooks函数. 一.什 ...

  6. RAID5 IO处理之写请求代码详解

    我们知道RAID5一个条带上的数据是由N个数据块和1个校验块组成,其校验块由N个数据块通过异或运算得出,这样才能在任意一个成员磁盘失效时通过其他N个成员磁盘恢复出用户写入的数据.这也就要求RAID5条 ...

  7. 1.MongoDB之服务启动

    1. 编写docker-compose.yaml文件 version: "3" services: mongo: image: mongo:4.2.6 ports: - 27017 ...

  8. 一个电器工厂可以生产多种类型的电器,如海尔工厂可以生产海尔电视机、海尔空调等,TCL工厂可以生产TCL电视机,TCL空调等,相同品牌的电器构成一个产品族,而相同类型的电器构成了一个产品等级结构,现使用

    一个电器工厂可以生产多种类型的电器,如海尔工厂可以生产海尔电视机.海尔空调等,TCL工厂可以生产TCL电视机,TCL空调等,相同品牌的电器构成一个产品族,而相同类型的电器构成了一个产品等级结构,现使用 ...

  9. 表单快速启用城市地区功能 齐博x1齐博x2齐博x3齐博x4齐博x5齐博x6齐博x7齐博x8齐博x9齐博x10

    比如分类系统\application\fenlei\config.php 修改这个文件,里边加入参数 'use_area'=>true, 那么会员中心与后台的,修改发布页面,都会自动加上城市地区 ...

  10. 齐博x1一段不错的小js提高一点点阅读体验 计算本文阅读所需的时长

    如图所示很多比较大的站点都有这样的一个小玩意 就是本文有多少字 阅读需要多少时间. 一段小小的js就可以实现,当然了php也可以功能太小了不值得做钩子或者插件自己需要的话再模板加一下吧. <sc ...