您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
【广告投放-大促备战】存储性能优化篇 -- 从异构同步到链路解耦的 JES 治理实战
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
【广告投放-大促备战】存储性能优化篇 -- 从异构同步到链路解耦的 JES 治理实战
jd****
2026-07-20
IP归属:北京
3185浏览
# 广告财务资金:50+亿文档的JES深水区性能优化 > *大促备战从来不是简单的"加机器、扩容量"。当广告账户资金的核心链路撞上亿级单表体量、50+亿文档的 ES 集群、以及月末/月初 头部 KA 客户的查账洪峰时,任何一个未被治理的技术债都可能在峰值时刻被放大成一场客诉级/资损级的故障。* > > ***本文深度复盘广告领域-账户资金团队在本次大促备战中,围绕 JES 完成的四项核心治理工作:*** > ***1、异构同步基建的自研组件;*** > ***2、查询链路提效:从 Cache 失效到 I/O 放大的源码级调优;*** > ***3、碎片治理:核心索引的在线Force Merge;*** > ***4、大key优化:对超级大客算力踩踏的架构解耦。*** *** ## 一、背景与选型:海量分库分表的算力瓶颈与自研异构同步基建 ### 背景痛点:计算与存储的双重挤压 广告账户财务的资金流水记录历史数据跨度10年+,是典型的"金融级高频读 + 海量存储"场景。随着业务增长,痛点在备战首轮压测中集中爆发: 1. 单表体量突破 3 亿行,且数据物理碎裂在 16 个分库 + 50+ 张周表(冷热表) 中; 2. 商家侧资金报表查询高度依赖 `COUNT` / `GROUP BY` / `SUM` 聚合,MySQL 优化器在跨分片场景下频繁走全表扫描; 3. 5.10 凌晨首轮共振压测中,慢 SQL 直接将资金mysql库繁忙度打满,触发数据库告警,读写全面劣化; 4. **<span style="color: #ab4642">MySQL 的 B+Tree 索引擅长点查与范围扫描,但不擅长海量数据的实时聚合与多维筛选——这是存储引擎的本质局限,靠加索引、加从库无法根治。</span>** ``` [首轮共振压测告警快照] ━━━━━━━数据库磁盘繁忙度99.9%━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ [CRITICAL] 查询充值记录接口性能 TP99 异常,超出最低阈值100倍以上 [CRITICAL] mysql慢查询堆积: 11000+ /min,平均 RT 50s 根因: SELECT ... WHERE master_id=? GROUP BY ... 全表扫描 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ```  *** ### 基于存储与计算分离的架构考量 <span style="color: #ab4642">结论很清晰:</span>**<span style="color: #ab4642">将高频聚合、多维检索类查询从 MySQL 卸载到更适合的检索引擎(ES)</span>**<span style="color: #ab4642">,让 MySQL 回归其擅长的事务与点查,让 ES 承担 OLAP 式的检索与聚合。</span> <span style="color: #ab4642">但难点不在"用 ES",而在</span>**<span style="color: #ab4642">如何把物理碎裂在 16 库 50+ 表中的数据,低延迟、无丢失地同步到 ES</span>**<span style="color: #ab4642">。</span> ### 【方案对比】 | 方案 | 实时性 | 侵入性 | 碎片聚合能力 | 结论 | | --- | --- | --- | ------ | --- | | 业务双写 | 高 | **高**(每个写入点都要改) | 需业务自己拼 | ❌ 侵入大、易漏写、事务不一致 | | 定时任务批量同步 | **低**(分钟\~小时级) | 中 | 需自己扫全表 | ❌ 实时性不达标 | | 通用 CDC 工具(如 Canal 裸用) | 高 | 低 | **弱**(不理解分库分表语义) | ⚠️ 无法感知 16 库 50 表的聚合语义 | | **复用自研 Binlog 同步组件** | **高(秒级)** | **低** | **强(内建分库分表路由)** | ✅ 采用 | *** ### 【核心优化动作】 #### **1\. 自研 Binlog 组件承接异构同步** 我们没有简单堆砌现成工具,而是复用团队沉淀的**统一 Binlog 数据同步组件**。它能精准解析并聚合 16 个分库 + 50+ 周表的碎片 Binlog,平滑、低延迟地异构路由至 JES。 **整体架构如下:**  如上架构图:组件内部沉淀了几个关键的工程细节: * **异构实体对齐**:MySQL 的 `DECIMAL` 金额统一 ×10000 存为 ES `long`,`datetime` 归一为 `yyyy-MM-dd HH:mm:ss` 字符串,规避浮点精度与序列化异常; * **防乱序 upsert**:针对 Binlog 消息可能乱序到达(UPDATE 先于 INSERT),用 `coalesce(modifyTime, creatTime)` 作为版本标识,通过 ES Painless Script 做版本比对,**只允许新数据覆盖旧数据**,杜绝"旧值覆盖新值"的数据错乱。 *** **坚决不裸奔:上线前的稳妥性验证** 金融资金链路的底线是"数据不能错、服务不能崩",接入 JES 全程通过资金中台服务统一收口,在第1步完成的基础上,陆续完成后三道防线: #### **2\. 实时流量 Diff 对齐**:ES 查询结果与 MySQL 原结果实时比对,确保语义 100% 等价; #### **3\. 三轮预发等比例缩容压测**:证明在 **3 倍峰值流量**下 TP999 达标,JES 集群 CPU / 内存平稳; #### **4\. 灰度名单 \+ 一键降级开关**:通过配置中心白名单灰度头部商家,并保留一键降级开关,**任何异常可秒级切回 MySQL**。 *** ### 【优化后成效】 | 指标 | MySQL 原链路 | JES 异构链路 | 亮点 | | --- | --------- | -------- | --- | | 单商家 2 年充值记录查询 | \~300ms | 见第三章 force merge 后 | ⭐ ⭐ ⭐ 计算压力从 DB 卸载 | | 资金库磁盘繁忙 | 压测打满 100% | **回落至平稳水位** | ⭐ ⭐ ⭐ 根治慢 SQL 共振 | | 数据一致性 | — | **实时 Diff 100% 对齐** | ⭐ ⭐ 金融级可靠 | | 故障恢复 | — | **一键降级 MySQL** | ⭐ 零风险兜底 | *** <br> ## 二、查询链路提效:从 Cache 失效到 I/O 放大的源码级调优 ### 背景痛点:正确查询 ≠ 高效查询 历史 JES 集群虽然扛下了部分流量,但存在大量**低效查询写法**,在高并发下被急剧放大,成为集群算力的隐性黑洞: 1. 大 `Terms` 查询(单次匹配 120+ 个值),倒排链合并开销随 term 数线性上升; 2. 开放式 `Range` 查询(`from:null`),退化为全范围扫描,索引形同虚设; 3. 全部条件走 `must` 上下文,白白计算相关性评分且 Filter Cache 完全失效,I/O 严重放大。 **这些查询单看都能返回结果,但每一次调用都在浪费JES集群的 CPU 与 I/O,在峰值流量下就是压垮集群的"慢性毒药",以下是JES巡检部分现场还原:**     *** ### **基于查询原理的优化考量** 优化的核心思想:**深入 Lucene 底层执行机制,从"评分、缓存、倒排链、索引命中"四个维度降低查询开销**——每一次 ES 调用都用尽量低的 CPU 与 I/O 成本拿到结果。三个手段对症下药: ### 【核心优化动作】 #### **1. `must` → `filter`:跳过评分,激活缓存** 资金检索全部是精确匹配(`term`/`terms`)和范围过滤(`range`),**不需要全文相关性评分**。`must` 上下文却强制计算 `_score` 且结果不可缓存,纯属浪费: ``` must 上下文 = 条件匹配 + 计算 _score(无意义) + 不缓存 filter 上下文 = 条件匹配 + 跳过评分 + 自动 Filter Cache ``` 底层 `EsBaseComponent` 查询组件统一将等值 / 范围条件从 `must` 改为 `filter`,一处改动,全局受益。 *** #### **2\. 精简大 Terms:TermInSetQuery 的源码级代价** Lucene 的 `TermInSetQuery` 本质是"多个 term 查询求或集"。单个 term 查倒排链极快,但 **120+ 个 term 需要合并 120 条倒排链**,耗时随 term 数量线性上升,且难以命中 Cache。要提效,就得从根上减少倒排链的合并数量: * **超长 Terms 熔断**:识别并拦截匹配条件超 **50 条**的异常调用,从入口消除极端慢查询; * **Terms 反转优化(带安全网)**:当 include 集合接近全集时(如 120/135),反转为 `must_not` 排除少数(如 15 个),把 120 条倒排链合并压缩到 15 条。**但反转有个隐藏陷阱**——白名单变黑名单后,枚举外的脏数据会泄漏,因此我们配套了**动态黑名单兜底 + 灰度开关**: ``` 原始(白名单): filter type IN [120个] → 拦截一切未知值 ✅ 反转(黑名单): must_not type IN [15个] → 枚举外 type=9999 会泄漏 ⚠️ 兜底: es.terms.invert.exclude.types 动态配置 + 开关默认关闭 ``` > 这里踩过一个坑值得记录:反转看似能把 120 terms 降到 15,但若为防脏数据强行保留 filter 全集约束,反而变成 135+15=150,得不偿失。**优化的前提是先想清楚语义边界,而非盲目套用"技巧"**。 *** #### **3\. 消除开放式 Range:让查询回归 BKD 树索引** 业务中"查询金额 < 0 的逆向冲正记录"被写成了 `[,0)` 区间,解析后 `from=null`,ES 无法利用 BKD 树的下界剪枝,退化为从负无穷开始的全范围扫描。补齐明确下界后,查询即可精确命中索引: ```java // 治理前:from:null,全范围扫描 rangeGleqValueCondition.put("cost", "[," + 0 + ")"); // 治理后:给定明确业务下界(decimal(14,4)×10000 的理论最小值) rangeGleqValueCondition.put("cost", "[-99999999999999," + 0 + ")"); ``` *** ### 【优化后成效】 | 优化项 | 优化前 | 优化后 | 亮点 | | --- | --- | --- | --- | | 条件上下文 | `must`(评分+不缓存) | `filter`(跳过评分+缓存) | ⭐⭐⭐⭐ Filter Cache 淘汰率 **百万级 → 近 0** | | 大 Terms | 无限制,120+ 条倒排链 | 熔断 >50,反转压缩至 \~15 | ⭐⭐⭐ 倒排链合并开销大幅下降,慢查询归0 | | Range | `from:null` 全范围扫描 | 明确下界,命中索引 | ⭐⭐ 精确走 BKD 树索引,慢查询归0 | *** ## 三、碎片治理:3TB 核心索引的 Force Merge 优化 ### 背景痛点:从未维护的碎片化集群 历史 JES 集群**从未做过深度段合并**。ES 每次写入产生新的 Segment,持续写入下单个分片碎裂成成百上千个小 Segment。查询时需逐段扫描,延迟随碎片数线性增长,Filter Cache 也因碎片分散而命中率低下。 ### 基于 Lucene Segment 机制的架构考量 `force merge` 将每个分片的多个小 Segment 合并为极少数(理想为 1 个),带来两个收益: 1. **查询从"逐段扫描"变为"单段直查"**; 2. **Filter Cache 从"每段各缓存一份 bitset"变为"一份即命中"**。 但 `force merge` 是 IO 密集操作(需重写全部数据),必须**精细选择低峰期在线执行**。 *** ### 【方案对比:分片数的再平衡】 在 merge 前,我们还顺带修正了一个配置债——分片数。50GB 的索引配了 12 分片,每片仅 4GB,查询扇出(fan-out)过大: | 分片数 | 单分片大小 | 查询扇出 | 评估 | | --- | ----- | ---- | --- | | 12(历史) | \~4GB | 12 | ❌ 分片过小,扇出浪费 | | 3 | \~16.7GB | 3 | ✅ 落在 ES 甜区(20\~50GB) | | 8(最终) | \~6GB × 2副本 | 8 | ✅ 均衡 24 节点 CPU 热点 | > 分片数没有银弹:**3 分片查询最快(扇出最小),但少数节点 CPU 偏高;8 分片×2 副本能把 24 个 shard 副本均匀铺满 24 个数据节点**,压测下集群负载更均衡。最终按"资源均衡优先"选择了 8 分片。 *** ### 【核心优化动作】 #### **1\. 集群整体force merge** **精选大促前夕业务低峰期(22:00–03:00)**,对账户侧 **6 个核心索引**(消耗花费、账单明细、资金变动等,合计 **3TB / 超 50 亿文档**)在线执行 `force merge`; #### 2\. Segment 收敛 通过 `_tasks` API 实时监控 merge 进度与 Segment 收敛; #### 3\. 清理废弃索引 顺带**清理废弃历史索引,释放 100GB 物理存储**; #### 4\. 定时任务固化**force merge** 定时任务中固化 `maxNumSegments=5` 的日常增量合并,防止碎片再次累积(全量 `=1` 仅限低峰手动执行)。 ### 【优化后成效】 | 指标 | Merge 前 | Merge 后 | 亮点 | | --- | ------- | ------- | --- | | 单商家查询 RT | 300\~800ms | **60\~140ms** | ⭐ ⭐ ⭐ **提升近6倍** | | 预发压测平均性能 | 基线 | **提升 \~5 倍** | ⭐ ⭐ ⭐ 全索引普惠 | | 物理存储 | — | **释放 100GB** | ⭐ 清理废弃索引 | | 业务影响 | — | **零感知、无停机** | ⭐ 在线滚动执行 | *** <br> ## 四、压测盲区与链路解耦:KA 大客数据倾斜引发的 JVM 飙升 ### 问题现场还原:优化之后,依然飙升 在前三章的一系列优化后,集群整体已趋于平稳。但适逢**月底/月初大客财务集中查账**,线上 ES 集群 **Master 节点及部分数据节点 JVM 突发飙升超 90%**,并伴随节点短时掉线与 Master 重平衡! 现场还原监控如下:   *** ### 根因剖析:压测模型的盲区 复盘定位到两个叠加根因: 1. **数据倾斜(Data Tilt)盲区:3 轮压测用的是**均值流量模型,而真实世界里,头部 KA 大客的 `ad_space_effect` 数据量是普通商家的成百上千倍。**均值模型天然覆盖不到这种长尾倾斜**——这是压测方法论层面的盲区。 2. **轻重链路混用引发连锁掉线**:**关键的架构缺陷--分页查询(轻读)与异步下载导出(重算)混用同一条链路、同一套聚合逻辑。** ``` 大客点击"导出" → 触发全量明细 + 重度 SUM 聚合 → 单次请求需协调 12 分片的海量数据聚合 → 数据节点 JVM 瞬间打满 → 节点掉线 → Master 感知掉线 → 触发分片重平衡 → Master CPU 打满 → 心跳超时 → 更多节点被误判掉线 → 连锁反应,集群整体劣化 ``` **<span style="color: #ab4642">一次大客的重度下载,就足以引发连锁的节点掉线与 Master 重平衡风暴。</span>** *** ### 【核心优化动作】基于"不带隐患过大促"的架构决断: #### **1\. 短期维稳扩容** 夜间低峰期果断**物理扩容 12 个数据节点**,分散并发压力,先把风险摁住。 #### **2\. 架构根治\-\-轻重链路的架构解耦(核心)** 在**大促封板前**,紧急完成"**轻重链路的架构解耦**"——彻底拆分在线分页查询与离线下载导出,针对下载接口**完全剥离高耗能聚合,改造为纯明细流式导出**。 优化链路架构如下:  #### 3\. 聚合精简、游标分页的三个精细化改造 1. **翻页跳过聚合**:仅首页(`page<=1 && minId==null`)计算 `SUM`,翻页与导出一律跳过,单次查询省 **300\~500ms** 的聚合开销; 2. **去掉冗余聚合**:充值记录查询本不需要金额汇总(由独立接口负责),彻底移除; 3. **导出改游标分页**:下载从 `offset` 深分页(`from+size` 受 10000 上限限制且越翻越慢)改为 **`minId` 游标翻页**(`pk < minId` + pk 降序),平滑扫描全量数据,零深分页压力。 ### 【优化后成效】 | 维度 | 解耦前 | 解耦后 | 亮点 | | --- | --- | --- | --- | | 轻重链路 | 混用同一聚合链路 | **物理隔离** | ⭐⭐⭐ 下载不再冲击在线查询 | | 下载导出 | 全量聚合 + 深分页 | **纯明细流式 + 游标** | ⭐⭐⭐ 剥离高耗能计算 | | 大客场景 JVM | 突发 >90% 掉线 | **平稳,爆栈隐患根除** | ⭐⭐ 抗数据倾斜 | | 集群稳定性 | Master 重平衡雪崩 | **历史级高并发稳如磐石** | ⭐⭐ 零踩踏 | *** ## 五、长期演进:按时间滚动的索引生命周期治理 上述四章解决了"当下"的问题,但广告账户资金数据仍在以每年 20%\~30% 的速度增长。为避免未来重蹈"单索引膨胀"的覆辙,我们规划了长期架构演进方向。 ### 痛点:固定索引的线性膨胀 单一固定索引随数据增长无限膨胀,分片大小终将超出 ES 甜区,查询与 merge 成本持续攀升;且 90% 的查询集中在最近 30 天,老数据白白占用热节点资源。 ### 方案:按季度滚动索引 + 别名  ### **核心收益:** | 维度 | 固定索引 | 季度滚动索引 | 亮点 | | --- | ---- | ------ | --- | | 单分片大小 | 随数据无限增长 | **恒定在甜区** | ⭐⭐⭐⭐ 性能长期稳定 | | 近期查询 | 扫描全量 | **时间裁剪,仅命中近期索引** | ⭐⭐⭐ 扇出大幅减少 | | 冷热分离 | 全在热节点 | **老季度迁 warm/cold 节点** | ⭐⭐ 降存储成本 | | 过期清理 | `DELETE BY QUERY`(慢) | **直接删整个季度索引**(秒级) | ⭐⭐ 运维零负担 | 配合 ES ILM(Index Lifecycle Management)策略,可实现索引从 hot → warm → cold → delete 的全自动生命周期流转,让集群具备可持续的水平扩展能力。 *** ## 六、结语:大促备战的四层修炼 回看这场备战,治理的层次逐级深入: 1. **基建层**——用自研异构同步,把对的数据放到对的引擎; 2. **调优层**——深入 Lucene 底层机制,从评分、缓存、倒排链、索引命中降低查询开销; 3. **架构层**——在极端场景下识别数据倾斜盲区,用轻重解耦实现算力隔离; 4. **演进层**——用时间维度的滚动索引,为未来的持续增长预留架构弹性。 **技术债不会消失,只会在峰值时刻连本带息地爆发。** 大促备战的真正价值,不在于临时加了多少机器,而在于借这个契机,把平时"能跑就行"的系统,重构成"高压下依然稳如磐石"的架构。
上一篇:输出感知的指令遵循编码
下一篇:女娲:AI Bug修复数字员工介绍
jd****
文章数
1
阅读量
3185
作者其他文章
01
【广告投放-大促备战】存储性能优化篇 -- 从异构同步到链路解耦的 JES 治理实战
广告财务资金:50+亿文档的JES深水区性能优化大促备战从来不是简单的”加机器、扩容量”。当广告账户资金的核心链路撞上亿级单表体量、50+亿文档的 ES 集群、以及月末/月初 头部 KA 客户的查账洪峰时,任何一个未被治理的技术债都可能在峰值时刻被放大成一场客诉级/资损级的故障。本文深度复盘广告领域-账户资金团队在本次大促备战中,围绕 JES 完成的四项核心治理工作:1、异构同步基建的自研组件;2
jd****
文章数
1
阅读量
3185
作者其他文章
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号