news 2026/7/30 5:30:58

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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架构隔离

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/30 5:27:08

Go 微服务治理趋势:服务网格、eBPF 与零信任架构的技术方向判断

Go 微服务治理趋势&#xff1a;服务网格、eBPF 与零信任架构的技术方向判断 一、微服务治理的"三层进化"&#xff1a;从应用代码到内核态 Go 在微服务治理领域一直是主力语言&#xff0c;但"用什么工具做治理"的答案在 2026 上半年发生了显著转移。传统方…

作者头像 李华
网站建设 2026/7/30 5:24:45

VLAN划分方式全解析:从端口到策略的实战选型指南

1. VLAN划分&#xff1a;从基础概念到实战选型在任何一个规模稍大的网络里&#xff0c;广播风暴都是一个让人头疼的问题。想象一下&#xff0c;一个办公室里几百台电脑&#xff0c;每次有设备发个ARP请求找网关&#xff0c;或者某个应用发个广播包&#xff0c;所有设备都得停下…

作者头像 李华
网站建设 2026/7/30 5:20:00

京东单品优惠券全攻略:从获取逻辑到实战避坑指南

1. 从“羊毛党”到普通用户&#xff1a;为什么我们需要关注单品优惠券&#xff1f;如果你经常在京东购物&#xff0c;可能会发现一个现象&#xff1a;同样一件商品&#xff0c;别人买的价格总是比你便宜几块、十几块&#xff0c;甚至几十块。这背后&#xff0c;除了平台大促&am…

作者头像 李华
网站建设 2026/7/30 5:19:04

NI HIL自动化测试18-Teststand04-自定义报告模板

目录 1.报告模板所在目录... 2.报告生成方法... 2.1国机项目报告样式... 2.1.1修改报告的表头... 2.1.2添加报告的名称... 2.1.3添加报告的软硬件版本号... 2.1.4添加测试用例执行的时间... 2.1.5添加测试人员信息... 2.1.6添加测试用例信息... 2.1.7添加统…

作者头像 李华
网站建设 2026/7/30 5:16:00

Windows平台C++版PaddleOCR GPU编译部署全攻略

1. 项目概述与核心价值最近在做一个需要从图片里批量提取文字的项目&#xff0c;用Python版的PaddleOCR跑起来虽然方便&#xff0c;但遇到大量图片时&#xff0c;CPU版本的推理速度实在让人捉急&#xff0c;而且想把功能集成到现有的C桌面应用里&#xff0c;用Python来回调也不…

作者头像 李华