架构师必知必会系列:多层次缓存与缓存策略

发布于:2023-09-27 ⋅ 阅读:(70) ⋅ 点赞:(0)

作者:禅与计算机程序设计艺术

1.简介

在互联网服务架构中,缓存通常被用来提升系统性能和降低延迟,它是指将用户请求重复的计算结果进行存储,以便后续访问时直接获取结果而不需要重新计算,从而提高用户体验、减少服务器压力和节省成本。 多层级缓存(multi-level cache)可以有效降低后端数据库查询次数,加快客户端响应速度。多层级缓存机制除了应用于对数据库的查询外,还包括本地内存缓存、CDN缓存等。缓存往往以硬件、软件或中间件的方式实现,常用的开源软件如Redis、Memcached、Varnish都可以用来构建多层级缓存。

但是,如何合理配置缓存策略,才能达到最优效果?如何避免过度设计导致缓存失效?如何更好的利用缓存资源?这些问题就是本系列文章要探讨的内容。

本文先回顾一下缓存的常用技术方案及其特点,包括单机缓存、分布式缓存、数据库缓存和浏览器缓存。然后详细阐述多层级缓存的工作原理,并通过常用缓存策略介绍各个层级缓存的作用,帮助读者更好地理解缓存的作用和价值。最后给出一些性能优化的方法,供读者进一步提升系统整体的运行效率。

2.缓存技术概览

(1)单机缓存

单机缓存是最简单也最常见的缓存方式。当用户第一次访问某个页面的时候,需要从后端数据库读取数据并生成缓存。后续用户访问相同页面时,就直接返回缓存数据,无需再向后端数据库请求数据,因此可以提升网站的响应时间。

优点:

  1. 部署简单,占用资源小
  2. 可靠性高,缓存数据永远不会过期

缺点:

  1. 性能受限,缓存服务只能处理部分用户请求,其它请求仍需要转向后端数据库
  2. 如果后端数据库经常更新,缓存可能很久不能反映最新的数据
  3. 需要担心缓存空间不足,一旦缓存服务重启,所有的缓存数据都会丢失

(2)分布式缓存

分布式缓存又称为集群缓存,是一种比较常见的缓存模式。采用分布式缓存后,每台机器上只保留一份完整的缓存数据,而且每台机器上的缓存数据互相独立,互不影响。这种缓存模式下,不同的机器之间通过网络通信共享缓存数据,可以有效提高缓存服务的性能。当用户第一次访问某个页面时,需要首先将缓存数据发送给访问者所在的机器,这样访问者就可以直接获得缓存数据,而无需等待其他机器返回缓存数据。

优点:

  1. 避免了单点故障,可容纳更多的缓存数据
  2. 可以应付负载均衡、异地多活场景下的高可用性要求
  3. 有利于减少依赖关系,降低系统耦合程度

缺点:

  1. 分布式缓存不像单机缓存那样可靠,如果主节点宕机,就会造成缓存不可用
  2. 数据同步复杂,缓存数据不同步容易出现不同步的问题
  3. 需要考虑数据一致性问题,需要引入协调器保证数据的一致性

(3)数据库缓存

数据库缓存属于典型的缓存模式。数据库缓存分为两种类型:实体缓存和对象缓存。

实体缓存(entity caching):将从数据库读取的数据存储在内存中,以提高查询速度。例如Hibernate提供的实体缓存(entity cache)。

对象缓存(object caching):将从数据库读取的数据存储在内存中的对象中,以提高查询速度。例如JPA提供的缓存实现。

优点:

  1. 对数据库查询消耗较少,提升响应速度
  2. 支持数据热刷新,缓存更新后立即生效

缺点:

  1. 不支持动态增删改查,缓存更新频繁会带来性能问题
  2. 如果数据库数据发生变化,需要手动更新缓存,代价较高

(4)浏览器缓存

浏览器缓存也是一个常见的缓存机制。浏览器缓存可以分为隐私模式缓存和强制缓存。

隐私模式缓存:即隐身模式下浏览器保存的一些临时数据,比如cookie、本地存储等数据,主要用于保持用户的登录状态。一般隐私模式缓存不会因为关闭浏览器或者清除缓存数据而自动删除,需要手动清除。

强制缓存:由HTTP协议头控制的缓存,比如Last-Modified、If-None-Match、ETag等,浏览器会先请求缓存数据,根据缓存标识判断是否命中强制缓存,如果命中则不会向服务器请求,直接从缓存中取数据;否则才会请求服务器数据。

浏览器缓存有两个条件必须同时满足:

  1. 请求方法和请求头:浏览器会先检查缓存数据是否是最新版本的;只有与当前请求方法匹配且请求头相同的情况下才可以使用缓存。
  2. 校验码验证:如Last-Modified、Etag、If-Modified-Since、If-None-Match。

缓存有效期:

  1. 强制缓存(Expires、Cache-Control):经过指定的时间后缓存会过期,浏览器会向服务器重新请求数据。
  2. 协商缓存(Last-Modified/If-Modified-Since、ETag/If-None-Match):由于浏览器会校验校验码,所以它可以复用之前的缓存副本。

3.多层级缓存概览

多层级缓存是一种提升网站性能的有效手段。它可以有效缓解数据库的查询压力,加速用户请求的响应时间,并且可以在一定程度上避免缓存击穿的问题。

多层级缓存可以分为两个维度:

  1. 本地缓存:指应用服务器本地的内存缓存,主要包括Ehcache、Memcached和Redis等。
  2. 中间件缓存:指基于分布式缓存中间件,例如:Nginx+Varnish、Apache Traffic Server+Squid等。

多层级缓存的工作流程如下图所示:

缓存数据一般按照以下优先级顺序查找:

  1. 本地缓存
  2. 中间件缓存
  3. 后端数据库

多层级缓存可以大幅度降低后端数据库的查询压力,提升网站的响应速度。除此之外,它还可以利用中间件缓存实现跨服务器的缓存共享,并保障缓存数据的一致性。

4.多层级缓存实践

(1)本地缓存——Ehcache、Memcached和Redis

本地缓存是多层级缓存中的最底层缓存。主要包含Ehcache、Memcached和Redis三种软件。

Ehcache

Ehcache是一个纯Java的分布式缓存框架,也是Apache软件基金会的一个开源项目。它提供了多种缓存策略,并能够在多种缓存容器之间共享数据。它提供了多线程环境下的安全机制,支持缓存监听器。

Ehcache的优点:

  1. 高度可扩展性:支持内存缓存、磁盘缓存和集群缓存,可随时添加新缓存。
  2. 具备良好的性能:具有快速、低延迟的读写操作。
  3. 使用简单:配置方便,API友好。
  4. 开放源码:免费使用。

Memcached

Memcached是一个自由和开放源代码的内存缓存服务器,支持多种缓存协议。它可以提供灵活的内存分配策略,允许用户自定义内存管理方式。

Memcached的优点:

  1. 性能优越:Memcached的并行IO处理能力大大优于Ehcache,并且无锁编程模型保证高性能。
  2. 轻量级:占用内存小,启动速度快,适合于分布式环境和负载较低的场合。
  3. 简单:支持多种缓存协议,API易用。

Redis

Redis是一个开源的高性能的key-value存储系统,也是一个基于内存的NoSQL数据库。它支持多种数据结构,如字符串(String),哈希(Hash),列表(List),集合(Set),有序集合(Sorted Set)和位图(Bitmap)。Redis具备单线程和多线程模式,并且可以做持久化。

Redis的优点:

  1. 性能优越:高性能,适合高并发场景。
  2. 数据结构丰富:支持各种数据结构,例如字符串(String),哈希(Hash),列表(List),集合(Set),有序集合(Sorted Set)和位图(Bitmap)。
  3. 数据持久化:支持RDB和AOF两种持久化方式,并提供数据快照功能,可用于数据恢复。
  4. 内存管理能力强:通过有效的算法和数据结构来管理内存,防止内存泄漏。

(2)中间件缓存——Nginx+Varnish

中间件缓存是基于分布式缓存中间件的缓存,它可以部署在Web服务器和其他中间件之间。中间件缓存的原理是在请求到达Nginx时,检查是否有缓存数据。如果有,Nginx直接把缓存数据发送给客户端;如果没有,则根据请求参数查询后端数据库,并将结果存入缓存。Nginx也可以对缓存数据进行更新和维护,比如清除旧的缓存数据,并提前通知客户端更新缓存。

Nginx是一种高性能的Web服务器,其核心工作是处理HTTP请求。它还可以集成各种模块,如图片处理模块、日志处理模块、压缩模块、安全防范模块等。

Varnish是一个高性能的反向代理和网页加速器。它运行在Nginx服务器之后,主要用于缓存静态内容,提升网站的访问速度。它可以对缓存内容进行过滤和修改,并结合流量控制和内容分发网络(CDN)一起使用,提升缓存的命中率和响应时间。

(3)两级缓存的使用规则

为了更准确地应用多层级缓存,应该遵循一些使用规则。

  1. 只缓存查询结果:不要缓存更新、新增、删除等操作,尽量只缓存查询结果。缓存更新导致的数据不一致问题会让缓存雪崩,增加系统复杂度,而查询缓存又可以有效降低后端数据库的查询压力。

  2. 时效性:缓存设置一个合理的过期时间,尽量减少缓存的过期时间,避免缓存一直存在占用内存空间。

  3. 更新策略:针对查询缓存,设定一种合理的更新策略,比如定时轮询、实时监控和页面访问触发等。实时监控可以及时的发现后端数据库数据变动,并及时更新缓存,避免缓存的陈旧问题。

  4. 清除策略:对于过期缓存,设定一种合理的清除策略,比如定时清空缓存、LRU算法淘汰缓存等。缓存淘汰策略是防止缓存过多而导致内存溢出的有效手段。

5.缓存策略

(1)缓存读策略

读缓存的目的就是尽量减少对后端数据库的查询次数,提升网站的响应速度。所以读缓存的策略就至关重要。常见的缓存读策略有以下几种:

  1. Cache Aside Pattern:又称为直写模式,它的基本思路是"先读缓存,缓存失效了再回源".换句话说,当缓存数据失效时,需要重新查询后端数据库。

  2. Read-Through Pattern:又称为读穿模式,它的基本思路是"先读缓存,缓存失效了再回源,然后把最新数据回填到缓存里".换句话说,当缓存数据失效时,需要重新查询后端数据库,并将最新数据写入缓存。

  3. Write-Around Pattern:又称为回写模式,它的基本思路是"直接回源,然后更新缓存".换句话说,直接将数据更新写入后端数据库,并在查询时再从数据库读入最新数据。

  4. Write-Back Pattern:又称为写直模式,它的基本思路是"先更新数据库,然后通知缓存更新".换句话说,先将数据更新写入后端数据库,然后通知缓存更新。

缓存策略之间的区别:

  1. 是否需要考虑缓存穿透?缓存穿透是指当查询缓存和数据库都不命中,此时数据库会正常响应请求,但缓存不会将空结果进行缓存,这样会造成流量上的浪费,增加服务器压力。

  2. 是否需要考虑缓存雪崩?缓存雪崩是指某些 key 的缓存过期时间太长,或者缓存的容量过大,使得所有缓存都失效,此时再有查询请求就可能会直接落到数据库,造成数据库短暂压力增大,引起雪崩效应。

  3. 是否需要考虑缓存击穿?缓存击穿是指当热点 Key (受多次查询请求) 过期时,发生缓存雪崩现象的一种特殊情况。缓存击穿就是指多个热点 Key 在同一时间过期,缓存大量失效,导致数据库短暂压力增大,引起雪崩效应。

(2)缓存写策略

写缓存策略是指当数据在后端数据库发生变更时,如何更新缓存。

更新缓存的基本思想:当数据发生变更时,只更新缓存中对应的条目即可,而非全量更新。

常见的写缓存策略有以下几种:

  1. Flush and Restart Strategy:是对所有缓存数据进行全量更新的策略。换句话说,当数据发生变更时,直接清空整个缓存,然后再将最新的缓存数据写入。

  2. Write-Behind Caching:又称为异步写入模式,它的基本思路是"先更新数据库,然后异步地批量更新缓存"。换句话说,当数据发生变更时,只更新数据库,然后后台异步地批量更新缓存。

  3. Computed Field Strategy:是一种延迟更新策略,它的基本思路是"先更新数据库,然后根据条件计算缓存"。换句话说,先将数据更新写入后端数据库,然后后台计算缓存。

缓存写策略之间的区别:

  1. 当缓存失效时,是否需要更新缓存?Flush and Restart Strategy 和 Write-Behind Caching 是需要更新缓存的策略,也就是说,即使缓存数据失效了,也会触发异步更新策略进行缓存更新。Write-Behind Caching 比较适合写入量比较大的场景。

  2. 写缓存对读的影响?写缓存后,客户端的读请求会怎样影响?Computed Field Strategy 不会影响读请求,因为计算缓存只是异步的后台任务。

(3)缓存更新策略

缓存更新策略是指数据发生变更时,缓存什么时候需要更新。

缓存更新的基本思想:缓存只应该保留最新的内容,过期的数据可以删除。缓存过期时,应该根据数据最后访问时间(Last Access Time)来判断是否过期。

常见的缓存更新策略有以下几种:

  1. Last-Modified/If-Modified-Since:是一种协商缓存更新策略,它的基本思路是"先请求数据,若数据没有变更则命中协商缓存,否则不命中协商缓存,重新请求数据"。换句话说,客户端通过 If-Modified-Since 来判定缓存是否需要更新。

  2. ETag/If-None-Match:是一种校验码更新策略,它的基本思路是"先请求数据,若数据没有变更则命中校验码,否则不命中校验码,重新请求数据"。换句话说,客户端通过 If-None-Match 来判定缓存是否需要更新。

  3. Refresh Aging Strategy:是一种刷新时效更新策略,它的基本思路是"缓存过期后,延长过期时间"。换句话说,当缓存数据过期时,会延长其过期时间。

缓存更新策略之间的区别:

  1. 更新策略之间的差异?Last-Modified/If-Modified-Since 和 ETag/If-None-Match 的区别在于数据变化的方式,而 Refresh Aging Strategy 更加简单粗暴。

  2. 策略的优劣?Refresh Aging Strategy 虽然简单粗暴,但是它的缓存命中率和平均延迟相对较低,所以当缓存失效时,不宜采用该策略。其他两种策略适合于复杂的业务场景。

(4)缓存重定位策略

缓存重定位策略是指缓存数据发生变更时,哪些数据需要重定位。

缓存重定位的基本思想:缓存数据发生变更时,应该根据数据的主键(Primary Key)而不是根据数据本身的哈希值来判断是否需要重定位。

常见的缓存重定位策略有以下几种:

  1. Primary-Secondary Indexing:是一种先查询索引再查询数据库的策略,它的基本思路是"先根据主键查询索引,得到记录所在的表名和数据文件位置,再根据表名和数据文件位置查询数据库"。换句话说,主键查询时,先检索索引数据,然后在索引指向的位置读取数据文件,进而减少数据库的查询压力。

  2. Denormalization:是一种使用联合查询的策略,它的基本思路是"将相关数据联合查询并缓存"。换句话说,将冗余数据存放在多个列中,减少联合查询的数量。

  3. Inline Caching:是一种嵌套查询的策略,它的基本思路是"将相关数据嵌套在一起"。换句话说,将相关数据存储在缓存的同一个数据项中。

缓存重定位策略之间的区别:

  1. 重定位数据的大小?Inline Caching 会将数据嵌入到缓存数据项中,所以它的缓存大小会变大。其他两种策略的缓存大小不会变。

  2. 重定位策略的优劣?Primary-Secondary Indexing 可能导致数据不一致问题,并且索引数据量可能会占用更多的存储空间。Denormalization 和 Inline Caching 都是减少查询次数的策略,所以它们的命中率比较高,但是需要考虑数据的一致性。

6.性能优化建议

(1)降低数据库查询次数

最基本的方法就是减少数据库查询次数。缓存的本质就是将数据库查询的结果存入内存中,那么每次查询之前都先去内存中找,就能提高查询速度。因此,只要把经常访问的数据缓存起来,就能实现减少查询次数的目的。另外,缓存需要注意数据的有效期,不要让缓存无限期保存,设置合理的过期时间,从而减少缓存的内存占用。

另一方面,也可以通过建立索引和优化SQL语句来降低数据库查询次数。通过创建索引,数据库引擎会事先对数据建立索引,当数据发生变化时,索引会自动更新。索引可以提高查询速度,因为数据库引擎在查询数据时,可以利用索引直接定位到数据页,从而大大降低查询时间。对于查询语句,可以通过分析SQL语句的执行计划和优化器的提示,来调整SQL语句,从而提高查询速度。

(2)使用空间换时间

为了提升网站的响应速度,可以选择缓存合适的空间大小。一般来说,空间越大,缓存的命中率越高,响应速度越快。当然,这里有一个权衡,如果空间过大,势必会占用更多的内存,导致系统的压力增大。同时,也可以通过减少缓存失效的次数,来提升缓存的命中率。如果缓存数据失效了,就需要重新查询后端数据库,再写入缓存,这个过程可能会产生额外的网络流量和服务器压力。所以,缓存的空间大小需要在合理的范围内进行调配,既要保证性能,又要保证命中率。

(3)使用分布式缓存中间件

分布式缓存中间件主要用于解决单机缓存的不足,如缓存容量限制、缓存一致性问题等。由于分布式缓存中间件具有以下特性:

  1. 大规模集群部署:分布式缓存中间件通常部署在多个服务器上,可以承受更大规模的缓存数据,甚至跨机房部署。

  2. 高可用性:分布式缓存中间件能够保证高可用性,这意味着即使某个服务器出现故障,也能保证缓存服务的可用性。

  3. 扩展性:分布式缓存中间件能够根据需要自动扩容,这意味着可以自动根据缓存数据的实际使用情况进行扩容。

  4. 并发控制:分布式缓存中间件能够保证高并发处理能力,这意味着可以同时支持更多的用户请求。

(4)合理设置缓存过期时间

缓存过期时间设置的合理性直接决定了缓存的命中率和缓存的生命周期。过期时间的设置太长,可能会出现缓存击穿的问题,从而降低缓存的命中率;过期时间设置太短,会导致缓存命中率降低,影响网站的响应速度。

合理设置缓存过期时间的方法有两种:

  1. 设置过期时间长:对于数据经常变更的业务,设置一个比较短的时间,比如5分钟,可以降低缓存过期时间对数据库的冲击。对于不经常变更的业务,可以设置一个较长的时间,比如1天。

  2. 通过缓存预热:缓存预热是指在缓存启动之前,先加载缓存数据。当缓存启动后,再根据访问的热点数据将其缓存到内存中,缓存预热可以有效降低缓存命中率。


网站公告

今日签到

点亮在社区的每一天
去签到