共计 4941 个字符,预计需要花费 13 分钟才能阅读完成。
业务背景
有时候日志的信息比拟多,怎么样才能够让零碎做到自适应采样呢?
拓展浏览
日志开源组件(一)java 注解联合 spring aop 实现主动输入日志
日志开源组件(二)java 注解联合 spring aop 实现日志 traceId 惟一标识
日志开源组件(三)java 注解联合 spring aop 主动输入日志新增拦截器与过滤器
日志开源组件(四)如何动静批改 spring aop 切面信息?让主动日志输入框架更好用
日志开源组件(五)如何将 dubbo filter 拦截器原理使用到日志拦截器中?
自适应采样
是什么?
系统生成的日志能够蕴含大量信息,包含谬误、正告、性能指标等,但在理论利用中,解决和剖析所有的日志数据可能会对系统性能和资源产生累赘。
自适应采样在这种状况下发挥作用,它可能依据以后零碎状态和日志信息的重要性,智能地决定哪些日志须要被采样记录,从而无效地治理和剖析日志数据。
采样的必要性
日志采样系统会给业务零碎额定减少耗费,很多零碎在接入的时候会比拟排挤。
给他们一个百分比的抉择,或者是一个不错的开始,而后依据理论须要抉择适合的比例。
自适应采样是一个对用户通明,同时又十分优雅的计划。
如何通过 java 实现自适应采样?
接口定义
首先咱们定义一个接口,返回 boolean。
依据是否为 true 来决定是否输入日志。
/**
* 采样条件
* @author binbin.hou
* @since 0.5.0
*/
public interface IAutoLogSampleCondition {
/**
* 条件
*
* @param context 上下文
* @return 后果
* @since 0.5.0
*/
boolean sampleCondition(IAutoLogContext context);
}
百分比概率采样
咱们先实现一个简略的概率采样。
0-100 的值,让用户指定,依照百分比决定是否采样。
public class InnerRandomUtil {
/**
* 1. 计算一个 1-100 的随机数 randomVal
* 2. targetRatePercent 值越大,则返回 true 的概率越高
* @param targetRatePercent 指标百分比
* @return 后果
*/
public static boolean randomRateCondition(int targetRatePercent) {if(targetRatePercent <= 0) {return false;}
if(targetRatePercent >= 100) {return true;}
// 随机
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
int value = threadLocalRandom.nextInt(1, 100);
// 随机概率
return targetRatePercent >= value;
}
}
实现起来也非常简单,间接一个随机数,而后比拟大小即可。
自适应采样
思路
咱们计算一下以后日志的 QPS,让输入的概率和 QPS 称正比。
/**
* 自适应采样
*
* 1. 初始化采样率为 100%,全副采样
*
* 2. QPS 如果越来越高,那么采样率应该越来越低。这样防止 cpu 等资源的损耗。最低为 1%
* 如果 QPS 越来越低,采样率应该越来越高。减少样本,最高为 100%
*
* 3. QPS 如何计算问题
*
* 间接设置大小为 100 的队列,每一次在外面放入工夫戳。* 当大小等于 100 的时候,计算首尾的时间差,currentQps = 100 / (endTime - startTime) * 1000
*
* 触发 rate 从新计算。*
* 3.1 rate 计算逻辑
*
* 这里咱们存储一下 preRate = 100, preQPS = ?
*
* newRate = (preQps / currentQps) * rate
*
* 范畴限度:* newRate = Math.min(100, newRate);
* newRate = Math.max(1, newRate);
*
* 3.2 工夫队列的清空
*
* 更新完 rate 之后,对应的队列能够清空?*
* 如果额定应用一个 count,如同也能够。* 能够调整为 atomicLong 的计算器,和 preTime。*
代码实现
public class AutoLogSampleConditionAdaptive implements IAutoLogSampleCondition {private static final AutoLogSampleConditionAdaptive INSTANCE = new AutoLogSampleConditionAdaptive();
/**
* 单例的形式获取实例
* @return 后果
*/
public static AutoLogSampleConditionAdaptive getInstance() {return INSTANCE;}
/**
* 次数大小限度,即接管到多少次申请更新一次 adaptive 计算
*
* TODO: 这个如何能够让用户能够自定义呢?后续思考配置从默认的配置文件中读取。*/
private static final int COUNT_LIMIT = 1000;
/**
* 自适应比率,初始化为 100. 全副采集
*/
private volatile int adaptiveRate = 100;
/**
* 上一次的 QPS
*
* TODO: 这个如何能够让用户能够自定义呢?后续思考配置从默认的配置文件中读取。*/
private volatile double preQps = 100.0;
/**
* 上一次的工夫
*/
private volatile long preTime;
/**
* 总数,申请计数器
*/
private final AtomicInteger counter;
public AutoLogSampleConditionAdaptive() {preTime = System.currentTimeMillis();
counter = new AtomicInteger(0);
}
@Override
public boolean sampleCondition(IAutoLogContext context) {int count = counter.incrementAndGet();
// 触发一次从新计算
if(count >= COUNT_LIMIT) {updateAdaptiveRate();
}
// 间接计算是否满足
return InnerRandomUtil.randomRateCondition(adaptiveRate);
}
}
每次累加次数超过限定次数之后,咱们就更新一下对应的日志概率。
最初的概率计算和下面的百分比相似,不再赘述。
/**
* 更新自适应的概率
*
* 100 计算一次,其实还好。理论应该能够适当调大这个阈值,自身不会常常变动的货色。*/
private synchronized void updateAdaptiveRate() {
// 耗费的毫秒数
long costTimeMs = System.currentTimeMillis() - preTime;
//qps 的计算,时间差是毫秒。所以次数须要乘以 1000
double currentQps = COUNT_LIMIT*1000.0 / costTimeMs;
// preRate * preQps = currentRate * currentQps; 保障采样平衡,服务器压力平衡
// currentRate = (preRate * preQps) / currentQps;
// 更新比率
int newRate = 100;
if(currentQps > 0) {newRate = (int) ((adaptiveRate * preQps) / currentQps);
newRate = Math.min(100, newRate);
newRate = Math.max(1, newRate);
}
// 更新 rate
adaptiveRate = newRate;
// 更新 QPS
preQps = currentQps;
// 更新上一次的工夫内戳
preTime = System.currentTimeMillis();
// 归零
counter.set(0);
}
自适应代码 - 改进
问题
下面的自适应算法个别状况下都能够运行的很好。
然而有一种状况会不太好,那就是流量从高峰期到低峰期。
比方凌晨 11 点是申请高峰期,咱们的输入日志概率很低。深夜之后申请数会很少,想达到累计值就会很慢,这个时间段就会导致日志输入很少。
如何解决这个问题呢?
思路
咱们能够通过固定工夫窗口的形式,来定时调整流量概率。
java 实现
咱们初始化一个定时工作,1min 定时更新一次。
public class AutoLogSampleConditionAdaptiveSchedule implements IAutoLogSampleCondition {private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
/**
* 工夫分钟距离
*/
private static final int TIME_INTERVAL_MINUTES = 5;
/**
* 自适应比率,初始化为 100. 全副采集
*/
private volatile int adaptiveRate = 100;
/**
* 上一次的总数
*
* TODO: 这个如何能够让用户能够自定义呢?后续思考配置从默认的配置文件中读取。*/
private volatile long preCount;
/**
* 总数,申请计数器
*/
private final AtomicLong counter;
public AutoLogSampleConditionAdaptiveSchedule() {counter = new AtomicLong(0);
preCount = TIME_INTERVAL_MINUTES * 60 * 100;
//1. 1min 后开始执行
//2. 两头默认 5 分钟更新一次
EXECUTOR_SERVICE.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {updateAdaptiveRate();
}
}, 60, TIME_INTERVAL_MINUTES * 60, TimeUnit.SECONDS);
}
@Override
public boolean sampleCondition(IAutoLogContext context) {counter.incrementAndGet();
// 间接计算是否满足
return InnerRandomUtil.randomRateCondition(adaptiveRate);
}
}
其中更新概率的逻辑和下面相似:
/**
* 更新自适应的概率
*
* QPS = count / time_interval
*
* 其中工夫维度是固定的,所以能够不必思考工夫。*/
private synchronized void updateAdaptiveRate() {
// preRate * preCount = currentRate * currentCount; 保障采样平衡,服务器压力平衡
// currentRate = (preRate * preCount) / currentCount;
// 更新比率
long currentCount = counter.get();
int newRate = 100;
if(currentCount != 0) {newRate = (int) ((adaptiveRate * preCount) / currentCount);
newRate = Math.min(100, newRate);
newRate = Math.max(1, newRate);
}
// 更新自适应频率
adaptiveRate = newRate;
// 更新 QPS
preCount = currentCount;
// 归零
counter.set(0);
}
小结
让零碎自动化分配资源,是一种十分好的思路,能够让资源利用最大化。
实现起来也不是很艰难,理论要依据咱们的业务量进行察看和调整。
开源地址
auto-log https://github.com/houbb/auto-log