业务背景

有时候日志的信息比较多,怎么样才可以让系统做到自适应采样呢?

拓展阅读

日志开源组件(一)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 自适应采样的更多相关文章

  1. log4net--不可多得的开源日志记录组件

    log4net--不可多得的开源日志记录组件 1 前奏 一直在用log4net日志工具,却没时间写个日志给大家分享一下这个工具,趁最近比较空些,好好分享一下这个工具. 2 说明 Log4net介绍就不 ...

  2. CVPR2020:基于自适应采样的非局部神经网络鲁棒点云处理(PointASNL)

    CVPR2020:基于自适应采样的非局部神经网络鲁棒点云处理(PointASNL) PointASNL: Robust Point Clouds Processing Using Nonlocal N ...

  3. 日志记录组件[Log4net]详细介绍

    转载:http://www.cnblogs.com/liwei6797/archive/2007/04/27/729679.html 因为工作中有要用到Log记录,找到一篇不错的文章,就转了过来. 一 ...

  4. C#Log4net日志记录组件的使用

    一.Log4Net介绍 Log4net是基于.NET开发的一款非常著名的记录日志开源组件.它通过一套XML配置的日志引擎,将日志分不同的等级,分别是:FATAL . ERROR. WARN. INFO ...

  5. 【广州.NET社区推荐】.NETCore 平台上的日志集成组件 TomatoLog

    TomatoLog简介 TomatoLog 是一套在 .NETCore 平台上最简单易用的日志集成组件,具有高度灵活的使用方式,完全可定义配置的可扩展性,使用异步写入,业务完全解耦,客户端的一键安装. ...

  6. 微信开源组件WCDB漫谈及Demo

    代码地址如下:http://www.demodashi.com/demo/12422.html 前言 移动端的数据库选型一直是一个难题,直到前段时间看到了WeMobileDev(微信前端团队)放出了第 ...

  7. 如何利用阿里视频云开源组件,快速自定义你的H5播放器?

    摘要: Aliplayer希望提供一种方便.简单.灵活的机制,让客户能够扩展播放器的功能,并且Aliplayer提供一些组件的基本实现,用户可以基于这些开源的组件实现个性化功能,比如自定义UI和自己A ...

  8. .NET Core开源组件:后台任务利器之Hangfire 转载 https://www.cnblogs.com/chenug/p/6655636.html

    .NET Core开源组件:后台任务利器之Hangfire   一.简述 Hangfire作为一款高人气且容易上手的分布式后台执行服务,支持多种数据库.在.net core的环境中,由Core自带的D ...

  9. react native 的图表开源组件react-native-chart-android

    react-native-chart-android是一个图表开源组件,使用方法可以去这里 由于需要在数据上加上触摸事件,而github上没有说明看源码找了半天才找到下面的解决方法,特此记录一下: 在 ...

  10. .net 开源组件

    文章转自:http://www.cnblogs.com/asxinyu/p/dotnet_opensource_project_3.html   在前2篇文章这些.NET开源项目你知道吗?让.NET开 ...

随机推荐

  1. AcWing 1024. 装箱问题

    有一个箱子容量为 V,同时有 n 个物品,每个物品有一个体积(正整数). 要求 n 个物品中,任取若干个装入箱内,使箱子的剩余空间为最小. 输入格式 第一行是一个整数 V,表示箱子容量. 第二行是一个 ...

  2. 小H分糖果

    7-5 小H分糖果 (20 分) 小H来到一个小学分糖果,小学生们很听话,站成一排等着分糖果,小H将根据每个人的上次考试分数给一定的糖果,规则如下. 每个人都有自己分数ai​,代表上次考试成绩. 每个 ...

  3. Windows 11 和 Rocky 9 Linux 平台 MySQL 8.0.33 简易安装教程

    目录 Windows 平台安装 MySQL Linux 平台 Rocky 9 安装 MySQL binary package rpm package yum 源 source package Wind ...

  4. CIO视角|平台工程带来的优势与机遇

    在当今高速发展的技术环境中,企业越来越依赖技术作为创新和竞争优势的战略驱动力.首席信息官(CIO)在企业中负责监督信息和计算机技术的管理和实施,以交付预期的业务成果.在技术是业务核心的公司中,CIO ...

  5. Abstract Factory Pattern 抽象工厂模式简介与 C# 示例【创建型】【设计模式来了】

    〇.简介 1.什么是抽象工厂模式? 一句话解释:   通过对抽象类和抽象工厂的一组实现,独立出一系列新的操作,客户端无需了解其逻辑直接访问. 抽象工厂模式(Abstract Factory Patte ...

  6. 今天在内部 Galaxy 分析平台操作探针引物设计小工具程序,调用 Ensembl API 获取相关序列和信息时,发现官网 MySQL server 异常,报告问题后当天晚上就收到了回复,并且修......

    本文分享自微信公众号 - 生信科技爱好者(bioitee).如有侵权,请联系 support@oschina.cn 删除.本文参与"OSC源创计划",欢迎正在阅读的你也加入,一起分 ...

  7. unity添加Mysql的dll以及发布的问题

    最近在做一个unity项目中,要读取数据库,还是MySql的数据库.遇到了很多问题,写出来供大家参考一下. 关于unity引用第三方的Mysql.data.dll的问题: 这个地方有一个难点,正常的C ...

  8. @Inherited元注解的使用

    @Inherited注解标记其他的注解用于指明标记的注解是可以被自动继承的. 注意:此注解只对注解标记的超类有效,对接口是无效的. 示例: 先声明两个用@Inherited标记的注解,@Name和@A ...

  9. 分享6个SQL小技巧

    原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,非公众号转载保留此声明. 简介 经常有小哥发出疑问,SQL还能这么写?我经常笑着回应,SQL确实可以这么写.其实SQL学起来简单,用起来也 ...

  10. 第四章 VIVIM编辑器

    1. 是什么 ‍ VI 是 Unix 操作系统和类 Unix 操作系统中最通用的文本编辑器. ‍ VIM 编辑器是从 VI 发展出来的一个性能更强大的文本编辑器. ‍ 2. 一般模式 ‍ 以 vi/v ...