25.微服务缓存设计模式——Cache-Aside、Read-Through、Write-Behind,分布式锁 Redlock 争议与 Redisson 实现

在这里插入图片描述
25 微服务缓存设计模式——Cache-Aside、Read-Through、Write-Behind,分布式锁 Redlock 争议与 Redisson 实现
(接上文《24 微服务缓存:从本地 LRU 到 Redis 集群》)

25.1 三种经典缓存模式
在上一节我们把 Redis 集群搭好、把序列化协议定了,但“有缓存”不等于“缓存好用”。工程上真正决定命中率、一致性、故障面的是“谁来写、谁来读、什么时候回源”。业界把最常见的三种套路抽象成 Cache-Aside、Read-Through、Write-Behind(也叫 Write-Back)。它们不是“要么 A 要么 B”的单选题,而是可以在不同业务域混用的战术组合。

25.1.1 Cache-Aside(旁路缓存)
角色划分:

  • 应用代码 = Cache Client + DB Client
  • Redis 完全被动,不知道 DB 的存在

读写时序:

  1. 读:先读 Redis → miss → 读 DB → 回写 Redis → 返回给用户
  2. 写:先写 DB → 成功后再删缓存(或发消息异步删)

优点:

  • 实现简单,不依赖 Redis 特殊指令
  • 缓存层可随意替换(Memcache、Caffeine 都行)

致命缺陷:

  • 并发读写存在“中间态窗口”:A 写 DB 成功、删缓存;B 在 A 删之前刚好 cache miss,把旧值拉回缓存,导致后续读都脏
  • 双写(DB+Cache)在应用代码里硬编码,事务边界容易漏

缓解方案:

  • 延迟双删:写完 DB 后先删一次,再发延迟消息 500ms 后再删一次
  • 版本号 + 增量回源:缓存里存“数据版本”,回源时比较版本,低则抛弃
  • 直接放弃强一致,用“过期时间”兜底,接受秒级不一致

25.1.2 Read-Through(通读缓存)
角色划分:

  • 应用只和缓存打交道
  • Cache Provider 里注册一个 CacheLoader,miss 时自动回源

Redis 本身没有 Loader 机制,需要上层框架(Spring Cache、Redisson ReadThroughMapping、RedisLabs 的 RedisGears)来做胶水层。

伪代码:

RMap<String, Order> orderMap = redisson.getMap("order", 
        MapOptions.<String, Order>defaults()
                .loader(orderId -> orderRepo.selectById(orderId)));
Order o = orderMap.get("123"); // miss 时框架会回调 loader

优点:

  • 业务代码 0 回源逻辑,符合“单一职责”
  • 缓存侧可做“批量回源”“级联回源”优化

缺点:

  • 首次 miss 的 RT 包含 DB 耗时,毛刺明显
  • 缓存层引入业务 schema,替换缓存产品成本升高

25.1.3 Write-Behind(写回缓存)
角色划分:

  • 应用只写缓存,不写 DB
  • 缓存层把变更刷盘异步化、批量合并

实现要点:

  1. 写缓存时同时写一份“待同步队列”(Redis Stream、Disruptor、MySQL binlog)
  2. 后台线程按时间或数量攒批后写 DB
  3. 刷盘成功再删除队列,失败重试直到超过阈值报警

优点:

  • 写性能 ≈ 纯内存,轻松抗 10w+ TPS
  • 合并热点写,DB 压力降 1~2 数量级

缺点:

  • 数据丢失窗口:宕机时队列里未刷盘的数据就丢了
  • 与 Read-Through 混用时,如果缓存淘汰策略不当,可能把“还没刷盘”的数据踢掉,导致永久丢失
  • 必须引入“顺序号”或“binlog 位点”做幂等,否则重试时 duplicate key 会把 DB 写炸

适用场景:

  • 计数器、点赞、秒杀库存这种“可丢、可合并”的业务
  • 对 RT 极其敏感、但能接受秒级不一致的场景

25.2 分布式锁:Redlock 争议与工程落地
缓存除了“存数据”,还常被顺手当成“分布式锁”。但 Redis 主从异步复制 + fail-over 会导致“同一把锁被两个节点同时拿到”,于是 Redis 作者 Salvatore 在 2015 年提出 Redlock 算法:

  1. 客户端拿到当前时间 T1
  2. 依次向 5 个独立 master 节点用 Lua 脚本抢锁(SET key uuid NX PX 30000)
  3. 当且仅当“多数节点(>=3)成功”且“耗时 < 锁有效时间”才算抢锁成功
  4. 释放时向所有节点发 DEL,仅当 value 是自己 uuid 时才删

争议点:

  • Martin Kleppmann 发文《How to do distributed locking》直指 Redlock 对“系统时钟假设”太强:GC 停顿、NTP 跳变会把步骤 3 的“耗时”计算整失效
  • Redis 作者反击:Redlock 不依赖绝对时钟,只需要“单调时钟”即可;GC 停顿超过 TTL 属于“业务层自己设置 TTL 不合理”,不是算法问题
  • 结论:Redlock 不是“银弹”,在跨机房、容器化、NTP 不稳定的场景下,确实会出现“双主”

25.3 Redisson 的“看门狗”实现
Redisson 没有重复造 Redlock,而是把 Redlock 作为“MultiLock”的一种组合用法,日常更推荐“单机 + 看门狗”模式:

  1. 加锁:
if (redis.call('exists', KEYS[1]) == 0) then 
    redis.call('hincrby', KEYS[1], ARGV[2], 1); 
    redis.call('pexpire', KEYS[1], ARGV[1]); 
    return nil; 
end; 
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then 
    redis.call('hincrby', KEYS[1], ARGV[2], 1); 
    redis.call('pexpire', KEYS[1], ARGV[1]); 
    return nil; 
end; 
return redis.call('pttl', KEYS[1]);
  • 用 Hash 结构存“重入次数”,value=uuid:threadId
  • TTL 默认 30s
  1. 看门狗:
  • 业务线程拿到锁后,Redisson 启动一个 Netty 定时任务,每 10s 续期一次(pexpire 到 30s)
  • 只要业务线程还存活(持有锁的 channel 没断开),就自动续
  • 一旦业务 crash,channel 关闭,续期任务随之取消,30s 后锁自动释放
  1. 红锁(MultiLock):
RLock lock1 = redisson1.getLock("lock");
RLock lock2 = redisson2.getLock("lock");
RLock lock3 = redisson3.getLock("lock");
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
lock.lock(); // 内部就是按 Redlock 算法依次加锁
  1. 读写锁:
    Redisson 还实现了 Redis 版的 ReadWriteLock,读锁完全可重入、写锁排他,通过 Hash+Semaphore 机制实现,适合“读多写少”的缓存重建场景。

25.4 综合选型建议

  • 并发量 < 1k/s、允许秒级不一致:Cache-Aside + 过期时间 足矣
  • 并发量高、读毛刺敏感:读域用 Read-Through,热点 key 加本地 Caffeine 二级缓存
  • 写量高、可丢数:Write-Behind + 队列持久化(Kafka)+ 定期对账
  • 分布式锁:
    – 同机房、主从延迟 < 1ms:Redisson 单机锁 + 看门狗
    – 跨机房、容灾级别高:Redlock(5 独立 master)(能接受极端场景双主风险)或直接用 etcd/zk 的 CP 锁

25.5 小结
缓存模式没有“best”,只有“least worst”。先把读写路径画成时序图,把“不一致窗口”圈出来,再决定用哪种模式;分布式锁能不用就不用,必须用时就接受“锁失效”的兜底方案(幂等、幂重、对账)。下一节我们将进入“缓存与数据库的最终一致性”实战:基于 Canal + Kafka 的异步消息链路透传,以及“缓存击穿、雪崩、穿透”的三板斧防护。
更多技术文章见公众号: 大城市小农民

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乔丹搞IT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值