redis应用场景(2)日志记录及指标统计

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
日志服务 SLS,月写入数据量 50GB 1个月
简介:

使用redis存储业务信息,同时也可以存储系统运维信息,比如日志和计数器来收集系统当前的状态信息,挖掘正在使用系统的顾客信息,以及诊断系统问题,发现潜在的问题。当然,系统日志信息及统计信息也可以存储在关系型数据库中,但是存在一个很大的弊端,影响业务性能。


1.使用redis记录日志

熟悉java的朋友,记录日志往往采用的是log4j,sl4j,大多记录载体选择文本文件。如果使用web集群的话,造成日志分散在各个web服务器,搜集有效日志信息,非常麻烦。如果选择数据库保存的话,解决了文件分散情况,但势必对业务造成影响,日志毕竟是个辅助支撑而已,不应该和业务系统相提并论。这时候,redis是一个不错的选择。如果可以的话,可以对log4j扩展,将数据保存到redis中,当然这不是本章的重点。本章重点,主要简单讨论下如何保存日志。

构建一个系统,判断哪些信息需要被记录是一件困难的事情,不同的业务有不同的需求。但一般的日志信息往往关注一下方面。

日志时间,日志内容,服务IP,日志级别,日志发生频率。

1.1redis日志存储设计

wKiom1fbcneAyJyaAAA3q53JGAQ005.png

记录详情里,可以按要求,增添想要的信息,发生的类名称,处理IP等。

1.2代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public  void  logCommon(
         Jedis conn, String name, String message, String severity,  int  timeout) {
     String commonDest =  "common:"  + name +  ':'  + severity;
     String startKey = commonDest +  ":start" ;
     long  end = System.currentTimeMillis() + timeout;
     while  (System.currentTimeMillis() < end){
         conn.watch(startKey);
         //当前所处的小时数
         String hourStart = ISO_FORMAT.format( new  Date());
         String existing = conn.get(startKey);
 
         Transaction trans = conn.multi();
         //如果记录的是上一个小时的日志
         if  (existing !=  null  && COLLATOR.compare(existing, hourStart) <  0 ){
             trans.rename(commonDest, commonDest +  ":last" );
             trans.rename(startKey, commonDest +  ":pstart" );
             trans.set(startKey, hourStart);
         } else {
             trans.set(startKey, hourStart);
         }
          //日志计数器增1
         trans.zincrby(commonDest,  1 , message);
         //记录最近日志详情
         String recentDest =  "recent:"  + name +  ':'  + severity;
         trans.lpush(recentDest, TIMESTAMP.format( new  Date()) +  ' '  + message);
         trans.ltrim(recentDest,  0 99 );
         List<Object> results = trans.exec();
         // null response indicates that the transaction was aborted due to
         // the watched key changing.
         if  (results ==  null ){
             continue ;
         }
         return ;
     }
}

2.网站点击量计数器统计

2.1redis计数器存储设计

wKioL1fbfrzDb0BzAABPubjNr2c322.png

2.2编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//以秒为单位的精度
public  static  final  int [] PRECISION =  new  int []{ 1 5 60 300 3600 18000 86400 };
public  void  updateCounter(Jedis conn, String name,  int  count,  long  now){
     Transaction trans = conn.multi();
     //每一次更新,都要更新所有精度的计数器
     for  ( int  prec : PRECISION) {
         long  pnow = (now / prec) * prec; //当前时间片的开始时间
         String hash = String.valueOf(prec) +  ':'  + name;
         trans.zadd( "known:" 0 , hash);
         trans.hincrBy( "count:"  + hash, String.valueOf(pnow), count);
     }
     trans.exec();
}
 
public  List<Pair<Integer,Integer>> getCounter(
     Jedis conn, String name,  int  precision)
{
     String hash = String.valueOf(precision) +  ':'  + name;
     Map<String,String> data = conn.hgetAll( "count:"  + hash);
     ArrayList<Pair<Integer,Integer>> results =
         new  ArrayList<Pair<Integer,Integer>>();
     for  (Map.Entry<String,String> entry : data.entrySet()) {
         results.add( new  Pair<Integer,Integer>(
                     Integer.parseInt(entry.getKey()),
                     Integer.parseInt(entry.getValue())));
     }
     Collections.sort(results);
     return  results;
}


2.3清楚旧数据


流程图

wKiom1fbhyPh_L_UAABqzIhA1As000.png

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class CleanCountersThread
     extends Thread
{
     private Jedis conn;
     private int sampleCount = 100;
     private boolean quit;
     private long timeOffset;  //  used to mimic a  time  in  the future.
 
     public CleanCountersThread(int sampleCount, long timeOffset){
         this.conn = new Jedis( "192.168.163.156" );
         this.conn. select (15);
         this.sampleCount = sampleCount;
         this.timeOffset = timeOffset;
     }
 
     public void quit(){
         quit =  true ;
     }
 
     public void run(){
         int passes = 0;
         while  (!quit){
             long start = System.currentTimeMillis() + timeOffset;
             int index = 0;
             while  (index < conn.zcard( "known:" )){
                 Set<String> hashSet = conn.zrange( "known:" , index, index);
                 index++;
                 if  (hashSet.size() == 0) {
                     break ;
                 }
                 String  hash  = hashSet.iterator().next();
                 int prec = Integer.parseInt( hash .substring(0,  hash .indexOf( ':' )));
                 int bprec = (int)Math.floor(prec / 60);
                 if  (bprec == 0){
                     bprec = 1;
                 }
                 if  ((passes % bprec) != 0){
                     continue ;
                 }
 
                 String hkey =  "count:"  hash ;
                 String cutoff = String.valueOf(
                     ((System.currentTimeMillis() + timeOffset) / 1000) - sampleCount * prec);
                 ArrayList<String> samples = new ArrayList<String>(conn.hkeys(hkey));
                 Collections. sort (samples);
                 int remove = bisectRight(samples, cutoff);
 
                 if  (remove != 0){
                     conn.hdel(hkey, samples.subList(0, remove).toArray(new String[0]));
                     if  (remove == samples.size()){
                         conn. watch (hkey);
                         if  (conn.hlen(hkey) == 0) {
                             Transaction trans = conn.multi();
                             trans.zrem( "known:" hash );
                             trans. exec ();
                             index--;
                         } else {
                             conn.unwatch();
                         }
                     }
                 }
             }
 
             passes++;
             long duration = Math.min(
                 (System.currentTimeMillis() + timeOffset) - start + 1000, 60000);
             try {
                 sleep (Math.max(60000 - duration, 1000));
             }catch(InterruptedException ie){
                 Thread.currentThread().interrupt();
             }
         }
     }
 
     //  mimic python's bisect.bisect_right
     public int bisectRight(List<String> values, String key) {
         int index = Collections.binarySearch(values, key);
         return  index < 0 ? Math.abs(index) - 1 : index + 1;
     }
}

3.使用redis统计数据

上面提到的计数器,是最简单的统计数据。除了计数器(count(*)),还是最大值(max),最小值(min).

设计

wKiom1fbiXig10-bAABFDHnJJ6Y518.png

stats:模块(页面)名称:指标名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public  List<Object> updateStats(Jedis conn, String context, String type,  double  value){
     int  timeout =  5000 ;
     String destination =  "stats:"  + context +  ':'  + type;
     String startKey = destination +  ":start" ;
     long  end = System.currentTimeMillis() + timeout;
     while  (System.currentTimeMillis() < end){
         conn.watch(startKey);
         String hourStart = ISO_FORMAT.format( new  Date());
 
         String existing = conn.get(startKey);
         Transaction trans = conn.multi();
         if  (existing !=  null  && COLLATOR.compare(existing, hourStart) <  0 ){
             trans.rename(destination, destination +  ":last" );
             trans.rename(startKey, destination +  ":pstart" );
             trans.set(startKey, hourStart);
         }
         //借助redis提供的最大值,最小值计算        
         String tkey1 = UUID.randomUUID().toString();
         String tkey2 = UUID.randomUUID().toString();
         trans.zadd(tkey1, value,  "min" );
         trans.zadd(tkey2, value,  "max" );
 
         trans.zunionstore(
             destination,
             new  ZParams().aggregate(ZParams.Aggregate.MIN),
             destination, tkey1);
         trans.zunionstore(
             destination,
             new  ZParams().aggregate(ZParams.Aggregate.MAX),
             destination, tkey2);
 
         trans.del(tkey1, tkey2);
         trans.zincrby(destination,  1 "count" );
         trans.zincrby(destination, value,  "sum" );
         trans.zincrby(destination, value * value,  "sumsq" );
 
         List<Object> results = trans.exec();
         if  (results ==  null ){
             continue ;
         }
         return  results.subList(results.size() -  3 , results.size());
     }
     return  null ;
}

需要注意的使用redis自带的最大值最小值,计算,所以创建了2个临时有序集合。其他的逻辑参照日志相关部分。




本文转自 randy_shandong 51CTO博客,原文链接:http://blog.51cto.com/dba10g/1853096,如需转载请自行联系原作者

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
19天前
|
NoSQL 安全 测试技术
Redis游戏积分排行榜项目中通义灵码的应用实战
Redis游戏积分排行榜项目中通义灵码的应用实战
39 4
|
24天前
|
存储 运维 监控
API明细日志及运维统计日志全面提升API可运维性
在数字化转型的大潮中,数据已成为企业最宝贵的资产之一。而数据服务API可快速为数据应用提供数据接口。面对越来越多的API以及越来越多的应用调用,如何快速查看API的服务情况、异常情况及影响范围,以及查看API的调用详情,进行API的性能优化、错误排查变得越来越重要,本文将介绍如何配置和开通API运维统计及明细日志,以及如何查看日志进行介绍。
|
3月前
|
机器学习/深度学习 存储 监控
Elasticsearch 在日志分析中的应用
【9月更文第2天】随着数字化转型的推进,日志数据的重要性日益凸显。日志不仅记录了系统的运行状态,还提供了宝贵的洞察,帮助企业改进产品质量、优化用户体验以及加强安全防护。Elasticsearch 作为一个分布式搜索和分析引擎,因其出色的性能和灵活性,成为了日志分析领域的首选工具之一。本文将探讨如何使用 Elasticsearch 作为日志分析平台的核心组件,并详细介绍 ELK(Elasticsearch, Logstash, Kibana)栈的搭建和配置流程。
330 4
|
4月前
|
Java API 开发者
你的应用是不是只有service_stdout.log?
本文记录了logback-spring.xml文件不生效问题的整体排查思路。
|
4月前
|
缓存 NoSQL Java
Redis深度解析:解锁高性能缓存的终极武器,让你的应用飞起来
【8月更文挑战第29天】本文从基本概念入手,通过实战示例、原理解析和高级使用技巧,全面讲解Redis这一高性能键值对数据库。Redis基于内存存储,支持多种数据结构,如字符串、列表和哈希表等,常用于数据库、缓存及消息队列。文中详细介绍了如何在Spring Boot项目中集成Redis,并展示了其工作原理、缓存实现方法及高级特性,如事务、发布/订阅、Lua脚本和集群等,帮助读者从入门到精通Redis,大幅提升应用性能与可扩展性。
85 0
|
10天前
|
运维 监控 Cloud Native
一行代码都不改,Golang 应用链路指标日志全知道!
本文将通过阿里云开源的 Golang Agent,帮助用户实现“一行代码都不改”就能获取到应用产生的各种观测数据,同时提升运维团队和研发团队的幸福感。
|
9天前
|
监控 应用服务中间件 定位技术
要统计Nginx的客户端IP,可以通过分析Nginx的访问日志文件来实现
要统计Nginx的客户端IP,可以通过分析Nginx的访问日志文件来实现
|
11天前
|
存储 Prometheus 监控
Docker容器内进行应用调试与故障排除的方法与技巧,包括使用日志、进入容器检查、利用监控工具及检查配置等,旨在帮助用户有效应对应用部署中的挑战,确保应用稳定运行
本文深入探讨了在Docker容器内进行应用调试与故障排除的方法与技巧,包括使用日志、进入容器检查、利用监控工具及检查配置等,旨在帮助用户有效应对应用部署中的挑战,确保应用稳定运行。
23 5
|
20天前
|
监控 NoSQL 网络协议
【Azure Redis】部署在AKS中的应用,连接Redis高频率出现timeout问题
查看Redis状态,没有任何异常,服务没有更新,Service Load, CPU, Memory, Connect等指标均正常。在排除Redis端问题后,转向了AKS中。 开始调查AKS的网络状态。最终发现每次Redis客户端出现超时问题时,几乎都对应了AKS NAT Gateway的更新事件,而Redis服务端没有任何异常。因此,超时问题很可能是由于NAT Gateway更新事件导致TCP连接被重置。
|
21天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
36 5