读副本与缓存:一致性、延迟和成本的三方取舍
“加个读副本”和“加个缓存”经常被当成同一种扩容手段,但它们对一致性的影响完全不同,混用会导致一些很难排查的“偶尔读到旧数据”问题。
读副本解决的是读吞吐。主库负责写,副本异步复制,读请求分散到副本。代价是复制延迟:主库写入成功后,副本可能还没收到,立刻读副本会读到旧数据。延迟通常在毫秒到秒级,取决于复制方式和负载。对于“写完立刻读”的场景,读副本会破坏读己之写(read-your-writes)一致性。解决方案包括:写后短时间内强制读主库、用会话粘性把同一用户的读写路由到同一节点、或者在应用层做版本号校验。
缓存解决的是重复读的成本。热点数据放在内存里,避免每次都打数据库。代价是失效策略:缓存和数据库之间有一致性窗口,更新数据库后缓存可能还是旧值。常见模式是“先更新数据库,再删除缓存”,但删除失败或并发读写仍可能导致脏数据。缓存更适合“读多写少、能容忍短时间不一致”的数据,比如商品详情、配置项、排行榜。
两者的组合需要明确优先级。一个常见的错误是“读副本前面再加一层缓存”,结果缓存失效后大量请求打到副本,副本又有复制延迟,用户看到的数据版本比缓存里还旧。更稳的做法是让缓存和副本各管一类数据:强一致要求的读走主库,高吞吐的列表页走副本,热点详情走缓存,并在缓存 key 里带上数据版本号,版本变化时主动失效。
成本维度也不同。读副本增加的是数据库实例和复制带宽,缓存增加的是内存和失效逻辑的复杂度。读副本的扩容相对线性,缓存命中率一旦下降,回源压力会突然放大。评估时要看峰值而不是均值:副本能不能扛住缓存全部失效的瞬间,缓存能不能扛住热点 key 过期时的击穿。
一个实用判断是:如果业务能接受“几秒后一致”,读副本加缓存可以组合使用;如果业务要求“写完立刻读到”,读副本只能用于非关键路径,关键路径读主库;如果数据更新频繁且一致性要求高,缓存可能不值得,读副本加查询优化更简单。先写清楚每类数据的一致性要求,再决定用哪种手段,比先加组件再补一致性逻辑更省事。
- 点赞
- 收藏
- 关注作者
评论(0)