许多大型互联网系统,如电商、社交、新闻等App或网站,动辄日活千万甚至上亿,每分钟的峰值流量在数十万以上,架构上如何应对如此高的流量峰值呢?
本文选自《技术人修炼之道:从程序员到百万高管的72项技能》一书,快来了解下如何通过“缓存”技术来给系统减压吧!流量峰值给系统带来的主要危害在于,它会瞬间产生大量对磁盘数据的读取和搜索,通常数据源是数据库或文件系统,当数据访问次数增大时,过多的磁盘读取可能会最终成为整个系统的性能瓶颈,甚至压垮整个数据库,导致系统卡死、服务不可用等严重后果。常规的应用系统通常会在需要的时候对数据库进行查找,因此系统的大致结构如下。当数据量较大时,需要减少对数据库磁盘的读写操作,因此通常都会选择在业务系统和数据库之间加入一层缓存(Cache),从而减少数据库的访问压力,结构如下。 缓存在实际的应用中并非如此简单,下面通过几个比较经典的缓存应用场景来看一些问题。
种模式通常都先从数据库缓存开始查找,如果缓存没有命中,则从数据库中查找。该模式会发生如下三种情况:● 缓存命中:当查找的时候发现缓存中存在查找的数据,那么直接从缓存中提取。
● 缓存失效:当缓存中没有数据的时候,则从数据库里面读取源数据,再同步到缓存中。
● 缓存更新:当有新的写操作去修改数据库里面的数据时,需要在写操作完成之后,让缓存里面对应的数据失效,做缓存同步。Cache Aside模式是在实际应用开发中最常用的模式,但这种模式的缓存处理并不完美。例如,一个读操作没有命中缓存,然后到数据库中取数据,此时发生一个写操作,在数据库中完成写操作后,让缓存失效,然后之前的读操作再把老的数据放进去,就会出现脏数据。分布式环境中要想完全保证数据一致性是一件极为困难的事情,只能够尽可能地减少数据不一致性问题。指应用程序始终从缓存中请求数据,如果缓存中没有数据,则它负责使用底层提供的程序插件从数据库中检索数据,检索数据后,缓存会自行更新并将数据返回给调用的应用程序。
使用Read Through模式有一个好处,由于总是使用key从缓存中检索数据,调用的应用程序不知道数据库,而由存储方来负责自己的缓存处理,这使得代码更具可读性,更清晰。但Read Through模式也有相应的缺陷,开发人员需要编写相关的程序插件,增加了开发的难度。和Read Through模式类似,当数据进行更新时,先去缓存中进行更新,如果命中,则先更新缓存再由缓存方来更新数据库。如果没有命中,就直接更新缓存里面的数据。这种模式通常先将数据写入缓存,再异步地写入数据库进行数据同步。这样的设计既可以减少对数据库的直接访问,降低压力,同时对数据库的多次修改可以合并操作,极大地提升了系统的承载能力。但是使用Write Behind Caching模式处理缓存数据有一定的风险,如当缓存机器出现宕机时,数据有丢失的可能。
缓存过期后请求将尝试从后端数据库获取数据,这是一个看似合理的流程。但是,在高并发场景下,有可能多个请求并发地从数据库获取数据,会对后端数据库造成极大的冲击,甚至导致“雪崩”。此外,当某个缓存key被更新时,也可能被大量请求获取,这也会导致一致性问题。那么如何避免类似问题呢?可以使用类似“锁”的机制,在缓存更新或者过期的情况下,先尝试获取锁,当更新或者从数据库获取完成后再释放锁,其他请求只需要一定的等待时间即可直接从缓存中继续获取数据。缓存穿透也称为“缓存击穿”。很多朋友对缓存穿透的理解是:由于缓存故障或者缓存过期导致大量请求穿透到后端数据库服务器,从而对数据库造成巨大冲击。高并发场景下,如果某个key被高并发访问,没有命中,出于容错性考虑,会尝试从后端数据库中获取数据,从而导致大量请求到达数据库,而当该key对应的数据本身为空时,就会导致数据库中并发地执行很多不必要的查询操作,从而给数据库带来巨大的冲击和压力。
可以通过如下常用方式来避免缓存穿透问题。
对查询结果为空的对象也进行缓存,如果是集合,则可以缓存一个空的集合(非null),如果是单个对象,则可以通过字段标识来区分。这样可以避免请求穿透到后端数据库,保证缓存数据的时效性。这种方式实现起来成本较低,比较适合命中率不高但可能被频繁更新的数据。对所有对应数据可能为空的key进行统一存放,并在请求前做拦截,可以避免请求穿透到后端数据库。这种方式实现起来相对复杂,比较适合命中率不高但是更新不频繁的数据。也被称为“缓存抖动”,可以看作一种比“雪崩”更轻微的故障,但是也会在一段时间内对系统造成冲击和性能影响,一般是由缓存节点故障导致的。业内推荐的做法是通过一致性Hash算法来解决问题。由于缓存的原因,导致大量请求到达后端数据库,从而导致数据库崩溃,进而整个系统崩溃,发生灾难。原因有很多种,上文提到的“缓存并发”“缓存穿透”“缓存颠簸”等问题,都可能导致缓存的雪崩。这些问题还可能被恶意攻击者利用。还有一种情况,例如在某个时间点,系统预加载的缓存周期性地集中失效了,也可能会导致雪崩。为了避免缓存周期性地集中失效,可以通过设置不同的过期时间来错开缓存过期时间。从应用架构的角度,可以通过限流、降级、熔断等手段来降低缓存雪崩的影响,也可以通过多级缓存来避免这种灾难。此外,从整个研发体系流程的角度,应该加强压力测试,尽量模拟真实场景,尽早地暴露问题,从而加以防范。缓存是大型互联网系统架构中常用的一种技术,在设计缓存架构的过程中,要根据业务场景进行有针对性的设计,避免缓存延迟、脏数据、缓存雪崩等问题,提高系统的高可用性和健壮性。
本文节选自《技术人修炼之道:从程序员到百万高管的72项技能》一书。
▊《技术人修炼之道:从程序员到百万高管的72项技能》
黄哲铿 著
本书旨在帮助IT技术人员提升职场核心技能、架构思维、团队管理能力、商业认知,让每一位普通的技术从业者,修炼成为“技术职场超级个体”,通过全面升级个人的底层操作系统,突破瓶颈,实现职场跃迁。
(扫码了解本书详情)
为了鼓励大家积极阅读,这次出版社赠送的3本书籍奖励给近期阅读最多的3的老铁,如下图所示,后续每月会有一次阅读最多奖励。添加我微信:itcodexy,备注:书籍获奖 。领取时间截止24小时,过时就当弃领了 ~