计算机科学中只有两件难事:缓存失效和命名事物。计算机科学中有两个难点:缓存失效和命名–PhilKarltonFromMartinFowler:TwoHardThings缓存系统在一定程度上极大地提高了系统的并发能力,但也增加了额外的技术考虑。下面介绍在缓存系统的设计和使用中所面临的常见问题。缓存应用典型场景缓存雪崩缓存穿透缓存更新和数据一致性缓存应用典型场景请求->缓存->***缓存返回数据->无缓存读取原始数据源缓存位置:数据加载前,避免数据回传-to-source,提供高性能、高并发的数据读取能力;数据回源只在没有安装缓存的情况下进行,大大降低了原始数据读取的压力缓存分类:根据缓存系统的位置不同,分为本地缓存和分布式缓存。本地缓存:内存级缓存和文件级缓存。内存级缓存的优势在于本地内存I/O和高性能(单内存寻址100ns)。缺点是空间有限,不能多端数据同步,此类方案有PHP中的Opcache/Yac,Java中的Encache/GuavaCache/SpringCache等;文件级缓存依赖磁盘I/O实现缓存,受机械磁盘寻道性能限制(单盘读取时间10ms左右),或者考虑SSD/Raid优化方案,较少使用分布式缓存:Memcached、Redis等,分布式系统解决了缓存容量问题,具有持续扩展的能力,但是一个网络I/O请求是不可避免的。本文主要讨论分布式缓存系统在设计和使用中面临的问题。缓存雪崩的定义:缓存雪崩是指缓存系统发生故障,导致大量请求同时返回数据源,导致数据源压力骤增而崩溃。有两种情况会导致这个问题:1.多个缓存数据同时失效;2.缓存系统崩溃,缓存同时失效。当大量缓存同时失效时,请求回源,导致数据源请求激增,崩溃,系统全局不可用。使用缓存时间设置原则:根据缓存数据访问规律和缓存数据不一致的敏感性要求选择缓存时间缓存数据访问规律:如果不同缓存数据的访问是不规则的或者相对离散的,则不会同时失效这些缓存数据;如果缓存数据是批量写入的(定时任务预热),需要考虑缓存时间的离散化,避免在同时失败的情况下,大量回源请求对缓存数据不一致的敏感性:不同的应用场景对缓存数据的一致性要求不同,缓存时间的设置视情况而定。这也涉及到缓存更新策略的问题。错误的更新策略可能是先删除缓存,再设置缓存。在这个时间差内的请求会返回到源头,就会出现这个问题。应考虑避免:缓存故障时间离散缓存系统故障如果整体缓存系统故障,整个缓存系统将不可用,大量回源请求,由于缓存系统无法回写缓存故障,导致故障快速恢复。一句老话:解决一个问题,引入一个新的解决方案,同时引入一个新的问题。这也是缓存系统的引入,在解决高性能和高并发的同时引入了新的故障点。考虑这个问题,考虑事故前、事故中、事故后的不同阶段:事前:增加缓存系统的高可用方案设计,避免系统故障事故中:断路器和限流机制系统定义缓存穿透:缓存穿透是指访问不存在的数据,从而绕过缓存,直接取数据源(大量的数据源读操作)解决缓存穿透:当没有资源访问时,在缓存系统中设置空值拦截优点:实现简单问题:当有大量非法请求时,缓存系统会填充大量非法值。根据资源设置拦截机制(Bloomfilter布隆过滤器或压缩过滤器过滤有效资源,如有效用户id等;也可以全局保存有效资源摘要,专用过滤,防渗透)优点:更好缓存系统空间利用问题:过滤器实现机制和数据一致性要求缓存更新和数据一致性缓存系统数据的更新策略需要一个专题。推荐阅读《左耳鼠标:缓存更新例程系统》了解,这里仅根据不同一致性要求下的实际经验给出建议。一种常见的缓存更新策略(这个方案有问题):读操作:***缓存返回,没有缓存则取回源数据,写缓存写操作:先删除缓存,再更新datasource问题场景:并发读写在某些场景下,先删除缓存可能会导致脏数据进入缓存。写操作:删除缓存读操作:如果没有缓存,取回源数据(旧数据),写回缓存(此时缓存为旧数据)写操作:更新数据源缓存数据此时不一致:缓存的是旧数据,数据源是新数据,存在缓存旧数据的问题。更新缓存的几种策略:CacheAsidePattern:缓存失效时,回源取数据更新缓存;***缓存到时候返回缓存的数据;先更新数据源,然后使缓存失效(通过等待下一次读取来回写入缓存)优点:不缓存旧数据,缓存系统维护简单,Facebook推荐解决方案问题:不能绝对消除concurrentreads和writes在缓存过期的上下文中,读操作返回到源中取数据(此时是旧数据)。写操作:更新数据源,使缓存失效。出现问题的概率极低。几个要求:缓存已过期,并发读写,读数据快于写数据,但读操作更新缓存慢于写操作无效缓存(也就是说写操作的行为必须完全发生在读操作之间step),一般来说,读操作(读库+更新缓存)比写操作(更新数据源+无效缓存)耗时要少,所以认为这个并发问题的概率较低。这个问题能否进一步解决:增加锁机制,解决并发问题ThroughWriteBehindCachingPattern:也称为WriteBack一句话总结:更新数据时,只更新缓存,不更新数据源(缓存异步批量更新数据源)优点:更新缓存作为内存操作,读和读写I/O很高,异步批量更新数据源,合并多个操作问题:缓存不满足强一致性要求。强一致性与高性能、高可用与高性能之间的冲突最终会使Trade-Off的实现复杂化,需要跟踪缓存更新,成本高。一般来说,不同的解决方案在不同的场景下各有优缺点。技术选型和架构设计应根据实际场景进行选择,对所选方案的优缺点有充分、深入的了解。一般来说,推荐CacheAsidePattern的方案,容忍小概率的不一致(同时也可以加入锁机制来解决这种低概率的并发问题),简化缓存系统的复杂度。
