19. 缓存
负载均衡帮助你在数量增加的服务器中横向扩容,但是缓存将允许你充分利用你已经拥有的资源,同时可以使得原本不可能的产品需求变成可能。缓存利用局部性原理:最近被请求的数据可能再次被请求。几乎在计算的每一层都用到缓存:硬件、操作系统、网络浏览器、网络应用等。缓存如同短期记忆:它的空间优先,但是通常比原始数据源更快,且包含最近访问的项目。
缓存可以在架构的任何层出现,但是通常在最靠近前端的层出现,这里实现缓存的目的是在不访问更低层的情况下快速返回数据。
1. 应用服务器缓存
将一个缓存直接放置在请求层节点上允许响应数据的本地存储。每次向服务发送请求时,如果有本地缓存数据,节点将快速返回该缓存数据。如果缓存中没有该数据,请求节点将从磁盘查询数据。一个请求层节点上的缓存也可以放置在内存中(速度非常快)以及放置在节点的本地磁盘上(比访问网络存储更快)。
如果扩展到多个节点会发生什么?如果请求层扩展到多个节点,仍然非常有可能使每个节点拥有其单独的缓存。然而,如果负载均衡器在节点中随机分布请求,相同的请求可能到达不同的节点,导致增加缓存未命中的情况。消除这个障碍的两个选项是全局缓存和分布式缓存。
2. 内容分发网络(CDN)
内容分发网络是一种用于处理大量静态媒介的网站的缓存。典型的内容分发网络设置中,请求将首先向内容分发网络请求一份静态媒介,如果本地有该内容的缓存,则内容分发网络将提供该内容。如果本地没有该内容的缓存,内容分发网络将查询后端服务器寻找该文件,将其在本地缓存,并将其提供给请求用户。
如果我们建立的系统没有大到拥有其独立的内容分发网络,为了方便将来的迁移,我们可以使用像 Nginx 这样的轻量级 HTTP 服务器,将静态媒介存放在一个独立的子域名上(例如 static.yourservice.com),后续将域名系统从服务器转换到内容分发网络。
3. 缓存失效
虽然缓存机制很好,但是也需要维护工作,保持缓存和源数据(例如数据库)一致。如果数据库中的数据被修改,该数据在缓存中应该失效,否则将引起应用的不一致行为。
解决这个问题的方法称为缓存失效,有三种主流方案:
直写(write-through)缓存:该方案下,数据同时写入缓存和相应的数据库。缓存数据允许被快速获得,由于相同的数据被写入永久存储,我们可以确保缓存和存储之间的数据完全一致。另外,这个方案确保当发生宕机、停电或者其他系统中断时不会有数据丢失。
虽然直写方案中的每个写操作必须执行两次才能给客户端返回成功,数据丢失的风险降到最低,但是该方案的缺点是写操作的延迟更高。
绕写(write-around)缓存:这个技术和直写缓存相似,但是数据绕过缓存直接写入永久存储。这样可以减少缓存被不会重新访问的写操作填满的情况,但是缺点是读取最近写入的数据时会出现缓存缺失,必须从更慢的后端存储读取数据,导致更高的延迟。
回写(write-back)缓存:该方案中,数据只写入缓存,由客户端立即确认完成。永久存储的写操作只在特定区间或特定条件下才会执行。对于写密集的应用,该方案可以降低延迟和提升吞吐量,但是速度提升的同时,由于写入数据的唯一备份在缓存中,因此如果发生宕机或者其他有害事件,将有数据丢失的风险。
4. 缓存淘汰策略
以下是一些最常见的缓存淘汰机制:
先进先出(FIFO):缓存将最先被访问的项目淘汰,不考虑之前被访问的频率和次数。
后进先出(LIFO):缓存将最后被访问的项目淘汰,不考虑之前被访问的频率和次数。
最近最少使用(LRU):最先淘汰最近最少使用的项目。
最近最多使用(MRU):和最近最少使用相反,最先淘汰最近最多使用的项目。
最不经常使用(LFU):计算一个项目需要的频率,最先淘汰频率最低的项目。
随机替换(RR):当需要留出空间的时候,随机选择一个备选项目并将其淘汰以留出空间。
以下链接有一些关于缓存的高质量讨论:
Last updated