BlockingQueue:异步批量上报

BlockingQueue:异步批量上报

作者:苏渡苇

项目地址github.com/iweidujiang...(感谢 Star !)

本文要干什么

前几篇把 Span 采出来了 :进站拦截器、出站 Feign 马甲,结束时都会 reportSpan

下一秒数据要离开业务进程,POST 到 insight-server。如果在 Tomcat 工作线程里当场发 HTTP:

  • 监测 Server 慢一拍,用户接口跟着慢
  • Server 挂了,业务线程被拖进超时、甚至被异常打脸
  • 每个请求打一次 HTTP,QPS 一高,监测比业务还忙

监测工具的铁律:采点可以发生在请求线程上,送走不行。

所以这篇的知识点是 BlockingQueue。Insight 用它做一层缓冲:请求线程只负责往队列里丢,后台线程再攒一批送走。

请求线程里别打 HTTP。Span 进队列,守护线程按批上报;队列满了就丢,失败只打日志,绝不拖垮业务。

一、先看这条链路

请求线程的活,到 offer 为止。后面那截慢的、会失败的,都在名叫 spring-insight-reporter 的后台线程里。

一些参数目前是写死在代码里的:

代码在此:io.github.iweidujiang.springinsight.agent.collector.AsyncSpanReporter

java 复制代码
// 配置常量
private static final int DEFAULT_QUEUE_CAPACITY = 10000;
private static final int DEFAULT_BATCH_SIZE = 200;
private static final long DEFAULT_FLUSH_INTERVAL_MS = 5000; // 5秒
private static final long DEFAULT_OFFER_TIMEOUT_MS = 100;

// 队列与状态控制
private final BlockingQueue<Object> metricsQueue;
private final AtomicBoolean running = new AtomicBoolean(false);
private Thread flushThread;

// 服务标识
private final String serviceName;
private final String serviceInstance;

/**
 * 延迟解析,避免与 Starter 中 BatchSink Bean 的初始化顺序竞态
 */
private final ObjectProvider<InsightBatchSink> batchSinkProvider;

// 统计信息
private final ReporterMetrics metrics = new ReporterMetrics();
参数 意思
队列容量 10000 最多囤这么多条,再多丢
入队等待 100ms 满了先等一丁点,还进不去就放弃
批量大小 200 一次最多送 200 条
刷盘间隔 5s 不够 200 也别让数据在队列里过夜

二、请求线程只做一件事:往队列里扔

拦截器、Feign 装饰器调用的都是 SpanReportingListener.reportSpan,里面立刻转到:

java 复制代码
public boolean report(Object metric) {
    if (!running.get()) {
        metrics.incrementDropped();
        return false;
    }
    boolean offered = metricsQueue.offer(metric, 100, TimeUnit.MILLISECONDS);
    if (!offered) {
        // 队列满:丢弃,不阻塞请求线程
        metrics.incrementDropped();
        return false;
    }
    return true;
}

几个选择都是故意的:

offer,不用 put

put 会在队列满时把调用线程卡住,直到有空位。监测队列一堵,下单接口就堵,这锅背不起。offer(..., 100ms):给后台一个极短的喘息,还进不去就丢。丢比卡好。

有界队列 LinkedBlockingQueue(10000)

无界队列在 Server 挂掉时会把 Span 堆到把堆内存吃光。有界 + 丢弃 = 背压。监测挂了,业务还在,只是控制台暂时少几条。

上报器没 start,直接丢。

启动中、关闭中,热路径上不要等。

所以 reportSpan 对业务几乎是「把对象塞进内存队列」。真正的网络,跟这次 HTTP 请求已经脱钩了。

三、后台线程:等到一批,或等到超时

构造时会 start() 一条 守护线程setDaemon(true))。进程要退了,它挡不住 JVM 退出;正常停机另有关闭钩子去 stop(),把队列里剩下的冲一把。

循环长这样:

java 复制代码
while (running.get()) {
    Object first = metricsQueue.poll(5000, TimeUnit.MILLISECONDS);
    if (first != null) {
        // 已经等到第一条,再非阻塞捞一批,最多凑到 200
        metricsQueue.drainTo(remaining, 199);
        flushTraceSpans(traceBatch);
    }
}

这是很常见的「时间 + 数量」双条件:

  • 5 秒内一条都没有:poll 超时,空转一圈(闲时几乎不打 HTTP)
  • 来了一条:立刻 drainTo 把队列里现成的一并拿走,最多 200
  • 流量暴:队列很快到 200,一批一批送,不会等到 5 秒

poll 等第一条,drainTo 捞后面的------比自己 take 一条循环 200 次要干净,也避免「只有 3 条还干等到凑满 200」。

刷盘时给 Span 补上 serviceName / serviceInstance(拦截器里不一定填了),再交给 InsightBatchSink。Sink 用 ObjectProvider 延迟取,跟第 7 篇同一个理由:上报器和 Sink 谁先创建不一定,热路径上再拿。

四、失败怎么处理:只打日志,继续跑

Sink 里发 HTTP 失败:不向调用方抛出,以免中断刷盘循环。

上报循环自己也套了大 try/catch:单次异常睡 1 秒,接着跑,监测 Server 重启那半分钟,业务进程不该跟着一起歇菜。

队列统计在 ReporterMetrics 里:接收、成功、失败、丢弃。

丢弃多了说明要么 Server 太慢、要么容量太小,该看的是监测侧,不是让调用接口超时。

关闭时:

java 复制代码
flushThread.interrupt();
flushThread.join(3000);
flushRemainingSpans();  // drainTo 剩余的再送一次

JVM 关闭钩子会调 stop(),守护线程保证「钩子没赶上」时至少不会把进程卡死。

极端情况:杀 -9、钩子没跑完,队列里那批就没了。

内存监测都这样,别当消息队列用。

五、自己做「旁路上报」时可以记住的

  1. 热路径只碰内存。 日志、指标、链路,能 offer 进有界队列就不要在请求线程里 httpClient.send
  2. 有界 + 丢弃。 无限堆积会先把你自己的服务 OOM。丢监测数据,保业务。
  3. 批量要有超时。 只按条数攒,低流量时数据会在队列里发霉;只按时间刷,高峰又太碎。poll(超时) + drainTo(批量) 够用。
  4. 后台失败不要冒泡。 监测挂了,用户不该看到 500。
  5. 线程设成 daemon,停机再补一刀 flush。 两个都要:平时别挡退出,平时停机尽量把尾巴送走。

六、小结

  • 不是 Span 已结束就 POST 出去,而是 先入队,后台再发

  • 不是队列满了卡住等空位,而是 队列满了直接丢掉,不堵请求线程

采点发生在业务线程,送走发生在 spring-insight-reporter,中间那条 BlockingQueue,就是监测工具和业务 QPS 之间的缓冲垫。

下一篇内容准备展示一下缓冲垫后面那一步:上报为啥用 JDK HttpClient,而不是 RestTemplate,Gateway 上没有 MVC,Starter 不能强绑 starter-web

🌟 最后:欢迎围观 Spring Insight

如果你对「轻量监测 / Spring Boot Starter / 链路埋点」感兴趣,欢迎看看这个还在打磨的小项目:

🔗 GitHubgithub.com/iweidujiang...

当前形态很朴素:业务服务加 spring-insight-agent-starter,旁边单独跑 insight-server 看拓扑和链路。能力有限,代码也还糙,适合当练手和对照。

你的 Star 是对我最大的鼓励。

相关推荐
IT界的老黄牛3 小时前
Prometheus TSDB 拆不出来、也存不了一年:4 条外部存储出路 + 容量测算
prometheus·时序数据库·监控·thanos·victoriametrics·remote_write
武哥聊编程4 小时前
【AI实战项目】AI Agent数据分析平台,基于SpringAI+Springboot+Vue+Agent的电商数据分析平台
人工智能·spring boot·数据分析·springai
计算机毕设定制辅导-无忧学长5 小时前
《基于SpringBoot的庭院玫瑰栽培养护知识交互式科普平台设计与实现》
java·spring boot·后端·毕业设计·个性化推荐·协同过滤算法·庭院玫瑰栽培养护知识科普平台
骇客野人5 小时前
SpringBoot数十Jar包批量生产部署落地实施方案(Shell+Systemd完整版)
spring boot·后端·jar
Json____5 小时前
从零构建家政服务平台:一套全栈架构如何打通管理端与移动端-java-springboot
java·spring boot·后端·架构·毕设·wwwoop.com
苏渡苇6 小时前
Spring Insight 里如何对 Span 进行清洗
java·spring boot·后端·spring·系统监控
WeiXin_DZbishe6 小时前
基于springboot大学生提问箱系统-计算机毕设【课程设计】72593
javascript·vue.js·spring boot·vscode·python·node.js·php
Json____8 小时前
基于 FastAPI + Vue3 的在线拍卖系统技术解析
spring boot·后端·fastapi·wwwoop.com
Java内核笔记8 小时前
Spring Boot 4.1 官方 gRPC 支持源码剖析:从社区 Starter 到一等公民
spring boot·后端