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

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

1.使用redis记录日志

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

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

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

1.1redis日志存储设计

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

1.2代码

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计数器存储设计

2.2编码

//以秒为单位的精度
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清楚旧数据

流程图

代码

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).

设计


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

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个临时有序集合。其他的逻辑参照日志相关部分。


参考内容

《redis in action》

时间: 2024-11-08 17:07:34

redis应用场景(2)日志记录及指标统计的相关文章

redis队列结合log4net实现异常日志记录

查看了一些关于mvc异常日志记录的文章,发现使用redis+log4net的最多,这里简单总结了使用这种方式实现日志记录的过程.直接上步骤: 第一步:配置redis服务器 参考redis的配置和在.net中的使用 第二步:添加自己的异常过滤器(Models文件夹添加一个MyExceptionAttribute类) 1 public class MyExceptionAttribute : HandleErrorAttribute 2 { 3 //----所有用户出现异常,向同一个静态队列添加数据

wcf利用IDispatchMessageInspector实现接口监控日志记录和并发限流

一般对于提供出来的接口,虽然知道在哪些业务场景下才会被调用,但是不知道什么时候被调用.调用的频率.接口性能,当出现问题的时候也不容易重现请求:为了追踪这些内容就需要把每次接口的调用信息给完整的记录下来,也就是记录日志.日志中可以把调用方ip.服务器ip.调用时间点.时长.输入输出都给完整的记录下来,有了这些数据,排查问题.重现异常.性能瓶颈都能准确的找到切入点. 这种功能,当然没人想要去在每个Operation里边插入一段代码,如果有类似AOP的玩意就再好不过了. wcf中有IDispatchM

Redis应用场景说明与部署

Redis简介 REmote DIctionary Server(Redis)是一个基于key-value键值对的持久化数据库存储系统.redis和大名鼎鼎的memcached缓存服务很像,但是redis支持的数据存储类型更丰富,但是redis支持的数据存储类型更丰富,包括string(字符串).list(链表),set(集合)和zset(有序集合)等.这些数据类型都支持push/pop.add/remove及取交集.并集和差集及更丰富的操作,而且这些操作都是原子性的.在此基础上,redis支持

详解 Redis 应用场景及应用实例

Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起,Redis的开发工作由VMware主持. 1. MySql+Memcached架构的问题 实际MySQL是适合进行海量数据存储的,通过Memcached将热点数据加载到cache,加速访问,很多公司都曾经使用过这样的架构,但随着业务数据量的不断增加,和访问量的持续增长,我们遇到了很多问题: 1.MySQL需要不断进行拆库拆表,Memc

详解 Redis 应用场景及原理

本文转自https://blog.csdn.net/niucsd/article/details/50966733,描述了redis实现原理和应用场景,篇幅较长,有意学习redis的同学可耐心阅读. Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起,Redis的开发工作由VMware主持. 1. MySql+Memcached架构的问题 实际MySQL是适合进行海量数据存储的,通过M

redis 数据类型详解 以及 redis适用场景场合

1.  MySql+Memcached架构的问题 实际MySQL是适合进行海量数据存储的,通过Memcached将热点数据加载到cache,加速访问,很多公司都曾经使用过这样的架构,但随着业务数据量的不断增加,和访问量的持续增长,我们遇到了很多问题: 1.MySQL需要不断进行拆库拆表,Memcached也需不断跟着扩容,扩容和维护工作占据大量开发时间. 2.Memcached与MySQL数据库数据一致性问题. 3.Memcached数据命中率低或down机,大量访问直接穿透到DB,MySQL无

Redis使用场景

一.Redis开创了一种新的数据存储思路,使用Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用Redis灵活多变的数据结构和数据操作,为不同的大象构建不同的冰箱. Redis常用数据类型 Redis最为常用的数据类型主要有以下五种: StringHashListSetSorted set 下面我们先来逐一的分析下这五种数据类型的使用和内部实现方式: String常用命令: set,get,decr,incr,mget 等. 应用场景: String

在谈Redis应用场景(再)

原文:在谈Redis应用场景 一.MySql+Memcached架构的问题 实际MySQL是适合进行海量数据存储的,通过Memcached将热点数据加载到cache,加速访问,很多公司都曾经使用过这样的架构,但随着业务数据量的不断增加,和访问量的持续增长,我们遇到了很多问题: 1)MySQL需要不断进行拆库拆表,Memcached也需不断跟着扩容,扩容和维护工作占据大量开发时间. 2)Memcached与MySQL数据库数据一致性问题. 3)Memcached数据命中率低或down机,大量访问直

log4net.NoSql +ElasticSearch 实现日志记录

前言: 前两天在查找如何扩展log4net的日志格式时找到一个开源项目Log4net.NoSql,它通过扩展Appender实现了把日志输出到ElasticSearch里面.顺藤摸瓜,发现涉及的项目还挺多,于是打算学习一下,记录在此. 项目一句话简介,详情点击链接去项目主页查看,最后提供打包下载: 1.log4net.nosql A collection of log4net Appenders to NoSQL data stores. Currently only ElasticSearch