缓存

来自网络:缓存更新的套路 | 酷 壳 - CoolShell

其实并不只是软件架构里的 MySQL 数据库和 Memcache/Redis 的更新策略,这些东西都是计算机体系结构里的设计,比如 CPU 的缓存,硬盘文件系统中的缓存,硬盘上的缓存,数据库中的缓存。基本上来说,这些缓存更新的设计模式都是非常老古董的,而且历经长时间考验的策略,所以这也就是,工程学上所谓的 Best Practice,遵从就好了。

更新缓存的的 Design Pattern 有四种:

  1. Cache aside
  2. Read through
  3. Write through
  4. Write behind caching

0 错误做法

看到好些人在写更新缓存数据代码时,先删除缓存,然后再更新数据库,而后续的操作会把数据再装载的缓存中。然而,这个是逻辑是错误的。

试想,两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了。

1 Cache Aside Pattern

  1. 失效:应用程序先从 Cache 取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  2. 命中:应用程序从 Cache 中取数据,取到后返回。
  3. 更新:先把数据存到数据库中,成功后,再让缓存失效。
    Cache Aside Pattern.png

2 Read / Write Through Pattern

更新数据库(Repository)的操作由缓存自己代理了,所以,对于应用层来说,就简单很多了。
可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的 Cache。

  1. Read Through:Read Through 套路就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),Cache Aside 是由调用方负责把数据加载入缓存,而 Read Through 则用缓存服务自己来加载,从而对应用方是透明的。
  2. Write Through:Write Through 套路和 Read Through 相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由 Cache 自己更新数据库(这是一个同步操作)。
    ReadWriteThroughPattern.png

4 Write Behind Caching Pattern

Linux 文件系统的 Page Cache 的算法。
在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。

  1. 这个设计的好处就是让数据的 I/O 操作飞快无比(因为直接操作内存嘛 ),因为异步,write backg 还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。
  2. 但是,其带来的问题是,数据不是强一致性的,而且可能会丢失(我们知道 Unix/Linux 非正常关机会导致数据丢失,就是因为这个事)。
    Write Behind Caching Pattern.png

5 总结

在软件设计上,我们基本上不可能做出一个没有缺陷的设计,就像算法设计中的时间换空间,空间换时间一个道理,有时候,强一致性和高性能,高可用和高性性是有冲突的。软件设计从来都是取舍 Trade-Off。

另外,Write Back 实现逻辑比较复杂,因为他需要 Track 有哪数据是被更新了的,需要刷到持久层上。操作系统的 write back 会在仅当这个 cache 需要失效的时候,才会被真正持久起来,比如,内存不够了,或是进程退出了等情况,这又叫 lazy write。