日志开源组件(六)Adaptive Sampling 自适应采样

news2025/3/17 8:00:07

业务背景

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

拓展阅读

日志开源组件(一)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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/942959.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

navicat连接数据库的方法(易懂)

1.首页要先下载Navicat 官网下载即可 2.下载完点击进入 找到左上角的连接 3.点击选择MySQL... 4.点击进入开始连接数据库

Linux常用命令——declare命令

在线Linux命令查询工具 declare 声明或显示shell变量 补充说明 declare命令用于声明和显示已存在的shell变量。当不提供变量名参数时显示所有shell变量。declare命令若不带任何参数选项&#xff0c;则会显示所有shell变量及其值。declare的功能与typeset命令的功能是相同的…

XPloteCAD开发实录-第一阶段

在该解阶段,主要的工作内容: 1.完成了框架引擎的设计代码设计工作; 2.完成了矩阵库,线性,微分几何等第三方数学库的封装; 3.完成了XPloteFrameWork 等设计和代码,里面包含不限于AutoFac,AutoMap,Serilog等封装以及各种模块的工具化; 这是目前用这个框架搭建完成的操作界面:…

IDEA遇到 git pull 冲突的几种解决方法

1 忽略本地修改&#xff0c;强制拉取远程到本地 主要是项目中的文档目录&#xff0c;看的时候可能多了些标注&#xff0c;现在远程文档更新&#xff0c;本地的版本已无用&#xff0c;可以强拉 git fetch --all git reset --hard origin/dev git pull关于commit和pull的先后顺…

iTOP-STM32MP157开发板应用层和内核层传递数据

我们的应用层和内核层是不能直接进行数据传输的。我们要想进行数据传输&#xff0c;要借助下面的这两个函数。 static inline long copy_from_user(void *to, const void __user * from, unsigned long n) static inline long copy_to_user(void __user *to, const void *fro…

基于java Swing 和 mysql实现的购物管理系统(源码+数据库+说明文档+运行指导视频)

一、项目简介 本项目是一套基于java Swing 和 mysql实现的购物管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过…

他们朝我扔泥巴(scratch)

前言 纯~~~属~~~虚~~~构~~~&#xff08;同学看完短视频要我做&#xff0c;蟹蟹你&#xff09; 用scratch做的&#xff0c;幼稚得嘞(&#xffe3;_&#xffe3;|||)呵呵&#xff08;强颜欢笑&#xff09; 完成视频 视频试了好久&#xff0c;就是传不上来&#xff0c;私信我加我…

19 个最佳Three.JS 示例

推荐&#xff1a;使用 NSDT编辑器快速搭建3D应用场景 在浏览器中创建动画三个JS模型创建 3D 动画文本从 3D 图像创建 2D 模型制作 3D 模型动画添加 3D 效果创建 3D 游戏和交互式体验编程 3D 虚拟现实体验将颜色应用于 3D 几何体控制 3D 渲染性能与 3D 环境交互修改 3D 环境的照…

向量数据库(第 4 部分):分析权衡

在本系列的上一篇文章中&#xff0c;我们介绍了向量数据库中通常使用的不同类型的索引。然而&#xff0c;索引只是向量数据库中更大问题中的一小部分。回想一下&#xff0c;在第二部分中&#xff0c;我们描述了什么是向量数据库。为了区分目前市场上的各种向量数据库产品&#…

19.(地图工具篇)ArcMap合并与分割Shape

地图之家总目录&#xff08;订阅之前必须详细了解该博客&#xff09; 一&#xff1a;Arcgis合并shape文件 1.使用Merge工具 2.配置 3.等待合并完成 二&#xff1a;Arcgis分割shape文件 1&#xff1a;选择split by attribute工具 2:配置&#xff08;根据COUNTYNAME分割…

看这里,iOS备忘录恢复的3个简单高效方法

想问iPhone用户们一个问题&#xff1a;你们知道手机上哪个软件最了解自己吗&#xff1f;答案是&#xff1a;ios备忘录。为什么说是备忘录呢&#xff1f;因为备忘录记录了大家各种软件的账号密码、日常计划、朋友的生日&#xff0c;甚至是非常重要的银行卡密码。 由此看来&…

QT6添加第三方模块的cmake配置和qmake配置(以串口模块qserialport为例)

参考1&#xff0c;参考2 qmake建立的工程&#xff1a;https://mar-sky.blog.csdn.net/article/details/132487461&#xff0c; 模块安装&#xff1a;https://mar-sky.blog.csdn.net/article/details/132483421 简单的使用介绍 在qmake创建的项目工程中&#xff0c;使用外部模…

国标视频融合云平台EasyCVR视频汇聚平台的应用场景及其功能说明

一、平台简介 EasyCVR国标视频融合云平台是一款基于端-边-云一体化架构的视频融合AI智能分析网关平台。EasyCVR平台支持视频汇聚、融合管理&#xff0c;兼容多类型设备、多协议接入。其提供的视频功能包括&#xff1a;视频监控、无插件直播录像、云存储、检索回放、智能告警、…

信息化发展9

智能制造 智能制造&#xff08; Intelligent Manufacturing , IM&#xff09; 是基于新一代信息通信技术与先进制造技术深度融合&#xff0c; 贯穿于设计、生产、管理、服务等制造活动的各个环节&#xff0c; 具有自感知、自学习、自决策、自执行、自适应等功能的新型生产方式…

Spring Cloud Nacos 和 Eureka区别,包含实战代码

目录 一、Spring Cloud Eureka详解二、Spring Cloud Nacos详解三、Spring Cloud Nacos和Eureka区别 Spring Cloud Nacos 和 Spring Cloud Eureka 都是 Spring Cloud 微服务框架中的服务注册和发现组件&#xff0c;用于帮助开发者轻松地构建和管理微服务应用。它们之间的主要区别…

电脑中毒的症状有哪些?电脑中毒数据没了能恢复吗

电脑中病毒想要恢复数据&#xff0c;怎么办&#xff1f;&#xff1f;急&#xff01;&#xff01;&#xff01;电脑D盘里有很多高三时候的照片&#xff0c;很珍贵&#xff01;&#xff01;求助啊&#xff01;&#xff01;? ——互联网的不断发展和普及使得电脑已经成为我们生活…

解决PPPoE连接与防火墙冲突问题

在使用PPPoE&#xff08;Point-to-Point Protocol over Ethernet&#xff09;连接时&#xff0c;有时可能会遇到与防火墙之间的冲突问题。本文将为您提供实用指南&#xff0c;帮助您解决这一常见问题。 1. 确认阻止网络访问的原因 首先要确定是否存在由于防火墙设置而造成无法…

长胜证券:券商股掉链子?北向资金丢盔弃甲!恒大瞬间成仙

A股在多重利好政策的刺激下高开&#xff0c;北向资金却一路出逃。 今天早盘&#xff0c;A股三大指数大幅高开&#xff0c;沪指开盘暴涨近5%&#xff0c;券商股更是直线拉升。不过&#xff0c;开盘后不久&#xff0c;券商股纷繁翻开涨停板。到收盘&#xff0c;沪指涨1.13%&#…

常见前端面试之VUE面试题汇总十二

35. defineProperty 和 proxy 的区别 Vue 在 实 例 初 始 化 时 遍 历 data 中 的 所 有 属 性 &#xff0c; 并 使 用 Object.defineProperty 把这些属性全部转为 getter/setter。这样 当追踪数据发生变化时&#xff0c;setter 会被自动调用。 Object.defineProperty 是 ES5…

嵌入式学习笔记(2)ARM的37个寄存器详解

ARM中寄存器包括SFR和37个通用寄存器&#xff0c;通用寄存器是CPU(运算器控制器通用寄存器)的组成部分。37个通用寄存器是搭配7种工作模式来学习的。因为每种工作模式下&#xff0c;可见的通用寄存器都不相同&#xff0c;每种模式下最多只能看到18个寄存器&#xff0c;部分寄存…