当前位置: 首页 > news >正文

OLAP 数据库混进 OLTP 链路:一次 ClickHouse 拖死 API 101 分钟的复盘

📝 摘要:OLAP 数据库 ClickHouse 被"试验性"接入 OLTP 主链路,一次普通发版将其引爆:CK 磁盘满且无告警导致查询 hang,Spring Boot /actuator/health 每秒 480 次心跳逐个 ping 依赖借走 CK 连接,HikariCP 连接池 10 连接被 374 线程争抢耗尽,TCP 连接数从 5k 飙到 22k,后端服务间断不可用 101 分钟。arthas thread+vmtool 5 分钟锁定 CK 连接池,根因是 OLAP 不该进 OLTP 主链路。

某天晚上发版,约半小时后客服群炸了——支付不上、登录转圈、商详打不开。

折腾 101 分钟才恢复服务。事后追凶,所有人都没想到罪魁居然是几个月前"试验性"加进后端服务的一个 ClickHouse 连接——加完就没人再动它,一个普通发版当晚就把它点燃了。

这篇文章把这次"OLAP 数据库混入 OLTP 主链路"的连锁反应讲清楚,顺便聊聊"哪些场景不该连 ClickHouse"。


一、问题现象

故障时间某天晚上;首个异常信号 +3min(Sentry 报错),+30min 才真正引爆
总时长约 101 分钟
影响范围后端服务间断不可用(不是全挂,是抽风式 502)
直接资损略(估损方案见下)
触发时机发版后约 30 分钟引爆

怎么估的这笔损失(方法比数字重要):支付系统读不出"因这次故障少赚了多少",于是用基线对比法——拿故障前近 7 天同时段(晚间 2 小时)支付成功的均值当基线,故障当晚相对基线的缺口,就是这次的直接损失。

⚠️这次真正的杀伤是「101 分钟间断不可用」:所有依赖这个后端服务的功能都在抽风。而且这只是个普通夜晚——要是撞上大促,就是另一个量级的事故。一个没人再看一眼的 OLAP 连接,就把 OLTP 主链路拖到了离大事故只差一步的地方。


二、火药桶链路:跨度 5 个月的伏笔

这次事故最让人后背发凉的是——5 个月之前埋的雷,在一次毫无关联的发版里被精准引爆。把时间线倒回去看:

5 个月前 搭建 ClickHouse,忘了挂磁盘告警 💣 引信 #1 约 4 个月前 上线埋点接口 /collect 给业务方 A 用, 但忘了配 APISIX 网关映射 💣 引信 #2 约 4 个月前 admin(后台管理)加了 ClickHouse 依赖 约 3 个月前 后端服务(C 端 OLTP 主接口)"试验性"加 CK 依赖 💣 引信 #3 ← 最关键 理由:"想试一下能不能用,反正不调" 发版前 3 天 某 H5 渠道发版,会调用 /collect → 返回 404 → 触发 APISIX 健康检查 /** 🔥 火苗 #1 发版前 1 天 后端服务 Network.IN 报警上升,无人在意 发版当天 发版 + 关闭心跳(本意降负载) → 反触发大量心跳(APISIX 热加载 bug) 🔥 火苗 #2 发版当天+30min CK 磁盘满,无告警 💥 引爆 发版当天+30min TCP 连接数 5k → 22k 💥 服务卡死 发版当天+101min 定位 CK 磁盘满 → 扩容 CK 磁盘 ✅ 恢复

每一步单看都是"小事",但串起来就是连锁反应。看图谱:

5 个月前搭 CK,缺磁盘告警

约 4 个月前 admin 加 CK 依赖

约 3 个月前 后端服务 '试验性' 加 CK 依赖

约 4 个月前上线 /collect,忘了配网关

发版前3天 H5 调 /collect → 404

APISIX 兜底回源触发健康检查 /**

发版前1天 后端服务 Network.IN 报警

发版当天+30min CK 磁盘满 (无告警)

发版当天 关心跳
APISIX 热加载 bug 反触发心跳

/actuator/health 480 req/s

心跳触发 CK 健康检查

HikariPool-CK 等待 374,连接 10

TCP 5k → 22k,新连接建不出

💥 后端服务间断不可用 101 分钟


三、排查现场:从"看不出来"到"arthas 一锤定音"

3.1 第一波:Redis 类型转换异常(假线索)

发版+3min Sentry 收到 7 次: Unknown redis exception; nested exception is java.lang.NumberFormatException: For input string: ... ClassCastException: java.lang.Long cannot be cast to [B

值班大哥第一反应:发版导致 Redis 序列化挂了,准备回滚

但代码 review 过、SkyWalking 链路里看不到这些异常的堆栈——这条线索是真问题但不是主因(后来确认 Redis 那边有少量类转换的脏数据需要修,但跟卡死无关)。

排查方向被带歪了大约 15 分钟。

3.2 第二波:回滚 + 重启 + 再回滚(无效循环)

发版+34min 电话联系开发回滚 发版+37min 第一次回滚代码 发版+41min 第二次回滚 + 重启后端服务 发版+49min 第三次回滚 + 重启后端服务 发版+72min 第四次回滚 + 重启后端服务 发版+82min 第五次回滚 + 重启后端服务 发版+90min 第六次回滚 + 重启后端服务

回滚 6 次,每次都是几分钟好转,然后又卡死。事后复盘才发现,这一波从一开始就回滚错了对象:大家回滚的是这次发版的代码,可真凶那行 CK 依赖压根不是这次发版引入的——它是上千次提交之前"试验性"加进来的,想回滚到没有它的版本,等于把这几个月所有功能一起撤掉,根本不可能;这次发版的回滚更是碰都碰不到它。

回滚 + 重启唯一的作用,只是把 TCP 连接和健康检查短暂清零,于是每次都"假好转"几分钟,然后又从干净状态一路堆到耗尽。真凶(CK 磁盘满)没动过,症状自然反复。

3.3 第三波:arthas 抓现场,5 分钟锁定真凶

发版+80min 决定用 arthas:

java-jararthas-boot.jar

thread命令一打:

Threads Total: 642, NEW: 0, RUNNABLE: 72, BLOCKED: 0, WAITING: 101, TIMED_WAITING: 457, TERMINATED: 0, Internal: 12

457 个线程 TIMED_WAITING + 101 个 WAITING——80% 的线程在等什么东西。

挑一个高 CPU 的 http-nio 线程看堆栈,马上看到:

at com.zaxxer.hikari.util.ConcurrentBag.borrow(ConcurrentBag.java:157) at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:173)

HikariCP 在排队等连接

下一步用vmtool看具体哪个池有问题:

vmtool--actiongetInstances--classNamecom.zaxxer.hikari.pool.HikariPool-x1# 返回 3 个池: HikariPool-1 / HikariPool-2 / HikariPool-3

逐个看池状态:

HikariPool-3: 连接数: 10 等待线程: 374 ← 罪魁 dataSource: jdbc:clickhouse://10.x.x.x:8123/ods

——HikariPool-3 就是 ClickHouse 连接池,10 个连接全占用,374 个线程在排队

10 vs 374,1:37 的供需比——这池子已经废了。

3.4 同时另一条线:网关 TCP 监控发现异常

发版+95min 运维同学看 grafana TCP 监控:

TCP_alloc (已分配 socket): 5k → 22k (4 倍!) TCP_tw (TIME_WAIT): 几乎与 alloc 同步上涨

后端服务器的 TCP 连接数被打爆,新连接进不来——这就是间断不可用的直接原因(不是后端进程挂了,是网络层先饱和了)。

3.5 发版+101min:真相

发版+101min 运维终于发现ClickHouse 磁盘在发版+30min 就满了:

df -h /data/clickhouse /dev/vdb 1.0T 1.0T 0 100% /data/clickhouse

CK 磁盘一直没有磁盘告警:5 个月前搭的时候漏配了云盘磁盘告警;虽然也有 Prometheus、磁盘指标能采到,但同样是历史遗留、始终没配告警规则。两套监控都在,却都没对"磁盘满"吭一声。

到这里链条对上了:

  • 发版+30min CK 磁盘满 → CK 查询 hang
  • 后端服务健康检查每秒去 ping CK(/actuator/health默认会 ping 所有依赖)
  • CK hang → HikariPool-CK 连接被占住 → 10 连接全满
  • 后续请求排队 → 374 线程等连接 → TCP 雪崩

四、根因分析:三个独立的"温问题"叠加成沸水

#温问题单独是否致命跟其他的耦合
1后端服务"试验性"接入 ClickHouse否(平时不调用)+ 2 → 一接入心跳就活跃
2/actuator/health默认 ping 所有依赖否(只是慢一点)+ 1 → ping CK 占用连接
3ClickHouse 磁盘满 + 无监控告警否(CK 平时也能熬过去)+ 1、2 → CK hang 直接卡死后端服务

任意两个都不致命,三个凑齐 = 101 分钟卡死

下面挨个拆。

4.1 最大根因:OLAP 数据库不该出现在 OLTP 主链路

这次最痛的教训。先把 OLTP 和 OLAP 摆出来对比:

维度OLTP(C 端业务 API)OLAP(ClickHouse 的主场)
并发模型几万 QPS,每条 < 50ms几十并发,每条扫几千万行
响应时间 SLAP99 < 100msP95 秒级可接受
连接占用短平快(几十毫秒)长占用(秒级 + 资源大)
写入模式频繁单行 CRUD大批量 append,极少更新
单 SQL 资源多并发分摊一条能吃满 CPU
适合的连接池几百~几千几十封顶
健康检查友好度心跳无感每次心跳都是开销

结论: OLTP 在线主链路里永远不要直连 ClickHouse。哪怕你只是"试验性"加个依赖,只要spring.datasource.clickhouse配进去了,health check 就会去 ping 它——你不调用业务,Spring 也帮你调用。

可以连 CK 的"在线"场景(仍要小心):

  • BI 报表 / 用户行为分析(用户接受秒级响应)
  • 异步任务、定时统计、月报
  • 后台 admin 管理系统(并发低,响应宽松)
  • 服务端预聚合后供 API 读(此时 CK 不在请求链路上)

不能连 CK:

  • 主 OLTP 链路上的高并发接口(本次后端服务就是这类)
  • 健康检查 / 心跳路径(致命!)
  • 任何要求 P99 < 200ms 的接口

4.2 次要根因:/actuator/health是个"昂贵"的接口

Spring Boot Actuator 的/actuator/health默认配置下,每次调用都会去 ping 所有注册的组件:

  • DataSource (MySQL / PostgreSQL / ClickHouse / …)
  • Redis
  • MongoDB
  • Kafka
  • Elasticsearch
  • Diskspace

健康检查链路:

APISIX → /actuator/health → Spring 遍历 HealthIndicator → ├── DataSource (MySQL) ping ├── DataSource (CK) ping ← 借走一个 CK 连接 ├── Redis ping └── ... 全部串行

在本次事故里,APISIX 每秒打过来480 req/s/actuator/health,其中 480 个都会去借 CK 连接——连接池 10 个,瞬间用完。

故障当晚 Grafana QPS-Top10:/actuator/health稳定压在480 req/s,健康检查风暴一图坐实。

这是一个国内深度博客很少讲的反模式,我后面专门写了一篇 【待发布后补充引用关系:《Spring Boot 的 /actuator/health 不是免费的:健康检查打爆连接池的反面教材》】 详细讲怎么避坑。

4.3 触发因素:APISIX 健康检查/**的"误放大"

APISIX 配了"兜底回源",当某个路径返回 404 时会触发健康检查/**

发版前 3 天 H5 渠道发版后开始调/collect,但这个接口没配网关映射→ 404 → 触发/**健康检查 → 每秒几百次打到/actuator/health

CK 侧ProfileEvent_Query从发版前 3 天(/collect开始 404)逐日爬升到 ~500 req/s——放大效应不是一夜爆发,而是慢慢累积到临界。

这本是一个温问题,但发版当天关心跳(APISIX 热加载 bug)+ CK 磁盘满后,瞬间从"温"变"沸"。

4.4 隐藏根因:CK 磁盘 5 个月没接监控

CK 是 5 个月前搭的,搭的时候漏了挂云盘磁盘告警。跑了 5 个月,磁盘从 60% 涨到 95% 也没人知道,发版当晚跨过 100% 才被发现。

ClickHouse 数据增长: 约 3 个月前 60% 占用 约 2 个月前 75% 约 1 个月前 90% 发版前 3 天 95% (3 天涨了 5%,跟某个新埋点表有关) 发版当天+30min 100% ← 引爆

磁盘从平日的 ~80% 一路爬升,发版当天 20:30 触顶100%(右上角容量% 99.9%),随后扩容才回落——「引爆」二字看图一目了然。


五、解决方案

5.1 紧急止血(发版当晚)

时点动作
发版+101min定位到 CK 磁盘满 →给 CK 扩容磁盘,查询解除 hang,后端服务随之恢复
发版+105min验证服务启动正常
发版+117min验证各业务功能全部恢复

⚠️ 注意:扩容只是"止血",不是"治本"。它让 CK 别再 hang、把后端服务从耗尽里捞出来;但只要 CK 还挂在 OLTP 主链路上,下次磁盘满 / CK 抖动照样会重演。真正的根治是次日把 CK 依赖从主链路里拔掉(见 5.2)。至于回滚——上面说过,真凶 CK 是上千次提交前加的,回滚这次发版根本够不着它。

5.2 短期治本(72 小时内)

1. 业务剥离 ClickHouse(P0,发版次日上午)

- spring: - datasource: - clickhouse: - jdbc-url: jdbc:clickhouse://... + # OLTP 服务不再依赖 ClickHouse

效果立竿见影:

发版次日下午 上线 "后端服务移除 ClickHouse" 配置后: Clickhouse QPS: 几百 → 0 CPU Load: 持续下降

发版次日下午上线"移除 CK"配置后,健康检查 QPS 从 ~500直接归零,CPU 随之回落——OLAP 一从主链路里拔掉,世界瞬间清净。

2. 给所有云服务器挂上磁盘告警

-alert:服务器磁盘使用率过高expr:(1-node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100>85for:5mlabels:severity:warning

3. APISIX 网关:补齐网关映射 + 关掉兜底回源

# 之前: /** 兜底,任何 404 都触发健康检查# 改后: 显式路由,匹配不到的直接返回 404,不再放大

4./actuator/health改成轻量探活

management:health:db:enabled:false# 禁用 DB 健康检查diskspace:enabled:falseredis:enabled:falseclickhouse:enabled:false

更激进一点是自己写一个空接口替代/actuator/health,因为我们的场景是探活,不是健康检查。详见专题 【待发布后补充引用关系:《Spring Boot 的 /actuator/health 不是免费的》】。

5.3 中长期治理

#措施负责方状态
1主线 OLTP 服务永久剥离 OLAP 数据库后端✅ 已落
2所有云主机磁盘告警检查清单运维⏳ 进行中
3健康检查 vs 探活检查的规范文档架构组
4APISIX 网关映射巡检(每月)运维
5上线最佳时间窗调整(避开高峰)全员✅ 已立规矩
6应急预案文档落地 + 桌面演练后端 + 运维

六、举一反三:5 条值得带走的经验

经验 1: "试验性"代码不是没有成本

“我加了个连接,但又不调用,应该没影响吧?”

——错。只要 DataSource bean 注入了,默认的 health indicator 就会用它,Prometheus 也会去采它的指标,连接就会被借走。

“代码进了 Spring 容器,就不是免费的。”

经验 2: 健康检查 ≠ 探活检查

名称用途应该检查啥
探活检查(liveness)进程还活着吗?TCP 端口 + 简单字段返回,不查依赖
就绪检查(readiness)进程能服务请求吗?关键依赖可用(注意:关键≠ 全部)
健康检查(health)给运维 / 监控看的诊断信息各组件状态明细

把这三个混在一起,就是/actuator/health反模式的源头。

经验 3: 慢问题 + 慢问题 = 快问题

§四那四瓢"温水"(连 CK 不用 / 磁盘缓慢满 / health 全检 / 健康检查放大),单看每一瓢都不致命。真正要命的是:监控只在"沸水"那一刻才报警,"温水"阶段仪表盘一片绿——等它报,人已经在群里挨骂了。

对策: 周期性盘点"温水"清单,主动做架构隔离 / 降级 / 监控补齐,别等它自己烧开。

经验 4: OLAP 跟 OLTP 之间必须做物理隔离

不仅是"OLTP 不连 OLAP"——更进一步:

  • 不同环境(生产 / 测试)隔离
  • 不同业务等级(C 端在线 / 后台管理)隔离
  • 不同模式(审核 / 非审核)隔离
  • 不同读写(OLAP read / OLTP write)隔离

判定标准很简单: 如果 A 系统挂了 B 系统跟着挂,就说明它俩没隔离干净。

经验 5: 监控覆盖率盘点要定期做

CK 磁盘 5 个月无告警这件事,本质是搭新中间件的"监控接入"没纳入上线 checklist

建议每季度做一次"监控覆盖率盘点":

- 所有云主机 → 是否有 CPU / Mem / Disk / Network 告警 - 所有中间件 → 是否有应用级监控 - 所有 DataSource → 是否有连接池监控 - 所有外部依赖 → 是否有调用成功率告警

七、延伸阅读

跟本文配套的两篇专题:

  • 专题:【待发布后补充引用关系:《Spring Boot 的 /actuator/health 不是免费的:健康检查打爆连接池的反面教材》】 — 把 health 反模式讲透
  • 专题:【待发布后补充引用关系:《arthas 5 分钟揪出 HikariCP 耗尽:从 thread 到 vmtool 的实战排查》】 — 同款排查工具,5 分钟从症状到根因

同主题"连接池耗尽"的另一种姿势(根因不同的同款现象):

  • 《AI 服务 502 雪崩排查:从 Nginx 超时到连接池耗尽,查了两次才找到真凶》 — 根因是 yaml 配置不生效 + 事务内调大模型

同系列"排查实战"组合拳:

  • 《CPU 占用高排查实战:从 top 到火焰图,一套组合拳搞定》
  • 《MongoDB 主从切换排查实战:从 docker ps 到 jq,一套 SOP 定位死因》

🏷️ 标签:ClickHouseOLTP/OLAP线上排障HikariCPSpring Boot架构隔离

http://www.cnnetsun.cn/news/3742919.html

相关文章:

  • AI Agent具体有哪些分类?从技术架构到企业应用,解析智能体的作用与价值
  • Go 微服务治理趋势:服务网格、eBPF 与零信任架构的技术方向判断
  • VLAN划分方式全解析:从端口到策略的实战选型指南
  • 京东单品优惠券全攻略:从获取逻辑到实战避坑指南
  • NI HIL自动化测试18-Teststand04-自定义报告模板
  • Windows平台C++版PaddleOCR GPU编译部署全攻略
  • MCU模拟串口实现:外部中断与定时器精准控制异步通信时序
  • STM32 HAL库DMA中断配置详解:从原理到实战应用
  • 解放双手!Linux 定时任务自动帮你跑脚本、备份数据
  • 差分放大电路输出电压偏移原理与工程实现详解
  • 从原理到实战:共阳极数码管驱动、动态扫描与消影技术详解
  • TWEN-ASR ONE语音识别开发板入门:从零搭建环境到运行第一个程序
  • 5分钟极速部署OWASP Juice Shop:Docker与Node.js方案全解析
  • React createPortal 实战:模态框逃出 overflow:hidden、事件冒泡与焦点管理
  • AI 与传统文化融合的下半场:从娱乐到研究的方法论升级
  • 2026深度实测:16款降AIGC网站实测,闭眼入这款就对了!
  • 【单片机毕业设计推荐】基于 STM32 的人体健康监测与跌倒报警装置设计与实现,基于 STM32 的可穿戴运动健康监测终端及 WiFi 移动端系统设计(013304)
  • REFramework终极指南:5分钟为RE引擎游戏安装模组和脚本平台
  • Selenium模拟登录全攻略:从环境搭建到实战优化
  • 2026AI论文工具稀缺功能排行榜[特殊字符]真正有独家技术的只有OKBIYE
  • 7天从零构建RAG应用:LangChain+Ollama本地部署实战指南
  • FPGA入门:从原理图设计模60计数器理解数字电路底层原理
  • 文献表格工具怎么选?我把手头 60 篇 PDF 喂给三种方案实测了一遍(2026 实测版)
  • 毫米波多路功放怎么选?鼎讯信通 DXGF-228A 关键参数全解析
  • STM32硬件I2C驱动RM3100磁力计:从原理到稳定实现的完整指南
  • STM32内部FLASH存储与显示彩色图片的嵌入式优化实践
  • 二维字符数组与函数(声明、作用域、生存周期、存储)
  • 大模型上下文长度:原理、挑战与RAG等主流扩展方案详解
  • 3步完成QQ空间历史说说完整备份:GetQzonehistory开源工具终极指南
  • Python编程核心范式:从语法基础到实战项目的100个必背源码精解