业务背景
有时候日志的信息比拟多,怎么样才能够让零碎做到自适应采样呢?
拓展浏览
日志开源组件(一)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
发表回复