没事瞎思考

没事瞎思考

缓存的基本套路

作为程序员,缓存都会碰到,然而在实践中,准确无误的用好缓存并不容易,大家容易忽略某些细节部分。本文特意根据自己的经验和同事的讨论,总结出来的几条基本原则(套路)供大家参考:

1 缓存策略

建议大家选取较小粒度的缓存,最好跟数据库的实体模型对应。拿一个论坛网站来说,用分页作为缓存的粒度是不合理的:

  • 有过多冗余,论坛帖子不但有「最新」还有「最热」
  • 缓存的命中率低,因为改变任何一个帖子,整页都失效了

2 缓存更新策略

基本两种思路

  • 更新数据的时候, 清除 缓存,读取的时候,载入新的缓存
  • 更新数据的时候, 更新 缓存

2.1 更新的时候清除缓存

这是我常用的方式,也是首选方式,因为它实现起来简单。然而还是一个小小的细节需要注意的,「先更新数据,再清除缓存」 还是 「先清除缓存,再更新记录」。对于这个问题,我几乎可以肯定的是你一定可以答对,因为作为问题提出来,总会触发我们的思考。然而写代码的时候,我们的潜意识可能导致我们犯错。

「先清除缓存,再更新记录」在并发情况下,是有问题的,最简单的一类情况就是:

假设 2 个线程,A 读数据,B 写数据,考虑下列执行顺序:

  1. B 清除缓存
  2. A 读取数据, 没有缓存,载入缓存
  3. B 更新数据

上述这种情况会导致 B 新写入的数据没有更新到缓存中,因为 A 的读取早于 B 的更新。

2.2 更新的时候更新缓存

这种思路比起前面一种,复杂很多,需要考虑的情况也多了很多。要确保缓存的数据跟实际数据的一致性,同样的,也有两种方式:

  • 确保实际数据的更新和缓存更新的步调一致
  • 缓存根据实际数据的版本号更新

第一种方式,可以采取锁机制,然而这种方案并不高效,而且也无法使用在分布式系统中。不过这种方式很适合读写分离的架构,关于读写分离和缓存,这里不打算详细讨论,篇幅太大。

第二种方式通常在更新数据的时候,更新版本号,并获得这个版本号,带着版本号去试图更新缓存,这种方式得确保缓存的更新和版本的大小比较是个原子操作,对于 Redis 来说,需要写 lua 脚本了。

2.3 哪种更新策略好?

这个没有答案,不过我喜欢第一种。

Comments

comments powered by Disqus