缓存设计之探了又探

💡《从"读多写少"到"读多写多":一次架构演进的深度复盘》

本文记录真实的业务演进过程:系统从"读多写少"的简单场景,逐渐演化为夜间任务的"读多写多"场景,导致原设计框架失效。
最终,我们通过 CQRS + 双轨模型 + 分段缓存 重新构建架构,使系统在性能、一致性、可扩展性上重新平衡。

目录

  1. 背景:业务为何突然"突变"?
  2. V1 架构:典型"读多写少"系统
  3. 业务变化:夜间裂变任务导致写多
  4. 问题分析:环依赖 + 缓存不合理 + 写压
  5. 架构重建:CQRS + 双轨模型
  6. 分段缓存:缓存不是一锅炖
  7. 最终架构(附全链路流程图)
  8. 总结:系统架构必须服务业务变化

1. 背景:业务为何突然"突变"?

最初我们的业务只有一个特点:

✔ 白天大量读取

✔ 写极少

(读多写少的典型场景)

例如基础档案、BOM 属性树、价格基础表等

然而业务方后期引入一个模型:

🌙 夜间裂变任务

某条基础数据变更后,会向上级、关联业务单据、关联子 BOM 全量传递引起 N 层价格链条变化。

在业务上,这就像原子链式反应 ------ 裂变

结果:

  • 白天依旧读多写少
  • 夜间突然变成写多写爆

这导致原本的架构从根上失效。

2. V1 架构:典型"读多写少"缓存架构

Hit Miss Hit Miss Client Request JVM 缓存 Return Redis DB 回填 Redis + JVM

特点:

  • 本地缓存 + Redis 二级缓存
  • 更新事件少,缓存结构简单
  • DB 只承担少量写

这套体系在读多写少场景非常稳。当然在集群环境内我们需要MQ进行Topic广播进行触达集群变更。

3. 业务变化:夜间裂变任务导致瞬时写爆

❗ 一个小小的基础价格变化 → 会触发 N 层业务数据回写

例如:

  • 改了一个 BOM 的价格

    → 它是别的 BOM 的子集

    → 所有上级 BOM 也要重算

    → 它们再触达更上层 BOM

    → ......

这不是简单写,而是"传染式写",数量可以从 1 变成 10,000。

夜间定时任务开始执行后:

  • DB 写压力骤增
  • Redis 写入大量 key
  • JVM 缓存瞬间失效密集
  • 更新链路反复触发

当然实际上的触达源有很多:账务、成本、工艺、工时、薪资调整等业务都会触发类似的回写

原有"读多写少"架构被彻底击穿。

4. 问题分析:本质是三大错误假设

(1) ❌ 有向有环的业务结构

业务数据修改基础数据 → 基础数据又影响业务数据

形成有向环 → 极易出现数据动荡、难以维护一致性。

最终我们做了正确选择:

必须打断环,引入中间层表单(中间结果)。

(2) ❌ 写被低估

早期架构完全按"写少"设计,没有考虑高并发写。

夜间任务一来,写量瞬间放大数百倍。

(3) ❌ 缓存策略不适配

白天缓存读写少 → ok

夜间缓存大量写入 → 缓存层来不及同步、更新量太大、全量刷新困难

5. 最终架构解决方案:CQRS + 双轨模型

这两个是架构升级的核心。

✔ CQRS:Command Query Responsibility Segregation

核心思想:读和写是两条完全不同的业务轨道。

写模型(Command Model)

  • 最终一致即可
  • 允许异步操作
  • 允许队列缓冲
  • 数据结构适合写优化

读模型(Query Model)

  • 为"读性能"设计
  • 可高度缓存化
  • 可预计算、可反范式化

可以用一张图概括:
事件 Command 接口 写库 Query 接口 读库/缓存

✔ 双轨模型(Two-Track Model)

我的理解如下:

Track A(白天在线业务)

  • 稳定
  • 缓存主导
  • 响应实时用户

Track B(夜间离线计算)

  • 不走缓存
  • 直接操作 DB
  • 集中计算、批处理
  • 计算完成后 → 统一刷新缓存

架构图如下:
夜间轨道->离线计算 直接写 DB 定时任务批处理 计算完成后触发全量缓存刷新 白天轨道->在线 读模型缓存层 用户请求 响应

这样就实现了:

✔ 白天响应快(缓存驱动)

✔ 夜间计算准(DB直写)

✔ 两条轨道互不干扰


6. 分段缓存:缓存不是一锅炖,而是结构化缓存

为什么需要分段缓存?

因为"缓存整张表"是最愚蠢的做法:

  • 写入一个字段,需要刷新整份缓存
  • 修改一条数据,会 invalid 全表缓存
  • 放在 Redis 时会造成巨大 key
  • JVM 缓存容易被大对象撑爆

正确做法是:

✔ 缓存拆成"段"(Segment)

例如:

段名 领域
S1 BOM 基础信息(变化少)
S2 BOM 价格段(变化中等)
S3 裂变需要的中间结果(变化多)
S4 业务计算依赖的临时缓存(变化快)

结构如下:
Redis BOM 基础段 价格段 裂变中间段 临时高速段

这就如图业务中台内的大量数据,进行清洗切割分段后,形成细碎的指标,进入数据中台。

为什么这样划分?

✔ 不同变化频率 → 不同缓存策略

✔ 不同冷热程度 → 不同 TTL

✔ 不同业务用途 → 不同数据结构

🔹 Segment 1:基础段(稳定)

特点:

  • 变化极少
  • 可永久缓存
  • 可 JVM + Redis 双缓存

数据结构:

json 复制代码
{
  "bom_id": 1001,
  "name": "发动机模块",
  "category": "机械",
  "version": 3
}

🔹 Segment 2:价格段(中等变动)

适合:

  • Map 结构(可更新单字段)
  • Redis Hash
  • 分字段更新

例:

tex 复制代码
HSET price:1001 material_cost 12.3 labor_cost 8.1 overhead_cost 4.0

🔹 Segment 3:裂变段(变化频繁)

特点:

  • 写多
  • 夜间高并发
  • 用 Redis 的必要性一般
  • 更适合不缓存或使用短 TTL(比如 10 分钟)

数据结构:

json 复制代码
{
   "bom_id": 1001,
   "fission_pending": true,
   "dependents": [1005, 1006, 1010]
}

🔹 Segment 4:临时高速段(极高变化 / 瞬时热点)

适合:

  • W-TinyLFU 思路
  • 用来缓存 1 小时内的热点 key
  • 防止夜间裂变任务"扫库"带来的缓存污染

数据结构:

json 复制代码
{
    "bom_id": 1001,
    "recent_access": 182
}

6.5一些拓展:🆚 秒杀系统 vs 12306:同样高 QPS,为何架构完全不同?

在理解缓存之前,必须理解一个事实:

高并发不是架构的本质,本质是 "业务形态" 决定架构。

💥 秒杀业务:

目标:顶住流量 → 提前准备 → 不求数据强一致,允许最终一致

典型特征:

  • 读远大于写
  • 数据高度可缓存(商品详情、库存标识)
  • 允许"先冻结库存 → 后异步扣减"
  • 允许失败(未抢到)
  • 高峰集中持续时间短

🚄 12306 业务:

目标:绝对一致 → 一个位置不能卖给两个人

典型特征:

  • 强一致(不能超卖)
  • 查询高、写高、锁竞争高
  • 庞大的路径规划
  • 动态路由分流
  • 不可用缓存票信息
  • 请求分散但总量巨大

所以两种架构完全不同。

🧩 秒杀架构(缓存 + 削峰 + 异步)

库存充足 库存不足 用户请求 CDN 缓存静态页 Nginx 限流 Token桶/漏斗限流 Redis 预库存 MQ 异步扣减库存 DB 最终落库 抢购失败

设计思想:

  • 一切往缓存中推:页面、库存、活动状态......
  • 强烈依赖 Redis + 预库存
  • DB 只承担最终写(削峰)
  • "一致性"可以接受延迟(最终一致)

🧩 12306 架构(强一致 + 路由 + 锁)

节点3 节点2 节点1 座位查询/加锁 节点3 订单系统 DB 集群 座位查询/加锁 节点2 订单系统 DB 集群 座位查询/加锁 节点1 订单系统 DB 集群 用户请求 LVS/负载均衡 全国路由调度层

设计思想:

  • "不可缓存"票务数据
  • 必须锁,必须串行,必须强一致
  • 通过 OSFP/LVS 等路由把请求打散
  • 分布式事务(TCC/2PC)补偿
  • 查询大,写也大,是典型强一致高并发系统

🧠 设计思想对比:为什么架构不同?

业务形态决定架构 秒杀 12306 可缓存 最终一致 读多写少 可接受失败 异步削峰 Redis/缓存为核心 不可缓存 强一致 读多写多 不可失败 分布式锁 路由/调度为核心

一句话总结:

秒杀在"扛流量",12306 在"保一致性"。
一个靠缓存,一个靠调度与锁。
所以架构路径完全不同。

7. 最终架构图

夜间_离线轨 白天_在线轨 直接写 DB 裂变任务 计算完成 统一刷新分段缓存 读模型 用户请求 S1 基础段 S2 价格段 S4 临时热点段 业务响应


8. 总结:架构必须服务业务,而不是业务迁就架构

本次演进体现三个关键点:

① 架构必须跟随业务模式

读多写少 → 读多写多 → 架构必须变化。

② CQRS + 双轨模型是应对业务突变的最佳实践

白天高性能,夜间高一致性,两条路分开走。

③ 缓存不是越大越好,而是越"精细分段"越稳定

不同段不同策略,缓存才真正有价值。

相关推荐
神算大模型APi--天枢6461 天前
国产硬件架构大模型算力服务平台:本地化部署与标准端口开发的创新实践
大数据·人工智能·科技·深度学习·架构·硬件架构
九河云1 天前
华为云 GaussDB 分布式架构解析:企业级数据库高可用与性能调优方案
分布式·架构·华为云·云计算·gaussdb
上海云盾-小余1 天前
DDoS防护方案性价比分析
人工智能·安全·web安全·架构·ddos
canonical_entropy1 天前
对于《目前程序语言与软件工程研究中真正严重的缺陷是什么?》一文的解读
后端·架构·领域驱动设计
北极象1 天前
Electron 通用技术架构分析
javascript·架构·electron
GIOTTO情1 天前
技术深度:Infoseek 舆情监测的多模态架构与二次开发实战,破解 AI 生成式舆情痛点
人工智能·架构
Dolphin_Home1 天前
【实用工具类】基于 Guava Cache 实现通用 Token 缓存工具类(附完整源码)
spring·缓存·guava
古城小栈1 天前
缓存界三座大山:穿透、击穿、雪崩
缓存
前端小白在前进1 天前
★力扣刷题:LRU缓存
spring·leetcode·缓存