MST

星途 面试题库

面试题:缓存设计:Redis高并发下的缓存一致性优化

在高并发场景下,后端应用频繁读写Redis缓存,如何保证缓存与数据库之间的数据一致性?请详细说明你在设计缓存读写逻辑、更新策略以及处理并发冲突等方面的思路和方法,并举例说明可能遇到的难点及解决方案。
24.0万 热度难度
后端开发缓存设计

知识考点

AI 面试

面试题答案

一键面试

缓存读写逻辑设计

  1. 读逻辑
    • 应用首先尝试从 Redis 缓存中读取数据。如果缓存命中,直接返回数据,减少数据库压力。例如,一个商品详情页面,先从 Redis 缓存获取商品信息。
    • 如果缓存未命中,从数据库读取数据。读取后,将数据写入 Redis 缓存,并设置合适的过期时间,以便后续请求可直接从缓存获取。如商品信息从数据库读取后,写入 Redis 并设置过期时间为 1 小时。
  2. 写逻辑
    • 优先更新数据库。确保数据库中的数据是最新的,因为数据库是数据的最终存储地。比如更新用户信息,先在数据库执行更新操作。
    • 然后更新 Redis 缓存,保证缓存数据与数据库同步。可以采用异步方式更新缓存,减少对业务流程的阻塞。例如使用消息队列,将更新缓存的任务发送到队列,由专门的消费者处理。

更新策略

  1. Cache - Aside 策略
    • 读操作:先查缓存,缓存命中直接返回;未命中查数据库,再将结果写入缓存。
    • 写操作:先更新数据库,然后删除缓存。下次读操作时,缓存不存在,会从数据库加载并更新缓存。例如电商系统中商品价格更新,先更新数据库价格,再删除 Redis 中该商品价格缓存。
  2. Read - Through / Write - Through 策略
    • Read - Through:应用请求读数据,缓存代理(如 Redis 客户端库)先查缓存,未命中时,代理从数据库读取数据并写入缓存,再返回给应用。
    • Write - Through:应用请求写数据,缓存代理先更新数据库,成功后更新缓存。这确保了数据库和缓存始终保持一致,但可能增加写操作的延迟。如订单系统中订单状态更新,通过代理先更新数据库订单状态,再更新 Redis 中订单状态缓存。
  3. Write - Behind Caching 策略
    • 应用写数据时,只更新缓存,将更新数据库的操作异步化。通过批量处理和延迟写入,减少数据库写压力。但可能导致数据一致性短暂延迟,适用于对一致性要求不高的场景。比如日志记录,先写入 Redis 缓存,再由后台任务批量写入数据库。

处理并发冲突

  1. 乐观锁
    • 在数据库表中添加版本号字段。每次更新数据时,先读取当前版本号,更新时带上版本号,只有当数据库中版本号与读取的版本号一致时才执行更新操作。例如,商品库存更新,先读取库存和版本号,更新库存时,若版本号未变则更新成功,否则重新读取数据并更新。
    • 在 Redis 中也可以通过 Lua 脚本实现类似乐观锁机制,确保在并发情况下缓存更新的正确性。
  2. 悲观锁
    • 在数据库层面使用锁机制,如行锁、表锁。例如在更新订单状态时,对订单表的某一行数据加锁,确保同一时间只有一个事务能更新该数据,避免并发冲突。但可能会影响系统并发性能。
    • 在 Redis 中可以使用 SETNX(SET if Not eXists)命令实现简单的分布式锁,处理并发更新缓存的问题。不过要注意锁的释放,避免死锁。

可能遇到的难点及解决方案

  1. 缓存雪崩
    • 难点:大量缓存同时过期,导致高并发请求同时穿透到数据库,使数据库压力瞬间增大,甚至崩溃。
    • 解决方案:设置不同的过期时间,避免大量缓存同时过期。可以在基础过期时间上加上一个随机值。例如,基础过期时间为 1 小时,随机值为 0 - 10 分钟,这样缓存过期时间在 1 小时到 1 小时 10 分钟之间随机分布。
  2. 缓存穿透
    • 难点:恶意请求访问不存在的数据,每次都穿透到数据库,导致数据库压力增大。
    • 解决方案
      • 布隆过滤器:在查询前,先通过布隆过滤器判断数据是否存在。布隆过滤器判断存在的数据不一定真实存在,但判断不存在的数据一定不存在,能有效拦截不存在数据的请求。
      • 缓存空值:如果数据库查询结果为空,也将空值写入缓存,并设置较短的过期时间。下次请求同样数据时,直接从缓存返回空值,避免穿透到数据库。
  3. 缓存击穿
    • 难点:热点数据缓存过期瞬间,大量并发请求同时访问该数据,导致数据库压力剧增。
    • 解决方案
      • 互斥锁:在缓存过期时,使用互斥锁(如 Redis 分布式锁)保证只有一个请求能从数据库加载数据并更新缓存,其他请求等待。待缓存更新后,释放锁,其他请求从缓存获取数据。
      • 永不过期:对于热点数据,不设置过期时间,通过后台定时任务或者数据变更时主动更新缓存。