后台开发中的缓存技术详解

发表时间: 2024-07-02 09:02

目录


1 缓存策略

2 缓存类型

3 缓存淘汰策略

4 缓存常见问题

5 总结




本文介绍了后台开发中使用的缓存技术,如缓存策略、缓存类型,包括本地缓存和分布式缓存,还有缓存淘汰策略,以及缓存使用中的常见问题,如一致性问题、缓存雪崩、缓存穿透、缓存击穿。




缓存(Cache)是一种存储技术,可以存储数据,以便快速获取数据。缓存最重要的是两个特性:存储、快速获取。缓存的本质:「用空间换时间」,用快速存储的介质保存数据,以提升数据访问的速度。


在计算机的世界里,缓存被广泛应用于硬件(如 CPU 高速缓存、寄存器)和软件中(如浏览器缓存、CDN、应用程序中的缓存等)。


后台中的缓存


后台开发中,缓存的使用场景一般有:

  • 提升接口响应速度:缓存相比 IO 请求、数据库查询速度要快得多,适当使用缓存提升响应速度
  • 降低数据源服务的负载压力:将需要高并发读取的数据缓存起来,当相同请求进来时返回缓存数据,减轻数据源服务(如数据库、上游系统)的负载压力
  • 减少计算资源的浪费:将需要复杂且耗时计算的结果缓存起来,减少相同请求导致的重复计算资源浪费




01



缓存策略


缓存策略通常分为两种,通读缓存和旁路缓存。


1.1 通读(read-through)缓存


在使用通读缓存时,应用程序会尝试从缓存中获取数据。如果该数据已存在于缓存中,那么缓存会直接返回该数据。若缓存中并未包含所需数据,那么缓存会自行访问数据源来获取数据,并将获取的数据返回给应用程序,同时将这份数据保存在缓存中。如此一来,当应用程序下次需要同样的数据时,就能够直接从 read-through 缓存中获取,无需再次访问数据源。



通读缓存的应用有很多,比如 CDN:


CDN(Content Delivery Network)即内容分发网络,是一种分布式的网络架构。依赖于服务商提供的广泛分布于各地的 CDN 服务器,通过把数据分发并缓存到各地的 CDN 服务器上,当用户请求数据时,CDN 会匹配离用户距离最近的节点并返回缓存数据,以此来提高用户访问资源的速度。CDN 一般用来加速静态资源的传输,比如图片、CSS、HTML 等内容。而动态的内容,如搜索结果、单据信息等则不合适,这类数据需要应用服务器实时计算后才能返回。




腾讯云 CDN 具有海量资源储备、全球智能调度、一键接入、丰富功能等产品优势,欢迎企业及用户了解试用:
https://cloud.tencent.com/product/cdn




配图来源:seobility


1.2 旁路(cache-aside)缓存


与通读缓存不同,旁路缓存不直接与数据源打交道。应用程序尝试从旁路缓存获取数据,如果数据不存在,则返回空。应用程序自行请求数据源获取数据,并写入旁路缓存。这样下次相同的请求到达时,应用程序从旁路缓存中则会获取到数据。



通读缓存和旁路缓存这两种策略,一般来说都会灵活应用、互相包含。比如通读缓存中,对数据的查询和写缓存,使用的就是旁路缓存策略。而该实现被整体封装起来,对外表现的则是通读缓存策略。




02



缓存类型


在后台服务中,缓存的类型可以分为本地缓存和分布式缓存。


2.1 本地缓存(local cache)


本地缓存,与应用程序的进程有相同的生命周期,存放于应用程序的堆空间(heap)中。


  • 优势:
  • 使用简单;无外部依赖;读取速度快(无网络 IO 请求);
  • 缺点:
  • 空间小:应用程序的服务器资源有限,所以本地缓存的空间小;分布式一致性问题:如果后台服务是分布式架构的,那么不同的服务实例之间的本地缓存可能会有差异;无法持久化:本地缓存会随着进程结束而被销毁,无法持久化。


2.2 分布式缓存(remote cache)


分布式缓存,也可理解为远端缓存。使用外部的缓存服务,独立部署,与应用程序解藕。


  • 优势:
  • 空间充足:外部存储一般空间都很充足;无分布式一致性问题:不同的服务实例连接同一个缓存服务,不存在一致性问题;主流的分布式缓存,如 redis,支持数据持久化和恢复,当缓存服务挂了可以恢复数据。
  • 缺点:
  • 引入外部依赖、需要部署和运维单独的缓存服务。


2.2.1 常见的缓存服务


Redis

  • 支持多种数据结构,如:list,set,zset,hash 等数据结构。
  • 支持数据的持久化和恢复。
  • 支持多种集群模式:主从复制、哨兵模式和 Cluster 模式,保证高可用、容灾恢复、易于扩展。
  • 能提供毫秒级别的响应速度。
  • Redis 6.0 引入了多线程 IO,之前是单线程模型。


Memcache

  • 主要支持简单的键值对存储。
  • 不支持持久化和恢复。
  • 集群:可以通过客户端分片实现分布式存储。
  • 多线程,非阻塞 IO 模式。
  • 能提供毫秒级别的响应速度。


选择 Redis 还是 Memcached 取决于具体的应用需求。如果需要一个轻量级、专注于缓存的解决方案,且不需要复杂的数据类型和持久化,Memcached 可能是更合适的选择。而如果应用需要利用丰富的数据类型、持久化以及构建高可用的分布式系统,Redis 将是更强大、更灵活的选择。




03



缓存淘汰策略


由于缓存的空间是有限的,如果缓慢的空间被使用完了,则需要淘汰旧的数据,腾出空间给新的数据使用。缓存淘汰常用的几种策略有如下几种。


3.1 FIFO(First In First Out)算法


FIFO 算法是最简单最好理解的,其策略是:先进先出,如果一个数据的写入时间越早,说明将来被访问的几率越低。因此 FIFO 算法优先淘汰最早写入的数据。



3.2 LRU(Least Recently Used)算法


LRU 算法,即最近最少使用算法。如果一个数据最近被访问了,那么将来被访问的几率越高。反之,如果一个数据很久都没有访问,那么将来被访问的几率越低。其淘汰策略就是:优先淘汰最久没有被使用到的数据。LRU 通常使用双向链表+哈希表来实现。




3.3 LFU(Least Frequently Used)算法


LFU 算法,即最少使用算法。如果一个数据被访问的次数越多,那么将来被访问的几率越高。反之,如果一个数据被访问的次数越少,那么将来被访问的几率越小。其淘汰策略就是:优先淘汰最少被使用的数据。LFU 算法可以使用小顶堆+哈希表来实现。



大部分本地缓存的三方库或缓存服务,都支持设置淘汰算法,无需自行实现。




04



缓存常见问题


4.1 缓存与数据源的一致性


缓存的数据是来自数据源的,当数据源被更新了,而缓存没有被更新,后台服务则会从缓存中取到脏数据,这就有数据脏读的问题。对于这个问题,主要的策略有两种。


4.1.1 过期失效


每个写入缓存中的数据,都设置一个合适的过期时间,在有效期内都返回缓存的数据。当缓存数据过期失效,则会回源到数据源,重新缓存最新的数据。这种情况会有数据脏读的问题,不过采用过期失效的数据,一般是更新不频繁的数据,比如用户信息、热门信息等,存在一定的数据延迟是可以接受的。


4.1.2 主动更新


当数据源的数据更新了,程序主动去更新缓存数据,保证缓存中的数据始终是最新的。这种策略,适合对数据的时效性要求很高的数据,比如库存、余额等等。主动更新的代价则是代码的复杂度增加,所有涉及数据更新的操作,都需要更新其缓存数据。


无论是过期失效还是主动更新,首先应该分析当前场景对数据的时效性要求是否很高?如不是,过期失效足矣。如果是,需要衡量主动更新的代价能否接受,比如代码复杂度增加。否则是否可以通过缩短失效时间而采取过期失效策略来折中处理。


4.2 缓存雪崩


缓存雪崩是指在同一时间点后台服务中的缓存大量过期失效,当服务的并发量很高时,大量的数据请求同时到达数据源服务(比如数据库或其他服务),引起数据源服务的瞬时负载增大,甚至崩溃。


引起缓存雪崩的原因一般有两种:

  • 一是大量的缓存 key 在同一时间失效
  • 二是缓存服务不可用,请求都被透传到数据库。



解决办法:

  • 设置合理的缓存失效时间:合理设置过期时间,分散缓存的失效时间,避免缓存在同一时间失效。
  • 保证缓存服务的高可用:使用独立的缓存服务,尽量使用缓存集群,保证缓存服务的高可用和容灾恢复。


4.3 缓存穿透


缓存穿透是指同一时间大量的空值请求到达后台服务,空值请求是指查询数据源后无数据的请求,由于数据源返回空数据,所以缓存层没有将空值保存,导致空值请求必然会穿透缓存层,透传到数据源。


引起缓存穿透的原因,一般有:

  • 未对请求参数进行合理的校验和拦截。
  • 被精心构造的恶意请求攻击。



解决办法:

  • 设置合理的参数校验:通过设置合理的校验规则,比如 id 范围、名称正则规则等,对不合理或空值请求的参数的进行过滤和拦截。
  • 缓存空值处理:即使数据源返回空值,也在缓存层将其保存起来。可能对其数值设置一些标记,比如 value=-1 等等,方便服务识别为空值。同时,也可以为空值的缓存设置一个较短的失效时间。


4.4 缓存击穿


缓存击穿是指某个热点数据的缓存失效了,然后同一时间有大量的请求访问该热点数据,由于缓存失效,这些请求同时被透传到数据源服务,导致数据源的负载增加,甚至崩溃。


引起缓存击穿的原因,一般是:热点数据的缓存失效时,高并发请求同时访问该数据。



解决办法:

  • 使用互斥锁或者分布式锁,对数据的回源操作进行上锁,保证同一时刻只有一个请求被透传到数据源服务。
  • 热点数据的缓存永不过期,由数据源主动更新缓存。
  • 热点数据预加载:在缓存数据将要过期时,由后台服务主动更新热点数据缓存,防止缓存失效。




05



总结


本文详细地介绍了后台开发中的缓存技术,希望能对你的日常工作有所帮助和启发。熟练掌握各种缓存策略和机制,解决缓存问题,对于程序员而言至关重要,可以在很大程度上提升后台开发的效率、稳定性和用户体验