支付系统里,缓存不是“锦上添花”,而是“性能底座”。

缓存使用场景

常见用途:

  • 用户信息缓存
  • 消息队列
  • 分布式锁
  • 防重消费
  • 限流
  • 地理位置
  • 布隆过滤

缓存淘汰策略

  • FIFO
  • LIFO
  • LRU
  • MRU
  • LFU
  • Random

核心原则:命中率优先,稳定性优先。

缓存更新策略

Cache Aside

逻辑:

  • 读:缓存命中直接返回,未命中读 DB 后写缓存
  • 写:先写 DB,再删缓存

常见并发问题:

  • 写后删缓存,读从从库命中旧数据
  • 并发读写导致脏缓存

最稳做法:写后读主库刷新缓存,降低主从延迟带来的脏读。

Read Through

缓存层自动加载 DB,应用层透明。

Write Through

同时写缓存与 DB,保证一致性但写延迟高。

Write Around

只写 DB,不写缓存,适合写多读少。

Write Back

只写缓存,异步落库,吞吐高但一致性弱。

常见线上问题

问题一:在 DB 事务里更新缓存

症状:

  • DB 事务耗时变长
  • TPS 下降
  • Redis 慢导致 DB 被拖死

解决:把缓存更新移出 DB 事务。

问题二:写后删缓存导致脏读

原因:读走从库,主从延迟导致读到旧数据。

解决:写后读主库刷新缓存,或写后延迟双删。

问题三:Redis hashtag 导致单点过载

症状:相同 hashtag 导致 key 落入同一 slot。

解决:去掉 {} 或重新设计 key。

问题四:Redis 作为 MQ 出现 hot key

症状:单队列 key QPS 过高,节点过载。

解决:拆分队列或切换到 Kafka。

问题五:缓存锁未释放

错误示例:

func DoBusiness(p Param) error {
  lock := getCacheLock()
  if err != nil {
    return err
  }

  err := DoSomething()
  if err != nil {
    return err
  }

  lock.release()
  return nil
}

正确示例:

func DoBusiness(p Param) error {
  lock, err := getCacheLock()
  if err != nil {
    return err
  }
  defer lock.release()

  if err := DoSomething(); err != nil {
    return err
  }
  return nil
}

小结

缓存的价值在于提升性能,但风险在于一致性和稳定性。要让缓存成为“加速器”,而不是“事故源”。