日志开源组件(六)Adaptive Sampling 自适应采样
业务背景
有时候日志的信息比较多,怎么样才可以让系统做到自适应采样呢?
拓展阅读
日志开源组件(一)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
日志开源组件(六)Adaptive Sampling 自适应采样的更多相关文章
- log4net--不可多得的开源日志记录组件
log4net--不可多得的开源日志记录组件 1 前奏 一直在用log4net日志工具,却没时间写个日志给大家分享一下这个工具,趁最近比较空些,好好分享一下这个工具. 2 说明 Log4net介绍就不 ...
- CVPR2020:基于自适应采样的非局部神经网络鲁棒点云处理(PointASNL)
CVPR2020:基于自适应采样的非局部神经网络鲁棒点云处理(PointASNL) PointASNL: Robust Point Clouds Processing Using Nonlocal N ...
- 日志记录组件[Log4net]详细介绍
转载:http://www.cnblogs.com/liwei6797/archive/2007/04/27/729679.html 因为工作中有要用到Log记录,找到一篇不错的文章,就转了过来. 一 ...
- C#Log4net日志记录组件的使用
一.Log4Net介绍 Log4net是基于.NET开发的一款非常著名的记录日志开源组件.它通过一套XML配置的日志引擎,将日志分不同的等级,分别是:FATAL . ERROR. WARN. INFO ...
- 【广州.NET社区推荐】.NETCore 平台上的日志集成组件 TomatoLog
TomatoLog简介 TomatoLog 是一套在 .NETCore 平台上最简单易用的日志集成组件,具有高度灵活的使用方式,完全可定义配置的可扩展性,使用异步写入,业务完全解耦,客户端的一键安装. ...
- 微信开源组件WCDB漫谈及Demo
代码地址如下:http://www.demodashi.com/demo/12422.html 前言 移动端的数据库选型一直是一个难题,直到前段时间看到了WeMobileDev(微信前端团队)放出了第 ...
- 如何利用阿里视频云开源组件,快速自定义你的H5播放器?
摘要: Aliplayer希望提供一种方便.简单.灵活的机制,让客户能够扩展播放器的功能,并且Aliplayer提供一些组件的基本实现,用户可以基于这些开源的组件实现个性化功能,比如自定义UI和自己A ...
- .NET Core开源组件:后台任务利器之Hangfire 转载 https://www.cnblogs.com/chenug/p/6655636.html
.NET Core开源组件:后台任务利器之Hangfire 一.简述 Hangfire作为一款高人气且容易上手的分布式后台执行服务,支持多种数据库.在.net core的环境中,由Core自带的D ...
- react native 的图表开源组件react-native-chart-android
react-native-chart-android是一个图表开源组件,使用方法可以去这里 由于需要在数据上加上触摸事件,而github上没有说明看源码找了半天才找到下面的解决方法,特此记录一下: 在 ...
- .net 开源组件
文章转自:http://www.cnblogs.com/asxinyu/p/dotnet_opensource_project_3.html 在前2篇文章这些.NET开源项目你知道吗?让.NET开 ...
随机推荐
- 蓝桥杯真题 平面切分(Set自定义去重)
题目详情 资源限制 内存限制:256.0MB C/C++时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s 问题描述 平面上有 N 条直线,其中第 i 条直线是 y=Ai⋅ ...
- 基于Jmeter+ant+Jenkins+钉钉机器人群通知的接口自动化测试
前言 搭建jmeter+ant+jenkins环境有些前提条件,那就是要先配置好java环境,本地java环境至少是JDK8及以上版本,最好是JAVA11或者JAVA17等较高的java环境,像jen ...
- 尤雨溪创立 Vue.js 的心路历程纪录片
Show More 本文分享自微信公众号 - 生信科技爱好者(bioitee).如有侵权,请联系 support@oschina.cn 删除.本文参与"OSC源创计划",欢迎正在阅 ...
- Hive执行计划之一文读懂Hive执行计划
目录 概述 1.hive执行计划的查看 2.学会查看Hive执行计划的基本信息 3.执行计划步骤操作过程 4.explain extended 概述 Hive的执行计划描述了一个hiveSQL语句的具 ...
- const 使用
宏定义与const的区别?(概念题是最容易丢分)1. 发生时机不一样: 宏定义发生在预处理时,const关键字发生编译时2. 宏定义仅仅只做了字符串的替换,没有类型检查; const关键字有类型检查, ...
- C++面试八股文:std::vector了解吗?
某日二师兄参加XXX科技公司的C++工程师开发岗位第23面: 面试官:vector了解吗? 二师兄:嗯,用过. 面试官:那你知道vector底层是如何实现的吗? 二师兄:vector底层使用动态数组来 ...
- 从零开始整SpringBoot-搭建一个企业级项目
创建Springboot项目 在企业里的项目多数是分Modules的,更多是通过物理方式隔离层之间的职责. 首先按照原有新建SpringBoot项目创建,填好相关信息后,选择Java 8其他不用选. ...
- Pychrm自定义代码块
Pycharm -> Perferences (command + ,)
- 2023-07-17:给定一个数组arr,长度为n, 再给定一个数字k,表示一定要将arr划分成k个集合, 每个数字只能进一个集合。 返回每个集合内部的平均值都累加起来最小的值。 平均值向下取整。 1
2023-07-17:给定一个数组arr,长度为n, 再给定一个数字k,表示一定要将arr划分成k个集合, 每个数字只能进一个集合. 返回每个集合内部的平均值都累加起来最小的值. 平均值向下取整. 1 ...
- Avalonia 使用EFCore调用SQLite实现Singleton全局注册
Avalonia 使用EFCore调用SQLite实现Singleton全局注册 本篇博客是我的开源项目TerraMours.Chat.Ava的更新的记录分享,本次更新使用EntityFrameWor ...