🪢Redis 缓存:数据一致性
00 min
2024-11-9
2024-11-14
type
status
date
slug
summary
tags
category
icon
password

1. 为什么需要使用缓存?

缓存是一种常用的技术,用于提高数据检索速度,从而优化系统的整体性能和效率。以下是使用缓存的几个主要原因:
  1. 提高响应速度:缓存可以将经常访问的数据存储在快速访问的存储系统中(如内存),这比从慢速存储(如硬盘或远程数据库)中检索数据要快得多。这意味着应用程序可以更快地响应用户请求。
  1. 减少数据库负载:通过在缓存中存储频繁查询的结果,可以减少对后端数据库的访问次数。这不仅可以减少数据库的工作负载,还可以避免在高流量情况下数据库成为瓶颈。
  1. 节省成本:减少对数据库的查询可以降低数据库服务器的规模需求,从而减少维护成本。此外,由于缓存通常使用更少的资源(如CPU和RAM),因此它可以更加经济高效。
  1. 提高可伸缩性:缓存可以帮助应对突发的流量增加,例如在促销或特殊事件期间。通过缓存常见的请求结果,系统可以处理更多的并发用户,而不会降低性能。
  1. 减少网络延迟:在分布式系统中,数据可能需要从远程服务器检索,这可能涉及显著的网络延迟。通过将数据缓存更靠近用户(例如,在用户的本地设备或地理上接近用户的服务器上),可以显著减少数据检索时间。
尽管缓存带来了许多优势,但它也引入了一些复杂性,如缓存一致性、缓存失效策略和内存管理等。因此,设计和维护缓存系统需要仔细考虑这些因素,以确保系统的正确性和高效性。

2. 为什么要保持数据的一致性?如果不保持数据的一致性会存在什么问题?

解答问题最好方式就举例子通俗易懂,所以接下来我们来通过电商库存的例子来了解下数据一致性的重要性。
notion image
如上面图片我们在 redis 查询的库存数为 4,实际数据库中的库存数为 3。这样就会造成超卖的现象,反之就会发生少买的情况。这就会对买家造成严重的影响,所以说保持数据一致性是重要且必要的

3. 缓存的类型

3.1 只读缓存

什么是只读缓存?就是在使用缓存的时候只进行读取操作。具体如下图:
notion image

3.2 读写缓存

什么是读写缓存? 就是在使用缓存的时候即进行读取操作还会进行写入操作。具体如下图:
notion image

4. 使用缓存必须要解决的问题

  • 缓存穿透:当一个请求查询的数据在缓存中不存在,且在数据库中也不存在时,如果每次都直接查询数据库,就会导致缓存无法起到应有的作用,从而影响性能。
  • 缓存击穿:当一个热点数据在缓存中的过期时间到了,而此时有大量请求同时访问该数据,这些请求会直接穿透缓存访问数据库,导致数据库压力瞬间增大。
  • 缓存雪崩:当缓存中大量的数据同时过期,导致大量请求直接访问数据库,可能会导致数据库压力过大,甚至宕机。

4.2.1 如何避免缓存穿透

  1. 缓存空结果
      • 对于查询结果为空的数据,可以将空结果(如空字符串或特定标志)也缓存起来,并设置较短的过期时间。这样下次查询相同的数据时,直接返回缓存中的空结果,避免每次都查询数据库。
  1. 使用布隆过滤器
      • 在查询缓存之前,使用布隆过滤器(Bloom Filter)来判断数据是否存在。如果布隆过滤器判断数据不存在,则直接返回空结果,避免查询数据库。布隆过滤器占用内存较少,能够高效地判断数据的存在性。
  1. 参数校验和限制
      • 对于用户输入的查询参数进行严格校验,过滤掉非法和不合理的请求,防止恶意攻击和无效查询。例如,对 ID 范围、字符串格式等进行校验。
  1. 缓存降级
      • 在缓存失效或数据不存在时,采用降级策略,比如返回默认值或空结果,避免对数据库造成过大压力。
  1. 限流和防刷
      • 对频繁访问相同数据的请求进行限流和防刷处理,防止恶意请求穿透缓存对数据库造成冲击。

4.2.2 如何避免缓存击穿

  1. 设置热点数据永不过期
      • 对于一些核心业务的热点数据,可以设置为永不过期,或者在数据更新时主动更新缓存。
  1. 使用互斥锁(Mutex Lock)
      • 当缓存失效时,使用互斥锁来保证只有一个线程去加载数据到缓存中,其他线程等待缓存更新完成后再读取。这样可以避免大量请求同时访问数据库。
  1. 使用分布式锁
      • 在分布式系统中,可以使用分布式锁(如基于 Redis 的分布式锁)来实现互斥锁的功能,确保只有一个服务实例去更新缓存。
  1. 异步加载
      • 当缓存失效时,可以异步地加载数据到缓存中,而不是同步等待。这样可以减少请求的等待时间,提高系统的响应速度。
  1. 使用二级缓存
      • 设置两级缓存,一级缓存失效时,可以使用二级缓存来提供服务,同时更新一级缓存。
  1. 缓存预热
      • 在系统启动或低峰期,提前将热点数据加载到缓存中,以减少高峰期的缓存击穿风险。

4.2.3 如何避免缓存雪崩

  1. 设置不同的过期时间
      • 为缓存数据设置不同的过期时间,避免大量数据在同一时间点失效。可以通过在过期时间上增加一个随机值来实现。

5. 什么时候使用读缓存,什么时候使用读写缓存

读缓存(Read Cache)通常用于提高读取数据的速度和效率。当应用程序或系统主要执行大量的读取操作,而写入操作较少时,使用读缓存可以显著提高性能。例如,网站服务器、文件服务器和数据库服务器在处理大量用户查询请求时,常常会使用读缓存来缓存热点数据,从而减少对后端存储的访问,提高响应速度。
读写缓存(Read-Write Cache)则在处理读取和写入操作时都能提供性能优势。它不仅缓存读取的数据,还可以暂存写入的数据,延迟将这些数据写回到存储设备。这种机制特别适用于写入操作频繁的应用,如交易处理系统、数据库写操作等。读写缓存可以减少磁盘I/O操作,缓解后端存储的压力,提高整体系统性能。
总的来说,选择使用读缓存还是读写缓存主要取决于应用的工作负载特性。如果应用主要是读取密集型的,使用读缓存即可;如果应用既有大量读操作又有大量写操作,那么使用读写缓存会更加合适。在实际部署时,还需要考虑数据一致性和耐久性等因素,合理配置缓存策略。

5. 读缓存怎么保持数据的一致性

日常的操作中我们有两种情况:
  • 先更新数据库,在删除缓存
  • 先删除缓存,在更新数据库
这两种情况有着什么问题我们来分别分析下。
notion image
  • 先更新数据库,在删除缓存
如果更新数据库后再删除缓存的时候失败,这样直到 redis key的过期时间后数据才会变化。如果对数据的实时性没有高强度的追求,但是需要等 redis key 过期也是不完美的。如果删除失败我们可以想到重试,但是在接口中同步重试终究是有限制的。所以我们这里直接引入一个队列来进行异步的删除,如下图:
notion image
但是如果我们对数据的时效性有着严格的要求我们只能对删除操作和更新操作进行加锁,但是这样就违背了我们引入缓存的目的:加速请求。
  • 先删除缓存,在更新数据库
如果先删除缓存在更新数据库,在高并发的情况下还未更新数据时候就有请求那么缓存就保存的是旧的数据,这样我们就只能等到数据过期才能拿到一致的数据。我们是不是能在更新数据库之后再次删除缓存呢?
基于上面两种情况的分析,目前比较的好的解决方案如下:
  • 延迟双删
      1. 更新数据库
      1. 删除缓存的数据
      1. 引入延迟
      1. 再次删除缓存中的数据

5. 读写缓存怎么保持数据的一致性

notion image
当我们进行双写的时候在写数据库的时候有可能请求数据库失败,这样就导致数据的不一致性。
  • 如何解决数据一致性问题
      1. 锁机制:我们可以使用分布式锁来保证更新 redis 和更新数据库的原子性,这样就能保证数据的一致性
      1. 缓存刷新:定期刷新缓存,将缓存中的数据写回到内存中,以确保缓存中的数据与内存中的数据保持一致。

分享到这里了!!! 加油,干饭人🍚
上一篇
哨兵机制:当主库挂了,redis 是否能继续服务
下一篇
限流算法