其他分享
首页 > 其他分享> > 第二部分:实战二

第二部分:实战二

作者:互联网

第二部分:实战二

实战二(上)

项目背景

需求分析

功能性需求分析

image

非功能性需求分析

框架设计

image

解决一个简单应用场景的性能计数器:统计用户注册、登录这两个接口的响应时间的最大值和平均值、接口调用次数,并且将统计结果以 JSON 的格式输出到命令行中。

应用场景的代码,具体如下所示:

// 应用场景:统计下面两个接口 (注册和登录)的响应时间和访问次数
public class UserController {
	public void register(UserVo user) {
		//...
	}
	public UserVo login(String telephone, String password) {
		//...
	}
}

最小原型实现如下所示:recordResponseTime() 和 recordTimestamp() 两个函数分别用来记录接口请求的响应时间和访问时间,startRepeatedReport() 函数以指定的频率统计数据并输出结果

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.google.gson.Gson;

public class Metrics {
        // Map 的 key 是接口名称,value 对应接口请求的响应时间或时间戳;
        private Map<String, List<Double>> responseTimes = new HashMap<>();
        private Map<String, List<Double>> timestamps = new HashMap<>();
        private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

        public void recordResponseTime(String apiName, double responseTime) {
            responseTimes.putIfAbsent(apiName, new ArrayList<>());
            responseTimes.get(apiName).add(responseTime);
        }

        public void recordTimestamp(String apiName, double timestamp) {
            timestamps.putIfAbsent(apiName, new ArrayList<>());
            timestamps.get(apiName).add(timestamp);
        }

        public void startRepeatedReport(long period, TimeUnit unit){
            executor.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    Gson gson = new Gson();
                    Map<String, Map<String, Double>> stats = new HashMap<>();
                    for (Map.Entry<String, List<Double>> entry : responseTimes.entrySet()) {
                        String apiName = entry.getKey();
                        List<Double> apiRespTimes = entry.getValue();
                        stats.putIfAbsent(apiName, new HashMap<>());
                        stats.get(apiName).put("max", max(apiRespTimes));
                        stats.get(apiName).put("avg", avg(apiRespTimes));
                    }
                    for (Map.Entry<String, List<Double>> entry : timestamps.entrySet()) {
                        String apiName = entry.getKey();
                        List<Double> apiTimestamps = entry.getValue();
                        stats.putIfAbsent(apiName, new HashMap<>());
                        stats.get(apiName).put("count", (double)apiTimestamps.size());
                    }
                    System.out.println(gson.toJson(stats));

                }
            }, 0, period, unit);
        }

    private double max(List<Double> dataset) {
        // 省略代码实现
        return (Double)null;
        
    }
    private double avg(List<Double> dataset) {
        // 省略代码实现
        return (Double)null;
    }

}

如何用它来统计注册、登录接口的响应时间和访问次数,具体的代码如下所示:

// 应用场景:统计下面两个接口 (注册和登录)的响应时间和访问次数
import java.util.concurrent.TimeUnit;

public class UserController {
    private Metrics metrics = new Metrics();

    public UserController() {
        metrics.startRepeatedReport(60, TimeUnit.SECONDS);
    }

    public void register(UserVo user) {
        long startTimestamp = System.currentTimeMillis();
        metrics.recordTimestamp("regsiter", startTimestamp);
        //...
        long respTime = System.currentTimeMillis() - startTimestamp;
        metrics.recordResponseTime("register", respTime);
    }

    public UserVo login(String telephone, String password) {
        long startTimestamp = System.currentTimeMillis();
        metrics.recordTimestamp("login", startTimestamp);
        //...
        long respTime = System.currentTimeMillis() - startTimestamp;
        metrics.recordResponseTime("login", respTime);
        return (UserVo) null;
    }
}

实战二(下)

小步快跑、逐步迭代

面向对象设计与实现

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

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

MetricsCollector 代码实现:

import org.apache.commons.lang3.StringUtils;

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);
    }
}

RequestInfo 代码实现:

public class RequestInfo {
    private String apiName;
    private double responseTime;
    private long timestamp;

    public RequestInfo(String apiName, int responseTime, int timestamp) {
        this.apiName = apiName;
        this.responseTime = responseTime;
        this.timestamp = timestamp;
    }
    //... 省略 constructor/getter/setter 方法...

    public String getApiName() {
        return apiName;
    }

    public void setApiName(String apiName) {
        this.apiName = apiName;
    }

    public double getResponseTime() {
        return responseTime;
    }

    public void setResponseTime(double responseTime) {
        this.responseTime = responseTime;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }
}

MetricsStorage 代码实现:

import java.util.List;
import java.util.Map;

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);
}

RedisMetricsStorage 代码实现:

import java.util.List;
import java.util.Map;

public class RedisMetricsStorage implements MetricsStorage{

    @Override
    public void saveRequestInfo(RequestInfo requestInfo) {

    }

    @Override
    public List<RequestInfo> getRequestInfos(String apiName, long startTimeInMillis, long endTimeInMillis) {
        return null;
    }

    @Override
    public Map<String, List<RequestInfo>> getRequestInfos(long startTimeInMillis, long endTimeInMillis) {
        return null;
    }
}

Aggregator 代码实现:

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

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;
    }

}

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 方法...

    public double getMaxResponseTime() {
        return maxResponseTime;
    }

    public void setMaxResponseTime(double maxResponseTime) {
        this.maxResponseTime = maxResponseTime;
    }

    public double getMinResponseTime() {
        return minResponseTime;
    }

    public void setMinResponseTime(double minResponseTime) {
        this.minResponseTime = minResponseTime;
    }

    public double getAvgResponseTime() {
        return avgResponseTime;
    }

    public void setAvgResponseTime(double avgResponseTime) {
        this.avgResponseTime = avgResponseTime;
    }

    public double getP999ResponseTime() {
        return p999ResponseTime;
    }

    public void setP999ResponseTime(double p999ResponseTime) {
        this.p999ResponseTime = p999ResponseTime;
    }

    public double getP99ResponseTime() {
        return p99ResponseTime;
    }

    public void setP99ResponseTime(double p99ResponseTime) {
        this.p99ResponseTime = p99ResponseTime;
    }

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }

    public long getTps() {
        return tps;
    }

    public void setTps(long tps) {
        this.tps = tps;
    }


}

ConsoleReporter 代码实现:

import com.google.gson.Gson;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

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);
    }
}

EmailReporter 代码实现:

import java.util.*;

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);
    }
}

Demo 类实现:

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();
        }
    }

}

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

Review 设计与实现

标签:实战,double,第二,long,void,apiName,new,部分,public
来源: https://www.cnblogs.com/sleepday/p/15367064.html