作者 | 中华石杉
责编 | 伍杏玲
本文经授权转载自石杉的架构笔记
【程序人生 编者按】昨天一连出现4个热点新闻:双宋离婚、唐艺昕张若昀结婚、宝强母亲去世、李晨范冰冰分手。广大网友在吃瓜之余,纷纷心疼其新浪程序员:
扛过头三次热点的微博,在最后也终于熬不住了:
估计是程序员有点累吧,需要喘口气。
我们在吃瓜之余,作为程序员,你是否会思考:究竟后台需要如何设计才能扛得住一天四热点连发呢?
本文作者为从业十多年的资深程序员,将在本文详细地给大家答疑解惑。
我们先普及下背景:如何用缓存集群承载读请求?
为什么要用缓存集群
其实使用缓存集群的时候,最怕的就是热Key、大Value这两种情况,那啥叫热Key大Value呢?
简单来说,热Key就是你的缓存集群中的某个Key瞬间被数万甚至十万的并发请求打爆。大Value,就是某个Key对应的Value可能有GB级的大小,导致查询Value的时候导致网络相关的故障问题。
我们先来看看下面一幅图,假设你手头有个系统,他本身是集群部署的,然后后面有一套缓存集群,这个集群不管你用Redis Cluster,还是Memcached,或者是公司自研缓存集群,都可以。
那么,这套系统用缓存集群干什么呢?
很简单,在缓存里放一些平时不怎么变动的数据,然后用户在查询大量的平时不怎么变动的数据的时候,不就可以直接从缓存里走了吗?
缓存集群的并发能力是很强的,而且读缓存的性能是很高的。举个例子,假设你每秒有2万请求,但是其中90%都是读请求,那么每秒1.8万请求都是在读一些不太变化的数据,而不是写数据。
那此时你把这些数据都放在数据库里,然后每秒发送2万请求到数据库上读写数据,你觉得合适吗?
当然不合适了,如果要用数据库承载每秒2万请求的话,那么不好意思,你很可能就得搞分库分表 + 读写分离。
比如可能得分3个主库,承载每秒2000的写入请求,然后每个主库挂3个从库,一共9个从库承载每秒1.8万的读请求。
这样的话,可能就需要一共是12台高配置的数据库服务器,这是很耗费钱的,成本非常高,很不合适。
大家看看下面的图,来体会下这种情况。
因此,我们完全可以把平时不太变化的数据放在缓存集群里,缓存集群可以采用2主2从,主节点用来写入缓存,从节点用来读缓存。
以缓存集群的性能,2个从节点完全可以用来承载每秒1.8万的大量读请求,然后3个数据库主库就是承载每秒2000的写请求和少量其他读请求就OK了。
这样一来,耗费的机器瞬间变成了4台缓存机器 + 3台数据库机器 = 7台机器,是不是比之前的12台机器减少了很大的资源开销?
没错,缓存其实在系统架构里是非常重要的组成部分。很多时候,对于那些很少变化但是大量高并发读的数据,通过缓存集群来抗高并发读,是非常合适的。
我们看看下面的图,体会一下这个过程。
需要说明的是,这里所有的机器数量、并发请求量都是一个示例,大家主要是体会一下这个意思就好。
其目的主要是给一些不太熟悉缓存相关技术的同学一点背景性的阐述,让这些同学能够理解在系统里用缓存集群承载读请求是什么意思。
20万用户同时访问一个热点缓存
好了,背景已经给大家解释清楚,现在就可以给大家说说今天重点要讨论的问题:热点缓存。
我们来做一个假设,现在有10个缓存节点来抗大量的读请求。正常情况下,读请求应该是均匀的落在10个缓存节点上。
这10个缓存节点,每秒承载1万请求是差不多的。
然后我们再做一个假设,一个节点承载2万请求是极限,所以一般你就限制一个节点正常承载1万请求就OK,稍微留一点Buffer出来。
好,所谓的热点缓存问题是什么意思呢?很简单,就是突然因为莫名的原因,出现大量的用户访问同一条缓存数据。
比如像昨天那样,双宋离婚、宝强母亲去世、李晨范冰冰分手,这是不是会引发短时间内每秒都数十万用户去查看这几条热点新闻?
假设上述3条新闻就是3个缓存,对应3个缓存Key,这些Key都存在于一台缓存机器上。
然后某条新闻一公布,比如范冰冰一发布微博,接着瞬间就可能几十万请求奔向那一台机器。
此时会如何?我们看看下面的图,来体会一下这种绝望的感受。
很明显了,我们刚才假设的是一个缓存Slave节点最多每秒就是2万的请求,当然实际缓存单机承载5万~10万读请求也是可能的,这里就是一个假设。
结果每秒突然奔过来20万请求到这台机器上会怎么样?很简单,上面图里那台被20万请求指向的缓存机器会过度操劳而宕机的。
那么如果缓存集群开始出现机器的宕机,此时会如何?
此时读请求发现读不到数据,会从数据库里提取原始数据,然后放入剩余的其他缓存机器里去。但是接踵而来的每秒20万请求,会再次压垮其他的缓存机器。
以此类推,最终导致缓存集群全盘崩溃,引发系统整体宕机。
咱们看看下面的图,再感受一下这个恐怖的现场。
基于流式计算技术的缓存热点自动发现
其实这里关键的一点,就是对于这种热点缓存,系统需要能够在热点缓存突然发生的时候,直接发现然后瞬间立马实现毫秒级的自动负载均衡。
那么我们就先来说说,你如何自动发现热点缓存问题?
首先你要知道,一般出现缓存热点的时候,每秒并发肯定是很高的,可能每秒都几十万甚至上百万的请求量过来,这都是有可能的。
所以,此时完全可以基于大数据领域的流式计算技术来进行实时数据访问次数的统计,比如Storm、Spark Streaming、Flink。
一旦在实时数据访问次数统计的过程中,比如发现一秒之内,某条数据突然访问次数超过了1000,就直接立马把这条数据判定为是热点数据,可以将这个发现出来的热点数据写入比如ZooKeeper中。
当然,系统如何判定热点数据,可以根据自己的业务还有经验值来就可以了。
大家看看下面这张图,看看整个流程是如何进行的。
这里肯定有人会问,那你的流式计算系统在进行数据访问次数统计的时候,会不会也存在说单台机器被请求每秒几十万次的问题呢?
答案是:否。
因为流式计算技术,尤其是Storm这种系统,他可以做到同一条数据的请求过来,先分散在很多机器里进行本地计算,最后再汇总局部计算结果到一台机器进行全局汇总。
所以几十万请求可以先分散在比如100台机器上,每台机器统计了这条数据的几千次请求。
然后100条局部计算好的结果汇总到一台机器做全局计算即可,所以基于流式计算技术来进行统计是不会有热点问题的。
热点缓存自动加载为JVM本地缓存
我们自己的系统可以对ZooKeeper指定的热点缓存对应的Znode进行监听,如果有变化他立马就可以感知到了。
此时系统层就可以立马把相关的缓存数据从数据库加载出来,然后直接放在自己系统内部的本地缓存里即可。
这个本地缓存,你用Ehcache、Hashmap,其实都可以,一切看自己的业务需求。我们这里主要说的就是将缓存集群里的集中式缓存,直接变成每个系统自己本地实现缓存即可,每个系统本地是无法缓存过多数据的。
因为一般这种普通系统单实例部署机器可能就一个4核8G的机器,留给本地缓存的空间是很少的,所以用来放这种热点数据的本地缓存是最合适的,刚刚好。
假设你的系统层集群部署了100台机器,那么好了,此时100台机器瞬间在本地都会有一份热点缓存的副本。
然后接下来对热点缓存的读操作,直接系统本地缓存读出来就给返回了,不用再走缓存集群了。
这样的话,也不可能允许每秒20万的读请求到达缓存机器的一台机器上读一个热点缓存了,而是变成100台机器每台机器承载数千请求,那么那数千请求就直接从机器本地缓存返回数据了,这是没有问题的。
我们再来画一幅图,一起来看看这个过程:
限流熔断保护
除此之外,在每个系统内部,其实还应该专门加一个对热点数据访问的限流熔断保护措施。
每个系统实例内部都可以加一个熔断保护机制,假设缓存集群最多每秒承载4万读请求,那么一共有100个系统实例。
这样的话就该限制好,每个系统实例每秒最多请求缓存集群读操作不超过400次,一超过就可以熔断掉,不让请求缓存集群,直接返回一个空白信息,然后用户稍后会自行再次重新刷新页面之类的。
通过系统层自己直接加限流熔断保护措施,可以很好的保护后面的缓存集群、数据库集群之类的不要被打死。
再来一幅图,一起来看看:
总结
具体要不要在系统里实现这种复杂的缓存热点优化架构呢?这个还要看你们自己的系统有没有这种场景了。
如果你的系统有热点缓存问题,那么就要实现类似本文的复杂热点缓存支撑架构。但是如果没有的话,那么也别过度设计,其实你的系统可能根本不需要这么复杂的架构。
如果是后者,那么大伙儿就权当看看本文,了解一下对应的架构思想好了。
作者简介:中华石杉,十余年BAT架构经验,倾囊相授
公众号:石杉的架构笔记(id:shishan100)
热 文 推 荐