日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

Redis Zset实现统计模块

發布時間:2023/10/11 综合教程 82 老码农
生活随笔 收集整理的這篇文章主要介紹了 Redis Zset实现统计模块 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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实现统计模块的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。