支付系统里,缓存不是“锦上添花”,而是“性能底座”。
缓存使用场景
常见用途:
- 用户信息缓存
- 消息队列
- 分布式锁
- 防重消费
- 限流
- 地理位置
- 布隆过滤
缓存淘汰策略
- 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
}
小结
缓存的价值在于提升性能,但风险在于一致性和稳定性。要让缓存成为“加速器”,而不是“事故源”。