当前位置: 首页 > 科技观察

吐血一个月,整理一下高并发下的缓存设计方案

时间:2023-03-16 11:55:51 科技观察

1、为什么要用缓存集群?本篇我们就来说说热点缓存的架构优化。其实在使用缓存集群的时候,最怕的两种情况就是热键和大值。那么什么是热键和大值呢?热键简单的说就是你的缓存集群中的某个键被几万甚至几十万的并发请求瞬间淹没。较大的值意味着您的某个键对应的值可能具有GB级别的大小,从而导致在查询该值时出现与网络相关的故障。在这篇文章中,我们来谈谈热键问题。看看下面的图片。简单的说,假设你手头有一个系统,它本身是部署在一个集群中的,然后它背后有一组缓存集群。这个集群用redis集群,memcached,还是公司自研的缓存集群都无所谓。那么,这个系统使用缓存集群有什么用呢?很简单,把一些变化不大的数据放在缓存中,然后当用户查询大量变化不大的数据时,直接从缓存中去不行吗?缓存集群并发能力很强,读缓存性能很高。举个例子,假设你每秒有20000个请求,但是其中90%是读请求,那么每秒18000个请求都是在读一些变化不大的数据,而不是写数据。这个时候你把所有的数据都放到数据库里,然后每秒发送20000个请求去读写数据库上的数据。你觉得合适吗?当然不合适。如果你要用数据库承载每秒20000个请求,那不好意思,你很可能要搞分库分表+读写分离。比如你打分3个主库,每秒承载2000个写请求,然后每个主库有3个从库,总共9个从库承载每秒18000个读请求。在这种情况下,你可能一共需要12台高配置的数据库服务器,非常昂贵,非常昂贵,非常不合适。看看下面的图片就可以了解这种情况。所以这个时候,可以把平时不会变化的数据放在缓存集群中。缓存集群可以使用2个master和2个slave。主节点用于写入缓存,从节点用于读取缓存。以缓存集群的性能,两个从节点可以承载每秒18000次的大量读,然后三个数据库主库可以承载每秒2000次的写请求和少量的其他读请求。让我们看看下面的图片。您消耗的机器立即变成4台缓存机器+3台数据库机器=7台机器。相比之前的12台机器,是不是减少了很多资源开销?是的,缓存其实是系统架构中非常重要的一部分。很多时候,对于很少变化但有大量高并发读的数据,使用缓存集群来抵抗高并发读是非常合适的。这里所有的机器数和并发请求量都是一个例子。你可以主要理解这个的意思。目的是给一些不熟悉缓存相关技术的同学做一些背景说明,让这些同学能够理解。在系统中使用缓存集群承载读请求是什么意思?2、20万用户同时访问一个热点缓存的问题没问题。背景已经给大家解释清楚了,那么现在我来告诉大家今天要讨论的重点问题:热点缓存。我们假设你现在有10个缓存节点来抵抗大量的读请求。一般情况下,读请求应该均匀的落在这10个缓存节点上吧!这10个缓存节点每秒承载10000个请求,差不多。那我们再做一个假设。一个节点承载20000个请求是极限,所以一般你限制一个节点正常承载10000个请求就可以了,留一点buffer。好吧,所谓的热点缓存问题是什么意思呢?这很简单。突然,由于某种莫名其妙的原因,大量用户访问了同一条缓存数据。比如某明星突然宣布和某个人结婚,会不会导致短时间内每秒都有几十万用户查看明星和某个人结婚的消息?然后假设这个消息是一个缓存,那么它对应一个缓存键,存在于一个缓存机上。此时,假设有200,000个请求发送到该机器上的一个键。这时候会发生什么?让我们看看下图来感受一下这种绝望的感觉吧。这时候,就很明显了。我们只是假设一个缓存从节点每秒最多可以处理20,000个请求。当然,实际缓存单机承载5万到10万个读请求也是有可能的。这是这里的假设。结果这个时候突然每秒有20万个请求涌到这台机器上,会发生什么?很简单,上图中20万个请求所指向的缓存机器就会超负荷崩溃。那么如果缓存集群开始出现机器停机会怎样呢?然后,当读请求发现无法读取数据时,会从数据库中提取原始数据,然后放入剩余的缓存机器中。但随之而来的每秒20万个请求又会压垮其他缓存机器。以此类推,最终会导致整个缓存集群崩溃,导致整个系统宕机。让我们看看下图,再次感受一下这恐怖的一幕。3、基于流计算技术自动发现缓存热点其实这里的关键点在于对于这种热点缓存,你的系统需要能够在突然出现热点缓存的时候直接发现他,然后瞬间实现毫秒级级自动负载均衡。那么我们先来说说,如何自动发现热缓存问题呢?首先你要知道,当有缓存热点的时候,你的每秒并发量肯定是非常高的。它可能是每秒数十万甚至数百万个请求。这都是可能的。所以,这个时候,完全可以统计大数据领域基于流计算技术的实时数据访问次数,比如storm、sparkstreaming、flink。这些技术都是可能的。那么一旦在统计实时数据访问次数的过程中,比如发现某条数据的访问次数在一秒内突然超过1000次,则立即判断该条数据为热点数据,发现的热点数据可以写入,比如zookeeper。当然你的系统如何判断热点数据可以根据你自己的业务和经验。看看下面的图片,看看整个过程是如何工作的。当然肯定有人会问,你的流式计算系统在统计数据访问次数的时候,会不会出现单机每秒请求几十万次的问题?答案是否定的,因为有了流计算技术,尤其是像storm这样的系统,可以请求同一份数据,先分散在多台机器上进行本地计算,最后将本地计算结果聚合到一台机器上进行全局聚合。所以几十万个请求可以分布在比如100台机器上,每台机器统计上千个请求这个数据。然后可以将100个本地计算的结果汇总到一台机器上进行全局计算,这样就不会出现基于流计算技术的统计热点问题。4.作为JVM本地缓存动态加载我们自己的系统可以监控zookeeper指定的热点缓存对应的znode,如果有变化,他可以第一时间感知到。这时,系统层可以立即从数据库中加载相关的缓存数据,然后直接放到系统内部的本地缓存中。对于此本地缓存,您可以使用ehcache或hashmap。事实上,一切都取决于您的业务需求。重点是将缓存集群中的集中缓存直接转换为每个系统自己的本地缓存。系统本身不能在本地缓存太多数据。因为这个普通系统的单实例部署机器可能是4核8G的机器,留给本地缓存的空间已经很少了,所以存放这种热点数据的本地缓存是最合适的,恰到好处。假设你的系统层集群已经部署了100台机器,那么,这个时候,你的100台机器都会瞬间在本地有一份热点缓存。那么接下来对热点缓存的读操作直接从系统本地缓存中读取返回,不经过缓存集群。这样的话,就不可能让每秒20万个读请求到达缓存机器去读取一台机器上的热点缓存,而是变成100台机器每台承载几千个请求,那么那几千个请求就直接返回数据来自机器的本地缓存,这没问题。我们再画一张图,一起来看看这个过程:5、限流熔断保护另外,在每个系统内部,都应该增加一个针对热点数据访问的限流熔断保护措施。在每个系统实例内部,都可以添加熔断器保护机制。假设缓存集群每秒最多可以承载40,000个读取请求,那么您总共有100个系统实例。你应该自己限制它。每个系统实例每秒可以为缓存集群请求不超过400次读取操作。一旦超过,就可以吹掉。缓存集群的请求不会被允许,直接返回空白信息,用户稍后再试。刷新页面等。通过直接在系统层加入限流熔断保护措施,可以很好的保护后续的缓存集群、数据库集群等不被杀掉。我们来看看下图。6、本文总结你想在系统中实现这种复杂的缓存热点优化架构吗?这个要看你自己的系统有没有这样的场景。如果你的系统存在热点缓存问题,那么就需要实现类似本文的复杂热点缓存支持架构。但如果不是,那就不要过度设计,事实上,你的系统可能根本不需要这么复杂的架构。如果是后者,那么大家有权利阅读本文了解相应的架构思路^_^