设计模式简记-实战二:如何实现一个支持各种统计规则的性能计数器?
Posted 杨海星
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式简记-实战二:如何实现一个支持各种统计规则的性能计数器?相关的知识,希望对你有一定的参考价值。
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();
}
}
}
以上是关于设计模式简记-实战二:如何实现一个支持各种统计规则的性能计数器?的主要内容,如果未能解决你的问题,请参考以下文章