20

缓存系统中面临的雪崩/穿透/一致性问题

 5 years ago
source link: http://fivezh.github.io/2019/02/11/cache-things/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

There are only two hard things in Computer Science: cache invalidation and naming things.

计算机科学中有两件难事:缓存失效和命名

– Phil Karlton

From Martin Fowler : TwoHardThings

缓存系统一定程度上极大提升系统并发能力,但同样也增加额外技术考虑因素,下面针对缓存系统设计与使用中面临的常见问题展开。

  • 缓存应用的典型场景
  • 缓存雪崩
  • 缓存穿透
  • 缓存更新与数据一致性

缓存应用的典型场景

eIfIfef.png!web

请求->缓存->命中缓存则返回数据->无缓存则读取原始数据源

缓存定位 :前置数据加载,避免数据回源,提供高性能、高并发的数据读取能力;只有未命中缓存时才进行数据回源,极大减轻原始数据读取的压力

缓存分类 :按缓存系统所处位置不同,分为本地缓存、分布式缓存

  • 本地缓存:内存级缓存、文件级缓存,内存级缓存优势在于本地内存I/O、高性能(单次内存寻址100ns),缺点在于空间有限,无法多端数据同步,此类方案有PHP的Opcache/Yac, Java中Encache/GuavaCache/SpringCache等;文件级缓存依赖磁盘I/O实现缓存作用,受机械磁盘寻道性能限制(单次磁盘读取时间10ms左右),或考虑固态硬盘/Raid优化方案,较少使用
  • 分布式缓存:Memcached、Redis等,分布式系统解决缓存容量问题,具备持续扩容能力,但不可避免一次网络I/O请求

本文主要讨论 分布式缓存 系统设计与使用中面临的问题。

缓存雪崩

定义: 缓存雪崩是指缓存系统失效,导致大量请求同时进行数据回源,导致数据源压力骤增而崩溃 。两种情况会导致此问题:1、多个缓存数据同时失效;2、缓存系统崩溃

缓存同时失效

  • 在大量缓存同时失效的情况下,请求回源,导致数据源请求暴增而崩溃,系统全局不可用
  • 缓存时间设置原则:根据 缓存数据访问规律和缓存数据不一致的敏感性 要求来选择缓存时间
  • 缓存数据访问规律:如不同缓存数据访问无规律或相对离散,则不会存在这些缓存数据同时失效的情况;如 缓存数据为批量写入 (定时任务预热),应考虑将 缓存时间离散化 ,避免同时失效的情况下大量回源请求
  • 缓存数据不一致的敏感性:不同应用场景下对缓存数据的一致性要求不同,缓存时间的设置视情况而定
  • 这里也涉及到缓存更新策略问题,错误的更新策略可能会先删除缓存,再设置缓存,此时间差范围内的请求会进行回源,会导致此问题

如何避免应考虑: 缓存失效时间离散化

缓存系统故障

缓存系统整体故障,则整个缓存系统不可用,大量回源请求,且由于缓存系统故障无法回写缓存,导致无法快速恢复。

一句老话:为解决一个问题,引入新的解决方案,同时也必然引入新的问题。

这也是缓存系统的引入,在解决高性能、高并发的同时,引入了新的故障点。

考虑此问题,应从事前、事故中、事后不同阶段考虑:

  • 事前:增加缓存系统 高可用方案设计 ,避免出现系统性故障
  • 事故中:
    熔断限流机制
    
  • 事后:缓存 数据持久化 ,在故障后 快速恢复 缓存系统

缓存穿透

定义: 缓存穿透是指访问不存在数据,从而绕过缓存,直取数据源(大量数据源读取操作)

解决缓存穿透的思路:

  • 不存在资源访问时,在缓存系统设置空值来拦截
    • 优点:实现简单
    • 问题:大量非法请求时,缓存系统被填充大量非法值
      UZbUzeq.png!web
  • 根据资源设置拦截机制(布隆过滤器bloomfilter或压缩filter过滤有效资源,如有效用户id等;也可以全局保存有效资源摘要,专用过滤、防穿透)
    • 优点:缓存系统空间利用较好
    • 问题:过滤器实现机制和数据一致性要求
      nYFJrqR.png!web

缓存更新与数据一致性

缓存系统数据的更新策略是需要专门开题来说的,建议阅读 左耳朵耗子:缓存更新的套路 系统了解,这里只根据实际经验给出在不同一致性要求下的建议。

一种常见缓存更新策略(此方案有问题):

  • 读操作:命中缓存则返回,无缓存则取回源数据,写缓存
  • 写操作:先删除缓存,再更新数据源

问题场景:读写并发的场景下先删缓存操作可能导致脏数据入缓存

  • 写操作:删除缓存
  • 读操作:无缓存则取回源数据(旧数据),回写缓存(此时缓存中为旧数据)
  • 写操作:更新数据源
  • 此时缓存数据不一致:缓存中为旧数据,数据源为新数据,出现缓存旧数据问题

几种更新缓存的策略:

  • Cache Aside Pattern:缓存失效时回源取数据,更新缓存;命中缓存时,返回缓存数据;先数据源更新后,再失效缓存(由等待下次读取来回写缓存)
    • 优势:无缓存旧数据问题、缓存系统维护简单、Facebook推荐方案
    • 问题:无法绝对杜绝并发读写问题
      • 缓存过期的背景下,读操作回源取数据(此时为旧数据)
      • 写操作:更新数据源,失效缓存
      • 读操作:将回源数据(旧数据)写缓存,出现缓存数据不一致问题
      • 这种问题出现概率极低,几点要求:缓存已过期、并发读写、读数据比写数据快、但读操作更新缓存比写操作失效缓存慢(也就是说写操作的行为需完全发生在读操作两步之间),一般而言读操作(读库+更新缓存)时长要小于写操作(更新数据源+失效缓存),所以认为这种并发问题概率较低
      • 是否可进一步解决此问题:增加锁机制,解决并发问题
  • Read Through Pattern:更新数据源由缓存系统操作
    读取数据
    
  • Write Through Pattern:更新数据源由缓存系统操作
    写数据
    Read Through
    
  • Write Behind Caching Pattern:又称 Write Back
    • 一句话总结:更新数据时,只更新缓存,不更新数据源(缓存 异步批量 更新数据源)
    • 优势:
      • 更新缓存为内存操作,读写I/O非常高
      • 异步批量更新数据源,合并多个操作
    • 问题:
      • 缓存不满足强一致性要求
      • 强一致性和高性能的冲突高可用和高性能的冲突 终究会使Trade-Off
      • 实现复杂,需跟踪哪些Cache更新,成本较高

总体来说,不同方案在不同场景下是有各自优劣的,技术选型、架构设计应根据实际场景取舍,并对选择方案的利弊有足够且深入理解。

一般而言,推荐 Cache Aside Pattern 方案,容忍较小概率的不一致(同时也可以增加锁机制解决此低概率并发问题),简化缓存系统复杂度。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK