每日一句
任何事情,你想做就有方法,不想做就有借口,你有选择的自由,也有承担后果的义务。不要明天再努力,只有努力过完今天的人才有明天!
目录
前言
上篇文章简单的聊了一下netflix的多维度统计指标的模块netflix-statistics,当然这个功能是属于netflix-commons项目的一个模块,大家可以在github上去获取源码。基础类说完之后 我们就回到Ribbon 说一下Ribbon的一个非常基础的功能 服务器指标管理器。ServerStats
一个Demo
Server server = new Server("stonse", 80);
ServerStats stats = new ServerStats();
stats.initialize(server);
//缓冲区大小为200 默认是60000
//这里设置的缓存区是不是就和前面说的 DataBuffer的缓存区有关了?
stats.setBufferSize(200);
//这里的发布 是不是就和我们上一篇文章里面说的那个发布器有关了?
//由于默认值一分钟发布一次 这里设置3s搜集一次数据
stats.setPublishInterval(3000);
/**
* 在一个异步的任务中开启10个线程 不停的模拟请求
*/
CompletableFuture.runAsync(()->{
for (int i = 0; i < 10; i++) {
new Thread(()->{
while (true){
int responseTime = new Random().nextInt(2000);
stats.incrementActiveRequestsCount();
stats.incrementNumRequests();
stats.incrementOpenConnectionsCount();
try {
Thread.sleep(responseTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (responseTime > 1000){
stats.incrementSuccessiveConnectionFailureCount();
stats.addToFailureCount();
}
stats.noteResponseTime(responseTime);
stats.decrementActiveRequestsCount();
stats.decrementOpenConnectionsCount();
}
}).start();
}
}).thenRun(()->{
//在主线程中 再开启一个线程 来获取运行时的一些指标数据
executor.scheduleAtFixedRate(()->{
System.out.println("=================时间:"+stats.getResponseTimePercentileTime()+"=====================");
System.out.println("总请求数:" + stats.getTotalRequestsCount());
System.out.println("时间窗口内(5min)的请求数量:" + stats.getMeasuredRequestsCount()); //这个值5分钟之后才有值 时间窗口是5分钟
System.out.println("一个时间窗口(1s)内请求失败数量:" + stats.getFailureCount());
System.out.println("当前活跃请求数量:" + stats.getActiveRequestsCount());
System.out.println("打开的连接数量:" + stats.getOpenConnectionsCount());
System.out.println("平均响应时间:" + stats.getResponseTimeAvg());
System.out.println("最大响应时间:" + stats.getResponseTimeMax());
System.out.println("最小响应时间:" + stats.getResponseTimeMin());
}, 3,3,TimeUnit.SECONDS);
}
).thenRun(()->{
//这里hold住主线程
try {
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
这个demo的代码比较多。但是很清晰 为了结构分明 所以用了CompletableFuture 这个类是JUC里面的 大神Doug Lea编写的。 简化了异步编程的 并且提供了函数式编程的支持。
这里思路很清晰
runAsync里面是模拟请求,第一个thenRun里面监控打印 ,第二个thenRun里面是hold住主线程。
输出:
=================时间:Tue Nov 24 23:05:43 CST 2020=====================
总请求数:36
时间窗口内(5min)的请求数量:0
一个时间窗口(1s)内请求失败数量:6
当前活跃请求数量:10
打开的连接数量:10
平均响应时间:928.2307692307693
最大响应时间:1906.0
最小响应时间:76.0
=================时间:Tue Nov 24 23:05:46 CST 2020=====================
总请求数:65
时间窗口内(5min)的请求数量:0
一个时间窗口(1s)内请求失败数量:0
当前活跃请求数量:10
打开的连接数量:10
平均响应时间:921.5090909090909
最大响应时间:1916.0
最小响应时间:76.0
通过上面的小示例我们可以拿到一些运行过程中的指标数据。通过上一篇文章我们知道 之所以能拿到这些数据 其核心就是netflix-statistics。我们下面就来看看ServerStats源码是如何工作的。
源码
构造方法
public ServerStats() {
connectionFailureThreshold = new CachedDynamicIntProperty(
"niws.loadbalancer.default.connectionFailureCountThreshold", 3);
circuitTrippedTimeoutFactor = new CachedDynamicIntProperty(
"niws.loadbalancer.default.circuitTripTimeoutFactorSeconds", 10);
maxCircuitTrippedTimeout = new CachedDynamicIntProperty(
"niws.loadbalancer.default.circuitTripMaxTimeoutSeconds", 30);
}
- connectionFailureThreshold:链接失败阈值 默认为3 超过就熔断 默认值由niws.loadbalancer.default.connectionFailureCountThreshold来指定
- circuitTrippedTimeoutFactor:断路器超时因子 默认10s 默认值由niws.loadbalancer.default.circuitTripTimeoutFactorSeconds属性指定
- maxCircuitTrippedTimeout断路器最大超时秒数 默认30s 默认值由niws.loadbalancer.default.circuitTripMaxTimeoutSeconds来指定
构造方法初始化了以上的三个值。都是和断路器有关。
initialize方法
public void initialize(Server server) {
//MeasuredRate 表示一段时间内的统计
//serverFailureCounts 维护了在一段时间内的失败次数 默认为1s
serverFailureCounts = new MeasuredRate(failureCountSlidingWindowInterval);
//维护了一个时间窗口内的请求数量 5分钟
requestCountInWindow = new MeasuredRate(300000L);
//下面的就比较熟悉了 DataPublisher 数据发布器 和DataDistribution数据统计器
if (publisher == null) {
//实例化数据累加器 默认缓存 60000
dataDist = new DataDistribution(getBufferSize(), PERCENTS);
//一分钟发布一次
publisher = new DataPublisher(dataDist, getPublishIntervalMillis());
publisher.start();
}
this.server = server;
}
- serverFailureCounts 存储了一段时间内的失败次数 默认1s
- requestCountInWindow 存储一段时间内的请求数量 默认5分钟
- dataDist 初始化一个数据累加器 默认缓存是5w上一篇文章介绍过
- publisher 初始化一个数据发布器 默认为1分钟发布一次
初始化完这些值之后就可以获取指标值了!
ServerStat 属性
除了上面初始化的属性 再看看其他值得注意的属性!
//活跃请求超时时间600s
private static final DynamicIntProperty activeRequestsCountTimeout = DynamicPropertyFactory.getInstance().getIntProperty("niws.loadbalancer.serverStats.activeRequestsCount.effectiveWindowSeconds", 60 * 10);
//初始化计算中位数的数组 该数组里面的值是固定的 参考Percent枚举值
private static final double[] PERCENTS = makePercentValues();
//定时发布数据 默认1分钟一次
private DataPublisher publisher = null;
//仅仅需要持续累加数据 提供最大值最小值 平均值等所以是Distribution类型
private final Distribution responseTimeDist = new Distribution();
//当前的服务
Server server;
//连续请求异常数量 发生在Retry重试期间。在重试期间 但凡有一次成功了 就会把此参数设置为0.
@VisibleForTesting
AtomicInteger successiveConnectionFailureCount = new AtomicInteger(0);
//活跃请求的数量(正在请求的数量 它能反应server的负载 压力)
@VisibleForTesting
AtomicInteger activeRequestsCount = new AtomicInteger(0);
//最后一次失败的时间戳
private volatile long lastConnectionFailedTimestamp;
//activeRequestsCount 的值最后变化的时间戳
private volatile long lastActiveRequestsCountChangeTimestamp;
//断路器断开的总时长 连续失败大于等于3次 增加20-30秒
private AtomicLong totalCircuitBreakerBlackOutPeriod = new AtomicLong(0);
//最后访问时间戳
private volatile long lastAccessedTimestamp;
//第一次连接的时间戳 只会记录首次请求进来的时间
private volatile long firstConnectionTimestamp = 0;
可以看到除了初始化的几个值以外 其他的属性都很简单。
getActiveRequestsCount 获取活跃的请求数
public int getActiveRequestsCount(long currentTime) {
int count = activeRequestsCount.get();
if (count == 0) {
return 0;
} else if (currentTime - lastActiveRequestsCountChangeTimestamp > activeRequestsCountTimeout.get() * 1000 || count < 0) {
//注意这个条件分支 如果最后一个请求 的超时时间已过 那么久吧当前负载 强制归0
activeRequestsCount.set(0);
return 0;
} else {
return count;
}
}
getCircuitBreakerBlackoutPeriod 获取中断的时间值
private long getCircuitBreakerBlackoutPeriod() {
//获取一端时间内请求的失败数量
int failureCount = successiveConnectionFailureCount.get();
//获取连续失败阈值 可在配置文件配置 默认是 3
int threshold = connectionFailureThreshold.get();
//如果没有达到阈值 返回0
if (failureCount < threshold) {
return 0;
}
//如果连续失败大于19次 返回16 否则返回差值
int diff = (failureCount - threshold) > 16 ? 16 : (failureCount - threshold);
//通过上面计算出来的值 乘上超时因子默认是10 获得熔断时间
int blackOutSeconds = (1 << diff) * circuitTrippedTimeoutFactor.get();
//如果上面计算的时间大于 默认的断路器超时最大时间 强制赋值为最大超时 默认30s
//所以 在默认的情况下 连续失败 5次 就达到最大超时时间了 不管是5次还是100次都是熔断30s
//所以上面的表达式 (1 << diff) * circuitTrippedTimeoutFactor.get() 是否有问题?
if (blackOutSeconds > maxCircuitTrippedTimeout.get()) {
blackOutSeconds = maxCircuitTrippedTimeout.get();
}
return blackOutSeconds * 1000L;
}
先来清楚几个配置:
connectionFailureThreshold: 一段时间内失败的阈值 默认是3 可以理解为超过这个阈值就会熔断
circuitTrippedTimeoutFactor:超时因子 默认是10
maxCircuitTrippedTimeout:最长的超时时间 默认30s
我们再来看看上面的计算是否有问题:
例如当前server已连续失败5次 那么
int diff = (failureCount - threshold) > 16 ? 16 : (failureCount - threshold); 计算出来的值为 2
通过 表达式 (1 << diff) * circuitTrippedTimeoutFactor.get(); 计算出来的值是 40
那么最终40是要大于最大的超时时间30s的所有要返回30s。
大家看到这应该发现问题了。其实按照默认的设置 连续失败5次就会返回最高的值 它那个>大于16 是什么意思?如果是这样的话熔断的秒数只有两种可能 20s和30s。
所以我想这个结果并不是设计者要的。
好了 ServerStats我们就到这。内容是在)netflix-statistics基础之上。搞清楚了netflix-statistics 再来读这个类的源码还是很easy的!