1.6 缓存
缓存是临时的存储空间,用于存储一些很耗时的响应结果或者内存中经常被访问的数据,这样后续再访问这些数据时能更快。如图1-6所示,每次加载一个新网页,都要执行一个或者多个数据库请求来获取数据。不断向数据库发送请求会使应用的性能受到很大影响,而缓存可以缓解这种情况。
1.6.1 缓存层
缓存层是一个临时数据存储层,比数据库快很多。设置独立缓存层的好处有:提高系统性能,减轻数据库的工作负载以及能够单独扩展缓存层。图1-7展示了一种设置缓存层的方式。
图1-7
当收到一个请求时,Web服务器首先检查缓存中是否有可用的数据:如果有,Web服务器就直接将数据返回给客户端;如果没有,就去查询数据库并把返回的响应存储在缓存中,再将其返回给Web服务器。这种缓存策略叫作通过缓存读(Read-through Cache)。根据数据的类型、大小和访问模式,可以采用不同的缓存策略。在网站Codeahoy上有一篇文章“Caching Strategies and How to Choose the Right One”,解释了不同的缓存策略是如何工作的。
大部分缓存服务器都为常见的编程语言提供了API,与其进行交互很简单。下面的代码段展示了典型的Memcached API:
1.6.2 使用缓存时的注意事项
使用缓存时有以下几点需要注意:
• 决定什么时候应使用缓存。如果对数据的读操作很频繁,而修改却不频繁,则可考虑使用缓存。因为被缓存的数据是存储在易变的内存中的,所以缓存服务器不是持久化数据的理想位置。比如,如果缓存服务器重启,其中的所有数据就会丢失。因此,重要的数据应该保存在持久性的数据存储中。
• 过期策略。执行过期策略是好的做法。一旦缓存中的数据过期,就应该将其从缓存中清除。如果不设置过期策略,缓存中的数据会一直被保存在内存中。通常建议不要把过期时间设得太短,因为这样会导致系统不得不经常从数据库重新加载数据;当然,也不要设得太长,这样会导致数据过时。
• 一致性:这关系到数据存储和缓存的同步。当对数据的修改在数据存储和缓存中不是通过同一个事务来操作的时候,就会发生不一致。当跨越多个地区进行扩展时,保持数据存储和缓存之间的一致性是很有挑战性的。如果你感兴趣,可以阅读Facebook的文章“Scaling Memcache at Facebook”。
• 减轻出错的影响:单缓存服务器是系统中的一个潜在单点故障(Single Point Of Failure,SPOF)(如图1-8所示)。在维基百科中,单点故障的定义如下:“单点故障是指系统中的某一部分,如果它出现故障,整个系统就不能工作”。所以,推荐的做法是在不同的数据中心部署多个缓存服务器以避免单点故障。另一个推荐的做法是为缓存超量提供一定比例的内存,这样可以在内存使用量上升时提供一定的缓冲。
• 驱逐策略:一旦缓存已满,任何对缓存添加条目的请求都有可能导致已有条目被删除,这叫作缓存驱逐。LRU(Least-Recently-Used,最近最少使用)是最流行的缓存驱逐策略。也可以采用其他缓存驱逐策略,比如LFU(Least Frequently Used,最不经常使用)或者FIFO(First In First Out,先进先出),以满足不同的使用场景。
图1-8[5]