数据库成为瓶颈后,缓存如何加速读请求

 

缓存的定义

与其说是定义,不妨在心中有如下概念:
凡是位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异结构,均可称之为缓存。
 
缓存是一种思想,用于协调速度差异,缓存作为一种常见的,空间换时间的性能优化手段,在很多地方都有应用。
下图就是业内人士总结的有关硬件组件的延时情况数据:
notion image
 
从上图的数据中我们可以做这样一个贴近实际生活的类比:
从这些数据中,你可以看到,做一次内存寻址大概需要 100ns,而做一次磁盘的查找则需要 10ms。 如果我们将做一次内存寻址的时间类比为一个课间,那么做一次磁盘查找相当于度过了大学的一个学期。
由此可见,我们使用内存作为缓存的存储介质相比以前用磁盘作为存储介质,性能上提高了一个数量级,同时也能够支撑更高的并发量。所以,内存是最常见的一种缓存介质。
 

缓存案例

  1. Linux 的内存管理是通过 MMU(Memory Management Unit)的硬件,实现从虚拟地址到物理地址的转换。转换会存在复杂的计算消耗,所以我们借助一个叫做 TLB(Translation Lookaside Buffer)的组件来缓存最近转换过的虚拟地址到物理地址的映射。
    1. TLB 就是一种缓存组件,缓存复杂的运算结果。
  1. 比如平常刷的抖音,通常打开抖音,不仅仅会缓存第一个推送的视频,同时还会缓存两三个视频。这样在看第二个、第三个视频的时候就可以给用户「秒开」的感觉。
  1. HTTP 也有缓存机制。比如请求一个图片,第一次响应结果中返回 Etag Header,浏览器会缓存图片信息和这个字段的值。当下一次请求这个图片的时候,浏览器发起的请求头里会有 If-None-Match 的字段,并把缓存的 Etag 的值写进入发送给服务端。服务端对比图片信息是否有变化,如果没有变化,服务端返回 304 的状态码,浏览器会继续用缓存的图片信息。通过这种缓存协商的方式,可以减少网络传输的数据量大小,从而提升页面展示的性能。
    1. notion image
 

缓存与缓冲区

缓存用来提高低速设备的访问速度,或者减少复杂耗时的计算带来的性能问题。理论上来说,可以通过缓存解决有关「慢」的一切问题。
缓冲区则是一块临时存储数据的区域,这些数据后面会被传输到其他设备上。缓冲区更像「消息队列篇」中即将提到的消息队列,用以弥补高速设备和低速设备通信时的速度差
比如,我们将数据写入磁盘时并不是直接刷盘,而是写到一块缓冲区里面,内核会标识这个缓冲区为脏。当经过一定时间或者脏缓冲区比例到达一定阈值时,由单独的线程把脏块刷新到硬盘。这样避免了每次写数据都要刷盘带来的性能问题。
notion image
 

缓存分类

  • 静态缓存 如 Nginx
  • 分布式缓存 如 Redis、Memcached
  • 热点本地缓存 HashMap,日常工作中,我们会对 Python 做进程内缓存 @lru_cache
 

缓存的不足

通过上面的内容,我们知道了缓存的作用主要是提升访问速度,从而扛住更高的并发。但是凡事均有两面性,缓存也不例外,我们不仅要了解其优势,同时也要知道其不足,从而扬长避短,将它的作用发挥到最大。
  1. 缓存适合读多写少的场景,并且数据最好带有一定的热点属性
  1. 增加缓存会给系统带来额外的复杂度,并且有数据不一致的风险
  1. 缓存通常采用内存作为存储介质,但是内存并不是无限的
  1. 缓存也会给运维带来一定的成本
即使缓存有诸多不足,但是做架构设计时也需要把它考虑在内,只是在做具体方案的时候对缓存的设计有更多细致的思考,才能最大化地发挥缓存的优势。
 

总结

  • 缓存可以有多层,如上面提到的静态缓存处在负载均衡层,分布式缓存处在应用层和数据库层之间,本地缓存处在应用层。我们需要将请求尽可能挡在上层,因为越往下层,对并发的承受能力越差
  • 缓存命中率是我们对于缓存最重要的一个监控项指标,越是热点的数据,缓存的命中率越高
 
缓存不仅仅是一种组件的名字,更是一种设计思想。任何一种能够加速读请求的组件和设计方案都是缓存思想的体现,这种体现通常由以下两种方式来实现:
  • 使用更快的介质,比如前面所说的内存
  • 缓存复杂的运算结果,比如前面所说的 TLB 的例子就是缓存地址转换的结果;再比如递归方式实现的斐波那契数列,使用缓存前面运算结果的方式来加快运算速度。
当工作中碰到「慢」的问题时,「缓存」就是你第一时间需要考虑的