某个计数器应用采集端分析

在我们应用经常需要统计一些计数,比如调用次数等。典型代码如下:

        Keys keys = newKeys("XXX","doSomething");
        MonitorLog.addStat(keys, System.currentTimeMillis()-begin, 1L);
 

  在监控系统的页面上就能看到准实时的数据。

  这是一个典型的计数器应用,实现也是比较经典。

  类图

 

  处理流程:

1.  初始化:

MonitorLog被加载时执行其static区代码:

static {  /** 动态创建日志记录的配置 */
      String userHome = System.getProperty(MonitorConstants.USER_HOME);
      if(!userHome.endsWith(File.separator)) {
         userHome+= File.separator;
      }
      String filePath = userHome+ MonitorConstants.DIR_NAME + File.separator;
      File dir = newFile(filePath);
      if (!dir.exists()) {
        dir.mkdirs();
      }
      ……
      String classLoader =MonitorLog.class.getClassLoader().toString();
      classLoader =classLoader.split("@")[0];

      initAppLog4j(filePath,classLoader);
      initMiddlewareLog4j(filePath,classLoader);
      setHostName();
      runWriteThread();
}
 

1)在用户的根目录下创建日志目录,linux下是/home/$user/logs/monitor

2)初始化应用日志文件,Logger和对应Appender,使用classloader名称作为文件名,使用DailyRollingFileAppender

3)同样方式初始化中间件的日志文件和Appender

4)启动flush线程

2.  应用方调用MonitorLog.addStat()方法,写日志

1)  从cache中获取key对应的ValueObject,如果没有,则创建之,同时如果cache的size超过默认100000,则该key被丢弃

使用RetreenLock+doublecheck实现。此处代码略有多余,使用ConcurrentHashMap的putIfAbsent即可。

2)  调用ValueObject. addCount()递增计数,代码:

     long[]current;
      long[] update = new long[NUM_VALUES];
      do {
         current= values.get();
        update[0] = current[0] + value1;
        update[1] = current[1] + value2;
    } while(!values.compareAndSet(current, update));
 

使用CAS实现并发递增,使用AtomicReference实现对计数同步更新

3.  flush线程,实现cache数据的定期刷新,2分钟一次

1)  周期控制,代码:

             while (true) {
               timerLock.lock();
               try {
                  condition.await(waitTime, TimeUnit.SECONDS);
               } catch (Exception e) {
                  logger.error("wait error", e);
               } finally {
                  timerLock.unlock();
               }
 

使用Condition的await实现。此处单线程用Thread.sleep更加简单实现。如果是多线程场景,condition.await可以提供另一种调度控制方式,对开发比较友好(await/signal)。

2)  writeLog()调用,使用log4j刷日志到磁盘.

a.  使用临时Map存放待刷磁盘的数据快照

b.  遍历原始map数据,拼装成一个SringBuilder,并将Key数据快照存入临时Map

c.  将日志刷磁盘

           if (tmp.size() > 0 && writeLog) {
              appStatLog.info(sb);
    }
 

d.  根据之前key的数据快照递减原始计数

        // 循环把已经写入文件的数据从datas中减少
         for (Keys key: tmp.keySet()) {
         long[]values = tmp.get(key).getValues();
         appDatas.get(key).deductCount(values[0],values[1]);
    }
 

e.  同样方式刷中间件的日志数据

f.  MonitorLog刷到磁盘的数据,会由哈勃的agent定期回收到哈勃服务端存储

      小结:

1. 使用Map务必注意map引起的内存泄漏问题,size检查必不可少

2. Flush线程和业务线程并发控制很重要,使用CAS以提高并发性能

3. 需要对同一个对象的不同属性进行原子更新时,可以使用AtomicReference+CAS实现

猜你喜欢

转载自iwinit.iteye.com/blog/1753125