zoukankan      html  css  js  c++  java
  • 热点缓存的架构优化

    使用缓存集群的时候,最怕的就是热key、大value这两种问题。热key问题,指的就是缓存集群中的某个key在瞬间被数万甚至十万的并发请求打爆。大value问题,指的是某个key对应的value可能有gb级别的大小,导致查询value的时候会引发网络相关的故障问题。这里说一下热key问题。

    为什么要使用缓存集群

    简单来说,假设你手头上有个系统,它本身是集群部署的,然后后面有一套缓存集群,这个集群不管你用Redis Cluster,还是Memcached,或者是公司自研缓存集群,都可以。

    那么这套系统用缓存集群做什么呢?很简单,在缓存放一些平时不怎么变动的数据,然后用户在查询大量的、平时不怎么变动的数据的时候,就可以直接访问缓存而不需要访问数据库了。缓存集群的并发能力很强,而且读缓存的性能也很高。举个例子,假设每秒钟有2万的请求,但是其中的90%都是读请求,那么每秒钟1.8万的请求都是在读一些不太变化的数据,而不是写数据。此时,如果你把数据都放在数据库里,然后每秒钟发送2万个请求到数据库上读写数据,显然是不合适的,因为如果要数据库能承载每秒2万个请求的话,很可能就需要搞分库分表+读写分离。比如,你需要分出来3个主库去承载每秒2000的写入请求,然后每个主库挂3个从库,一个9个从库去承载每秒1.8万的读请求。那么这样你就需要一共是12台高配置的数据库服务器,成本非常高,而且很不合适。

    因此,此时你就可以完全把平时不太变化的数据放在缓存集群里,缓存集群可以采用2主2从,主节点用来写入缓存,从节点用来读取缓存。以缓存集群的性能,2个从节点完全可以用来承载每秒1.8万的大量读,然后3个主库就只要承载每秒2000的写请求和少量的其他读就可以了。这样,耗费的机器瞬间变成了4台缓存机器+3台数据库机器=7台机器,比起前面的12台机器减少了很大的资源开销。事实上,缓存是系统架构里非常重要的一个组成部分。很多的时候,对于哪些很少变化但是大量高并发读的数据,通过缓存集群来抗高并发读,是非常合适的。

    以上的机器数量、并发请求量只是一个简单的示例,实际的情况要复杂得多,通过这个例子就能大概理解在系统中为什么要使用缓存集群来承载读写请求。

    20万用户同时访问一个热点缓存的问题

    做一个假设,现在有10个缓存节点来抗大量的读请求。正常情况下,读请求应该是均匀地落在10个缓存节点上的(负载均衡),那么这10个缓存节点,每秒承载1万个请求是差不多的。然后我们来做一个假设,一个节点承载2万个请求是极限,所以一般就限制一个节点正常承载1万个请求就ok了,因为要稍微留一些buffer出来。所谓的热点缓存问题,就是突然因为莫名的原因,出现大量的用户访问同一条缓存数据。举个例子就是,某个明星突然宣布跟某某结婚,这个时候就可能会引发短时间内每秒有数十万的用户去查看这条结婚的新闻。假设这条新闻是一个缓存,然后对应就是一个缓存key,就存在一台缓存机器上,假设这时候有20万个请求一起奔向这台缓存机器上的一个缓存key上,就会引发热点缓存问题。

    通过上图就很明显看出问题来了。我们刚才假设的是一个缓存Slave节点最多每秒接受2万的请求(当然了,实际的缓存单机承载5万~10万的都请求也是可能的),此时每秒却突然奔过来20万的请求到这台机器上,那么这台机器就会被这20万请求弄宕机。一旦缓存集群开始出现机器的宕机,那么读请求发现读不到数据,就会从数据库里面去提取原始数据,然后将这些数据放到剩余的其他缓存机器里面去。但是接踵而来的每秒20万的请求还是会接着压垮其他的缓存机器,周而复始,最终导致缓存集群全盘奔溃,引发系统的整体宕机。

    基于流式计算技术的缓存热点自动发现

    这里关键的一点,就是对于这种热点缓存,系统需要能够在热点缓存突然发生的时候,直接发现它,然后瞬间立马实现毫秒级的自动负载均衡。那么如何实现自动发现热点缓存问题呢?首先,一般出现缓存热点的时候,每秒并发肯定是很高的,可能每秒都几十万甚至上百万的请求量过来,多是可能的。所以此时完全可以基于大数据领域的流式计算技术来进行实时数据访问次数的统计,比如storm、spark streaming或flink,这些技术都是可以的。然后一旦在实时数据访问次数统计的过程中,比如发现一秒之内,某条数据突然访问次数超过了1000,就直接立刻把这条数据判定为是热点数据,可以将这个发现出来的热点数据写入比如zookeeper中。当然,系统如何判定热点数据可以根据自己的业务还有经验值来。

    那么流式计算系统在进行数据访问次数统计的时候,会不会也存在说单台机器被请求每秒几十万次的问题呢?答案是否,因为流式计算技术,尤其是storm这种系统,可以做到同一条数据的请求过来,先分散在很多机器里进行本地计算,最后再汇总局部计算结果到一台机器进行全局汇总。所以几十万的请求可以先分散在比如100台机器上,每台机器统计了这条数据的几千次请求。然后100条局部计算好的结果汇总到一台机器做全局计算即可,所以基于流式计算技术来进行统计是不会有热点问题的。

    热点缓存自动加载为JVM本地缓存

    现在系统可以对zookeeper指定的热点缓存对应的znode进行监听了,如果有变化,系统立马就感知到了。这时,系统层就可以立马把相关的缓存数据从数据库加载出来,然后直接放在自己系统内部的本地缓存即可。这个本地缓存,用ehcach,hashmap都可以,具体看业务需求,主要就是要将缓存集群里的集中式缓存直接变成每个系统自己本地实现的缓存即可,每个系统自己本地是无法缓存过多数据的。因为一般这种普通系统单实例,部署机器可能就是一个4核8G的机器,留给本地缓存的空间是很少的,所以用来放这种热点数据的本地缓存是最合适的。

    假设系统层集群部署了100台机器,这时这100台机器瞬间在本地都会有一份热点缓存的副本。然后接下来对热点缓存的读操作,直接系统本地缓存都出来就会返回了,不需要再走缓存集群了。这样的话,也不可能允许每秒20万的读请求到达缓存机器的一台机器上读一个热点缓存了,而是变成100台机器每台机器承载数千个请求,这数千请求直接从机器的本地缓存返回数据。

    限流熔断保护

    除此之外,在每个系统内部,其实还应该专门加一个对热点数据访问的限流熔断保护措施。每个系统实例的内部,都可以加一个熔断保护机制,假设缓存集群最多每秒承载4万读请求,那么一共有100个系统实例。这时候就要限制好,每个系统实例每秒最多请求缓存集群读操作不超过400次,一超过就可以熔断掉,不让请求缓存集群,直接返回一个空白信息,然后用户稍后会自行再次重新刷新页面之类的。通过系统层自己直接加限流熔断保护措施,就可以很好地保护后面的缓存集群、数据库集群之类的不会被打死。

    总结

    具体要不要在系统里面实现这种复杂的缓存热点优化架构,要看系统有没有这种场景。如果系统有热点缓存问题,那么就要实现类似的复杂热点缓存支撑架构。但是如果没有的话,也别过度设计,系统可能并不需要这么复杂的架构,反而会成为拖累。

    "人这一生,都是命。你的禀赋就决定了你90分的人生。别人的帮助可以决定9分,你自己的努力只占1分。但是只为了这1分,你就要拼尽全力,因为这1分是你唯一能够争取的,这1分弥足珍贵。"

  • 相关阅读:
    Go语言实现:【剑指offer】复杂链表的复制
    Go语言实现:【剑指offer】字符串的排列
    Go语言实现:【剑指offer】机器人的运动范围
    Delphi10.3的DBGrid美化(03)
    uniGUI学习之uniButton设置图标+扁平化CSS(34)
    UniGUI学习之hBox布局(33)
    Bug集锦
    ImageNamed、imageWithContentsOfFile、SupportingFiles、Images.xcassess
    MVC设计模式
    [转]iOS开发之视图控制器(UIViewController)
  • 原文地址:https://www.cnblogs.com/yanggb/p/11113474.html
Copyright © 2011-2022 走看看