名词解释
- LRU:Least Recently Used的缩写,一种缓存替换算法。
缓存系统的应用场景
缓存是一种可以提高系统读性能的常见技术,对于读多于写的应用场景,经常使用缓存来进行系统性能优化。
缓存架构
- 缓存服务化。对于调用方来看,缓存层和数据存储层提供统一的服务接口。调用方不用处理缓存层和数据存储层的数据一致性问题,全权交给统一的缓存服务来做。统一的缓存服务来保证缓存和数据存储层之间的数据一致性。
- 异步缓存更新。业务层所有的写操作都走数据库,所以的读操作都走缓存,由一个异步的工具来做数据库与缓存之间的数据同步。
缓存更新策略
先提出一个问题,业务层对缓存和数据库操作的时序性是什么,即先操作数据库还是先操作缓存?接下来我们逐个分析。
先更新缓存,再更新数据库。
考虑这样一种场景,两个并发操作,一个是更新操作,一个是查询操作。更新操作删除缓存,查询操作没有命中缓存,先把旧数据读出来后放入缓存,然后更新操作更新了数据库。此时缓存中依然是旧数据,导致缓存中的数据是脏的,并且一直无法得到更新。
先更新数据库,再更新缓存。
考虑同样的场景,两个并发操作,一个是更新操作,一个是查询操作。更新操作更新数据库,查询操作命中缓存把旧数据读出,最后更新操作删除并更新缓存。查询操作虽然查询到了旧数据,但是下一次即可从缓存中查询到数据库同步的真实数据。
Cache Aside Pattern
- 失效:应用程序先从缓存中读取数据,没有得到,则从数据库中读取,成功后,放到缓存中。
- 命中:应用程序从缓存中读取数据,取到后返回。
- 更新:先把数据存到数据库中,成功后,再让缓存失效。
这是最常用和相对靠谱的方法。那么 Cache Aside 会不会因为并发问题导致的出现脏数据呢?也会。比如,一个读操作先到,没有命中缓存,然后就到数据库中读取。这时来了一个写操作,写完数据库后,然缓存失效。然后,之前的读操作再把旧数据写到缓存中,还是会造成脏数据。
但是,这个case出现的概率非常低。这个场景需要发生在读缓存时缓存失效,并发并发着有一个写操作。而实际上数据库的写操作比读操作慢得多,而且还要锁表,而读操作需要在写操作之前进入数据库操作,又要在写操作完成后更新缓存,所有的这些条件都具备的概率并不大。所以,Cache Aside Pattern 还是相对靠谱的方式。
总结
陈皓也说过,在软件开发或设计中,非常建议在开始之前先去参考一下已有的设计和思路,看看相应的guildline, best practice 或 design pattern。吃透了已有的这些东西,在决定是否要重新发明轮子。千万不要似是而非的,想当然的做软件设计。
延伸
- 计算机体系结构设计中的CPU缓存,linux文件系统中的缓存,数据库中的缓存更新策略。
- Unix/linux中经典的系统设计思路,best practice / desigh pattern等。