经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
日志开源组件(六)Adaptive Sampling 自适应采样
来源:cnblogs  作者:老马啸西风  时间:2023/8/29 8:46:34  对本文有异议

业务背景

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

拓展阅读

日志开源组件(一)java 注解结合 spring aop 实现自动输出日志

日志开源组件(二)java 注解结合 spring aop 实现日志traceId唯一标识

日志开源组件(三)java 注解结合 spring aop 自动输出日志新增拦截器与过滤器

日志开源组件(四)如何动态修改 spring aop 切面信息?让自动日志输出框架更好用

日志开源组件(五)如何将 dubbo filter 拦截器原理运用到日志拦截器中?

自适应采样

是什么?

系统生成的日志可以包含大量信息,包括错误、警告、性能指标等,但在实际应用中,处理和分析所有的日志数据可能会对系统性能和资源产生负担。

自适应采样在这种情况下发挥作用,它能够根据当前系统状态和日志信息的重要性,智能地决定哪些日志需要被采样记录,从而有效地管理和分析日志数据。

采样的必要性

日志采样系统会给业务系统额外增加消耗,很多系统在接入的时候会比较排斥。

给他们一个百分比的选择,或许是一个不错的开始,然后根据实际需要选择合适的比例。

自适应采样是一个对用户透明,同时又非常优雅的方案。

自适应

如何通过 java 实现自适应采样?

接口定义

首先我们定义一个接口,返回 boolean。

根据是否为 true 来决定是否输出日志。

  1. /**
  2. * 采样条件
  3. * @author binbin.hou
  4. * @since 0.5.0
  5. */
  6. public interface IAutoLogSampleCondition {
  7. /**
  8. * 条件
  9. *
  10. * @param context 上下文
  11. * @return 结果
  12. * @since 0.5.0
  13. */
  14. boolean sampleCondition(IAutoLogContext context);
  15. }

百分比概率采样

我们先实现一个简单的概率采样。

0-100 的值,让用户指定,按照百分比决定是否采样。

  1. public class InnerRandomUtil {
  2. /**
  3. * 1. 计算一个 1-100 的随机数 randomVal
  4. * 2. targetRatePercent 值越大,则返回 true 的概率越高
  5. * @param targetRatePercent 目标百分比
  6. * @return 结果
  7. */
  8. public static boolean randomRateCondition(int targetRatePercent) {
  9. if(targetRatePercent <= 0) {
  10. return false;
  11. }
  12. if(targetRatePercent >= 100) {
  13. return true;
  14. }
  15. // 随机
  16. ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
  17. int value = threadLocalRandom.nextInt(1, 100);
  18. // 随机概率
  19. return targetRatePercent >= value;
  20. }
  21. }

实现起来也非常简单,直接一个随机数,然后比较大小即可。

自适应采样

思路

我们计算一下当前日志的 QPS,让输出的概率和 QPS 称反比。

  1. /**
  2. * 自适应采样
  3. *
  4. * 1. 初始化采样率为 100%,全部采样
  5. *
  6. * 2. QPS 如果越来越高,那么采样率应该越来越低。这样避免 cpu 等资源的损耗。最低为 1%
  7. * 如果 QPS 越来越低,采样率应该越来越高。增加样本,最高为 100%
  8. *
  9. * 3. QPS 如何计算问题
  10. *
  11. * 直接设置大小为 100 的队列,每一次在里面放入时间戳。
  12. * 当大小等于 100 的时候,计算首尾的时间差,currentQps = 100 / (endTime - startTime) * 1000
  13. *
  14. * 触发 rate 重新计算。
  15. *
  16. * 3.1 rate 计算逻辑
  17. *
  18. * 这里我们存储一下 preRate = 100, preQPS = ?
  19. *
  20. * newRate = (preQps / currentQps) * rate
  21. *
  22. * 范围限制:
  23. * newRate = Math.min(100, newRate);
  24. * newRate = Math.max(1, newRate);
  25. *
  26. * 3.2 时间队列的清空
  27. *
  28. * 更新完 rate 之后,对应的队列可以清空?
  29. *
  30. * 如果额外使用一个 count,好像也可以。
  31. * 可以调整为 atomicLong 的计算器,和 preTime。
  32. *

代码实现

  1. public class AutoLogSampleConditionAdaptive implements IAutoLogSampleCondition {
  2. private static final AutoLogSampleConditionAdaptive INSTANCE = new AutoLogSampleConditionAdaptive();
  3. /**
  4. * 单例的方式获取实例
  5. * @return 结果
  6. */
  7. public static AutoLogSampleConditionAdaptive getInstance() {
  8. return INSTANCE;
  9. }
  10. /**
  11. * 次数大小限制,即接收到多少次请求更新一次 adaptive 计算
  12. *
  13. * TODO: 这个如何可以让用户可以自定义呢?后续考虑配置从默认的配置文件中读取。
  14. */
  15. private static final int COUNT_LIMIT = 1000;
  16. /**
  17. * 自适应比率,初始化为 100.全部采集
  18. */
  19. private volatile int adaptiveRate = 100;
  20. /**
  21. * 上一次的 QPS
  22. *
  23. * TODO: 这个如何可以让用户可以自定义呢?后续考虑配置从默认的配置文件中读取。
  24. */
  25. private volatile double preQps = 100.0;
  26. /**
  27. * 上一次的时间
  28. */
  29. private volatile long preTime;
  30. /**
  31. * 总数,请求计数器
  32. */
  33. private final AtomicInteger counter;
  34. public AutoLogSampleConditionAdaptive() {
  35. preTime = System.currentTimeMillis();
  36. counter = new AtomicInteger(0);
  37. }
  38. @Override
  39. public boolean sampleCondition(IAutoLogContext context) {
  40. int count = counter.incrementAndGet();
  41. // 触发一次重新计算
  42. if(count >= COUNT_LIMIT) {
  43. updateAdaptiveRate();
  44. }
  45. // 直接计算是否满足
  46. return InnerRandomUtil.randomRateCondition(adaptiveRate);
  47. }
  48. }

每次累加次数超过限定次数之后,我们就更新一下对应的日志概率。

最后的概率计算和上面的百分比类似,不再赘述。

  1. /**
  2. * 更新自适应的概率
  3. *
  4. * 100 计算一次,其实还好。实际应该可以适当调大这个阈值,本身不会经常变化的东西。
  5. */
  6. private synchronized void updateAdaptiveRate() {
  7. //消耗的毫秒数
  8. long costTimeMs = System.currentTimeMillis() - preTime;
  9. //qps 的计算,时间差是毫秒。所以次数需要乘以 1000
  10. double currentQps = COUNT_LIMIT*1000.0 / costTimeMs;
  11. // preRate * preQps = currentRate * currentQps; 保障采样均衡,服务器压力均衡
  12. // currentRate = (preRate * preQps) / currentQps;
  13. // 更新比率
  14. int newRate = 100;
  15. if(currentQps > 0) {
  16. newRate = (int) ((adaptiveRate * preQps) / currentQps);
  17. newRate = Math.min(100, newRate);
  18. newRate = Math.max(1, newRate);
  19. }
  20. // 更新 rate
  21. adaptiveRate = newRate;
  22. // 更新 QPS
  23. preQps = currentQps;
  24. // 更新上一次的时间内戳
  25. preTime = System.currentTimeMillis();
  26. // 归零
  27. counter.set(0);
  28. }

自适应代码-改良

问题

上面的自适应算法一般情况下都可以运行的很好。

但是有一种情况会不太好,那就是流量从高峰期到低峰期。

比如凌晨11点是请求高峰期,我们的输出日志概率很低。深夜之后请求数会很少,想达到累计值就会很慢,这个时间段就会导致日志输出很少。

如何解决这个问题呢?

思路

我们可以通过固定时间窗口的方式,来定时调整流量概率。

java 实现

我们初始化一个定时任务,1min 定时更新一次。

  1. public class AutoLogSampleConditionAdaptiveSchedule implements IAutoLogSampleCondition {
  2. private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
  3. /**
  4. * 时间分钟间隔
  5. */
  6. private static final int TIME_INTERVAL_MINUTES = 5;
  7. /**
  8. * 自适应比率,初始化为 100.全部采集
  9. */
  10. private volatile int adaptiveRate = 100;
  11. /**
  12. * 上一次的总数
  13. *
  14. * TODO: 这个如何可以让用户可以自定义呢?后续考虑配置从默认的配置文件中读取。
  15. */
  16. private volatile long preCount;
  17. /**
  18. * 总数,请求计数器
  19. */
  20. private final AtomicLong counter;
  21. public AutoLogSampleConditionAdaptiveSchedule() {
  22. counter = new AtomicLong(0);
  23. preCount = TIME_INTERVAL_MINUTES * 60 * 100;
  24. //1. 1min 后开始执行
  25. //2. 中间默认 5 分钟更新一次
  26. EXECUTOR_SERVICE.scheduleAtFixedRate(new Runnable() {
  27. @Override
  28. public void run() {
  29. updateAdaptiveRate();
  30. }
  31. }, 60, TIME_INTERVAL_MINUTES * 60, TimeUnit.SECONDS);
  32. }
  33. @Override
  34. public boolean sampleCondition(IAutoLogContext context) {
  35. counter.incrementAndGet();
  36. // 直接计算是否满足
  37. return InnerRandomUtil.randomRateCondition(adaptiveRate);
  38. }
  39. }

其中更新概率的逻辑和上面类似:

  1. /**
  2. * 更新自适应的概率
  3. *
  4. * QPS = count / time_interval
  5. *
  6. * 其中时间维度是固定的,所以可以不用考虑时间。
  7. */
  8. private synchronized void updateAdaptiveRate() {
  9. // preRate * preCount = currentRate * currentCount; 保障采样均衡,服务器压力均衡
  10. // currentRate = (preRate * preCount) / currentCount;
  11. // 更新比率
  12. long currentCount = counter.get();
  13. int newRate = 100;
  14. if(currentCount != 0) {
  15. newRate = (int) ((adaptiveRate * preCount) / currentCount);
  16. newRate = Math.min(100, newRate);
  17. newRate = Math.max(1, newRate);
  18. }
  19. // 更新自适应频率
  20. adaptiveRate = newRate;
  21. // 更新 QPS
  22. preCount = currentCount;
  23. // 归零
  24. counter.set(0);
  25. }

小结

让系统自动化分配资源,是一种非常好的思路,可以让资源利用最大化。

实现起来也不是很困难,实际要根据我们的业务量进行观察和调整。

开源地址

auto-log https://github.com/houbb/auto-log

原文链接:https://www.cnblogs.com/houbbBlogs/p/17663700.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号