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