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();
        }
      }
    }
    
  • 相关阅读:
    言多必失失在哪?
    C/C++四种方法实现加法操作_艾孜尔江撰
    VS Code添加到右键菜单_艾孜尔江撰
    网页端UVC相机测试_艾孜尔江撰
    Python爬虫示例
    Java制作软光栅化渲染器_艾孜尔江撰
    指针常量与常量指针
    C/C++中Main之后执行的函数_艾孜尔江撰
    Token机制 ——JSON Web Token(JWT)的详解
    Excel-实现隔行设置背景色
  • 原文地址:https://www.cnblogs.com/wod-Y/p/12885426.html
Copyright © 2011-2022 走看看