zoukankan      html  css  js  c++  java
  • 设计模式简记-实战二:如何实现一个支持各种统计规则的性能计数器?

    3.12 实战二:如何实现一个支持各种统计规则的性能计数器?

    3.12.1 划分职责进而识别出有哪些类

    根据需求描述,先大致识别出下面几个接口或类。这一步不难,完全就是翻译需求。

    • MetricsCollector 类负责提供 API,来采集接口请求的原始数据。我们可以为 MetricsCollector 抽象出一个接口,但这并不是必须的,因为暂时我们只能想到一个 MetricsCollector 的实现方式。
    • MetricsStorage 接口负责原始数据存储,RedisMetricsStorage 类实现 MetricsStorage 接口。这样做是为了今后灵活地扩展新的存储方法,比如用 HBase 来存储。
    • Aggregator 类负责根据原始数据计算统计数据。
    • ConsoleReporter 类、EmailReporter 类分别负责以一定频率统计并发送统计数据到命令行和邮件。至于 ConsoleReporter 和 EmailReporter 是否可以抽象出可复用的抽象类,或者抽象出一个公共的接口,我们暂时还不能确定。

    3.12.2 定义类及类与类之间的关系

    • 识别出几个核心的类之后,先在 IDE 中创建好这几个类,然后开始试着定义它们的属性和方法。在设计类、类与类之间交互的时候,不断地用之前学过的设计原则和思想来审视设计是否合理

      比如,是否满足单一职责原则、开闭原则、依赖注入、KISS 原则、DRY 原则、迪米特法则,是否符合基于接口而非实现编程思想,代码是否高内聚、低耦合,是否可以抽象出可复用代码等等。

    • 数据采集类

      public class MetricsCollector {
        private MetricsStorage metricsStorage;//基于接口而非实现编程
      
        //依赖注入
        public MetricsCollector(MetricsStorage metricsStorage) {
          this.metricsStorage = metricsStorage;
        }
      
        //用一个函数代替了最小原型中的两个函数
        public void recordRequest(RequestInfo requestInfo) {
          if (requestInfo == null || StringUtils.isBlank(requestInfo.getApiName())) {
            return;
          }
          metricsStorage.saveRequestInfo(requestInfo);
        }
      }
      
      public class RequestInfo {
        private String apiName;
        private double responseTime;
        private long timestamp;
        //...省略constructor/getter/setter方法...
      }
      
    • MetricsStorage 类和 RedisMetricsStorage 类的属性和方法也比较明确。具体的代码实现如下所示。注意,一次性取太长时间区间的数据,可能会导致拉取太多的数据到内存中,有可能会撑爆内存(OOM, FULL GC)。

      public interface MetricsStorage {
        void saveRequestInfo(RequestInfo requestInfo);
      
        List<RequestInfo> getRequestInfos(String apiName, long startTimeInMillis, long endTimeInMillis);
      
        Map<String, List<RequestInfo>> getRequestInfos(long startTimeInMillis, long endTimeInMillis);
      }
      
      public class RedisMetricsStorage implements MetricsStorage {
        //...省略属性和构造函数等...
        @Override
        public void saveRequestInfo(RequestInfo requestInfo) {
          //...
        }
      
        @Override
        public List<RequestInfo> getRequestInfos(String apiName, long startTimestamp, long endTimestamp) {
          //...
        }
      
        @Override
        public Map<String, List<RequestInfo>> getRequestInfos(long startTimestamp, long endTimestamp) {
          //...
        }
      }
      
    • 统计和显示所要完成的功能逻辑细分:

      • 根据给定的时间区间,从数据库中拉取数据;
      • 根据原始数据,计算得到统计数据;
      • 将统计数据显示到终端(命令行或邮件);
      • 定时触发以上 3 个过程的执行。

      选择把第 1、3、4 逻辑放到 ConsoleReporter 或 EmailReporter 类中,把第 2 个逻辑放到 Aggregator 类中。其中,Aggregator 类负责的逻辑比较简单把它设计成只包含静态方法的工具类。具体的代码实现如下所示:

      public class Aggregator {
        public static RequestStat aggregate(List<RequestInfo> requestInfos, long durationInMillis) {
          double maxRespTime = Double.MIN_VALUE;
          double minRespTime = Double.MAX_VALUE;
          double avgRespTime = -1;
          double p999RespTime = -1;
          double p99RespTime = -1;
          double sumRespTime = 0;
          long count = 0;
          for (RequestInfo requestInfo : requestInfos) {
            ++count;
            double respTime = requestInfo.getResponseTime();
            if (maxRespTime < respTime) {
              maxRespTime = respTime;
            }
            if (minRespTime > respTime) {
              minRespTime = respTime;
            }
            sumRespTime += respTime;
          }
          if (count != 0) {
            avgRespTime = sumRespTime / count;
          }
          long tps = (long)(count / durationInMillis * 1000);
          Collections.sort(requestInfos, new Comparator<RequestInfo>() {
            @Override
            public int compare(RequestInfo o1, RequestInfo o2) {
              double diff = o1.getResponseTime() - o2.getResponseTime();
              if (diff < 0.0) {
                return -1;
              } else if (diff > 0.0) {
                return 1;
              } else {
                return 0;
              }
            }
          });
          int idx999 = (int)(count * 0.999);
          int idx99 = (int)(count * 0.99);
          if (count != 0) {
            p999RespTime = requestInfos.get(idx999).getResponseTime();
            p99RespTime = requestInfos.get(idx99).getResponseTime();
          }
          RequestStat requestStat = new RequestStat();
          requestStat.setMaxResponseTime(maxRespTime);
          requestStat.setMinResponseTime(minRespTime);
          requestStat.setAvgResponseTime(avgRespTime);
          requestStat.setP999ResponseTime(p999RespTime);
          requestStat.setP99ResponseTime(p99RespTime);
          requestStat.setCount(count);
          requestStat.setTps(tps);
          return requestStat;
        }
      }
      
      public class RequestStat {
        private double maxResponseTime;
        private double minResponseTime;
        private double avgResponseTime;
        private double p999ResponseTime;
        private double p99ResponseTime;
        private long count;
        private long tps;
        //...省略getter/setter方法...
      }
      
      • ConsoleReporter 类相当于一个上帝类,定时根据给定的时间区间,从数据库中取出数据,借助 Aggregator 类完成统计工作,并将统计结果输出到命令行。具体的代码实现如下所示:
      public class ConsoleReporter {
        private MetricsStorage metricsStorage;
        private ScheduledExecutorService executor;
      
        public ConsoleReporter(MetricsStorage metricsStorage) {
          this.metricsStorage = metricsStorage;
          this.executor = Executors.newSingleThreadScheduledExecutor();
        }
        
        // 第4个代码逻辑:定时触发第1、2、3代码逻辑的执行;
        public void startRepeatedReport(long periodInSeconds, long durationInSeconds) {
          executor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
              // 第1个代码逻辑:根据给定的时间区间,从数据库中拉取数据;
              long durationInMillis = durationInSeconds * 1000;
              long endTimeInMillis = System.currentTimeMillis();
              long startTimeInMillis = endTimeInMillis - durationInMillis;
              Map<String, List<RequestInfo>> requestInfos =
                      metricsStorage.getRequestInfos(startTimeInMillis, endTimeInMillis);
              Map<String, RequestStat> stats = new HashMap<>();
              for (Map.Entry<String, List<RequestInfo>> entry : requestInfos.entrySet()) {
                String apiName = entry.getKey();
                List<RequestInfo> requestInfosPerApi = entry.getValue();
                // 第2个代码逻辑:根据原始数据,计算得到统计数据;
                RequestStat requestStat = Aggregator.aggregate(requestInfosPerApi, durationInMillis);
                stats.put(apiName, requestStat);
              }
              // 第3个代码逻辑:将统计数据显示到终端(命令行或邮件);
              System.out.println("Time Span: [" + startTimeInMillis + ", " + endTimeInMillis + "]");
              Gson gson = new Gson();
              System.out.println(gson.toJson(stats));
            }
          }, 0, periodInSeconds, TimeUnit.SECONDS);
        }
      }
      
      public class EmailReporter {
        private static final Long DAY_HOURS_IN_SECONDS = 86400L;
      
        private MetricsStorage metricsStorage;
        private EmailSender emailSender;
        private List<String> toAddresses = new ArrayList<>();
      
        public EmailReporter(MetricsStorage metricsStorage) {
          this(metricsStorage, new EmailSender(/*省略参数*/));
        }
      
        public EmailReporter(MetricsStorage metricsStorage, EmailSender emailSender) {
          this.metricsStorage = metricsStorage;
          this.emailSender = emailSender;
        }
      
        public void addToAddress(String address) {
          toAddresses.add(address);
        }
      
        public void startDailyReport() {
          Calendar calendar = Calendar.getInstance();
          calendar.add(Calendar.DATE, 1);
          calendar.set(Calendar.HOUR_OF_DAY, 0);
          calendar.set(Calendar.MINUTE, 0);
          calendar.set(Calendar.SECOND, 0);
          calendar.set(Calendar.MILLISECOND, 0);
          Date firstTime = calendar.getTime();
          Timer timer = new Timer();
          timer.schedule(new TimerTask() {
            @Override
            public void run() {
              long durationInMillis = DAY_HOURS_IN_SECONDS * 1000;
              long endTimeInMillis = System.currentTimeMillis();
              long startTimeInMillis = endTimeInMillis - durationInMillis;
              Map<String, List<RequestInfo>> requestInfos =
                      metricsStorage.getRequestInfos(startTimeInMillis, endTimeInMillis);
              Map<String, RequestStat> stats = new HashMap<>();
              for (Map.Entry<String, List<RequestInfo>> entry : requestInfos.entrySet()) {
                String apiName = entry.getKey();
                List<RequestInfo> requestInfosPerApi = entry.getValue();
                RequestStat requestStat = Aggregator.aggregate(requestInfosPerApi, durationInMillis);
                stats.put(apiName, requestStat);
              }
              // TODO: 格式化为html格式,并且发送邮件
            }
          }, firstTime, DAY_HOURS_IN_SECONDS * 1000);
        }
      }
      

    3.12.3 将类组装起来并提供执行入口

    两个执行入口:一个是 MetricsCollector 类,提供了一组 API 来采集原始数据;另一个是 ConsoleReporter 类和 EmailReporter 类,用来触发统计显示。框架具体的使用方式如下所示:

    public class Demo {
      public static void main(String[] args) {
        MetricsStorage storage = new RedisMetricsStorage();
        ConsoleReporter consoleReporter = new ConsoleReporter(storage);
        consoleReporter.startRepeatedReport(60, 60);
    
        EmailReporter emailReporter = new EmailReporter(storage);
        emailReporter.addToAddress("wangzheng@xzg.com");
        emailReporter.startDailyReport();
    
        MetricsCollector collector = new MetricsCollector(storage);
        collector.recordRequest(new RequestInfo("register", 123, 10234));
        collector.recordRequest(new RequestInfo("register", 223, 11234));
        collector.recordRequest(new RequestInfo("register", 323, 12334));
        collector.recordRequest(new RequestInfo("login", 23, 12434));
        collector.recordRequest(new RequestInfo("login", 1223, 14234));
    
        try {
          Thread.sleep(100000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
    
  • 相关阅读:
    windows中dos命令指南
    HDU 2084 数塔 (dp)
    HDU 1176 免费馅饼 (dp)
    HDU 1004 Let the Balloon Rise (map)
    变态杀人狂 (数学)
    HDU 2717 Catch That Cow (深搜)
    HDU 1234 开门人和关门人 (模拟)
    HDU 1070 Milk (模拟)
    HDU 1175 连连看 (深搜+剪枝)
    HDU 1159 Common Subsequence (dp)
  • 原文地址:https://www.cnblogs.com/wod-Y/p/12885426.html
Copyright © 2011-2022 走看看