• 常见缓存问题处理-缓存热点key


    参考:

    https://blog.csdn.net/shipfei_csdn/article/details/103380618

    https://www.cnblogs.com/rjzheng/p/10874537.html

    缓存热点key的处理探讨

    使用缓存集群的时候,最怕的就是热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分弥足珍贵。"

    【原创】谈谈redis的热key问题如何解决

    引言

    讲了几天的数据库系列的文章,大家一定看烦了,其实还没讲完。。。(以下省略一万字)。
    今天我们换换口味,来写redis方面的内容,谈谈热key问题如何解决。
    其实热key问题说来也很简单,就是瞬间有几十万的请求去访问redis上某个固定的key,从而压垮缓存服务的情情况。
    其实生活中也是有不少这样的例子。比如XX明星结婚。那么关于XX明星的Key就会瞬间增大,就会出现热数据问题。
    ps:hot key和big key问题,大家一定要有所了解。
    本文预计分为如下几个部分

    • 热key问题
    • 如何发现
    • 业内方案

    正文

    热Key问题

    上面提到,所谓热key问题就是,突然有几十万的请求去访问redis上的某个特定key。那么,这样会造成流量过于集中,达到物理网卡上限,从而导致这台redis的服务器宕机。
    那接下来这个key的请求,就会直接怼到你的数据库上,导致你的服务不可用。

    怎么发现热key

    方法一:凭借业务经验,进行预估哪些是热key
    其实这个方法还是挺有可行性的。比如某商品在做秒杀,那这个商品的key就可以判断出是热key。缺点很明显,并非所有业务都能预估出哪些key是热key。
    方法二:在客户端进行收集
    这个方式就是在操作redis之前,加入一行代码进行数据统计。那么这个数据统计的方式有很多种,也可以是给外部的通讯系统发送一个通知信息。缺点就是对客户端代码造成入侵。
    方法三:在Proxy层做收集
    有些集群架构是下面这样的,Proxy可以是Twemproxy,是统一的入口。可以在Proxy层做收集上报,但是缺点很明显,并非所有的redis集群架构都有proxy。

    方法四:用redis自带命令
    (1)monitor命令,该命令可以实时抓取出redis服务器接收到的命令,然后写代码统计出热key是啥。当然,也有现成的分析工具可以给你使用,比如redis-faina。但是该命令在高并发的条件下,有内存增暴增的隐患,还会降低redis的性能。
    (2)hotkeys参数,redis 4.0.3提供了redis-cli的热点key发现功能,执行redis-cli时加上–hotkeys选项即可。但是该参数在执行的时候,如果key比较多,执行起来比较慢。
    方法五:自己抓包评估
    Redis客户端使用TCP协议与服务端进行交互,通信协议采用的是RESP。自己写程序监听端口,按照RESP协议规则解析数据,进行分析。缺点就是开发成本高,维护困难,有丢包可能性。

    以上五种方案,各有优缺点。根据自己业务场景进行抉择即可。那么发现热key后,如何解决呢?

    如何解决

    目前业内的方案有两种
    (1)利用二级缓存
    比如利用ehcache,或者一个HashMap都可以。在你发现热key以后,把热key加载到系统的JVM中。
    针对这种热key请求,会直接从jvm中取,而不会走到redis层。
    假设此时有十万个针对同一个key的请求过来,如果没有本地缓存,这十万个请求就直接怼到同一台redis上了。
    现在假设,你的应用层有50台机器,OK,你也有jvm缓存了。这十万个请求平均分散开来,每个机器有2000个请求,会从JVM中取到value值,然后返回数据。避免了十万个请求怼到同一台redis上的情形。
    (2)备份热key
    这个方案也很简单。不要让key走到同一台redis上不就行了。我们把这个key,在多个redis上都存一份不就好了。接下来,有热key请求进来的时候,我们就在有备份的redis上随机选取一台,进行访问取值,返回数据。
    假设redis的集群数量为N,步骤如下图所示

    注:不一定是2N,你想取3N,4N都可以,看要求。
    伪代码如下

    const M = N * 2
    //生成随机数
    random = GenRandom(0, M)
    //构造备份新key
    bakHotKey = hotKey + “_” + random
    data = redis.GET(bakHotKey)
    if data == NULL {
        data = GetFromDB()
        redis.SET(bakHotKey, expireTime + GenRandom(0,5))
    }
    

    业内方案

    OK,其实看完上面的内容,大家可能会有一个疑问。

    烟哥,有办法在项目运行过程中,自动发现热key,然后程序自动处理么?

    嗯,好问题,那我们来讲讲业内怎么做的。其实只有两步
    (1)监控热key
    (2)通知系统做处理
    正巧,前几天有赞出了一篇《有赞透明多级缓存解决方案(TMC)》,里头也有提到热点key问题,我们刚好借此说明
    (1)监控热key
    在监控热key方面,有赞用的是方式二:在客户端进行收集
    在《有赞透明多级缓存解决方案(TMC)》中有一句话提到

    TMC 对原生jedis包的JedisPool和Jedis类做了改造,在JedisPool初始化过程中集成TMC“热点发现”+“本地缓存”功能Hermes-SDK包的初始化逻辑。

    也就说人家改写了jedis原生的jar包,加入了Hermes-SDK包。
    那Hermes-SDK包用来干嘛?
    OK,就是做热点发现本地缓存
    从监控的角度看,该包对于Jedis-Client的每次key值访问请求,Hermes-SDK 都会通过其通信模块将key访问事件异步上报给Hermes服务端集群,以便其根据上报数据进行“热点探测”。

    当然,这只是其中一种方式,有的公司在监控方面用的是方式五:自己抓包评估
    具体是这么做的,先利用flink搭建一套流式计算系统。然后自己写一个抓包程序抓redis监听端口的数据,抓到数据后往kafka里丢。
    接下来,流式计算系统消费kafka里的数据,进行数据统计即可,也能达到监控热key的目的。

    (2)通知系统做处理
    在这个角度,有赞用的是上面的解决方案一:利用二级缓存进行处理。
    有赞在监控到热key后,Hermes服务端集群会通过各种手段通知各业务系统里的Hermes-SDK,告诉他们:"老弟,这个key是热key,记得做本地缓存。"
    于是Hermes-SDK就会将该key缓存在本地,对于后面的请求。Hermes-SDK发现这个是一个热key,直接从本地中拿,而不会去访问集群。

    除了这种通知方式以外。我们也可以这么做,比如你的流式计算系统监控到热key了,往zookeeper里头的某个节点里写。然后你的业务系统监听该节点,发现节点数据变化了,就代表发现热key。最后往本地缓存里写,也是可以的。

    通知方式各种各样,大家可以自由发挥。本文只是提供一个思路。

    总结

    希望通过本文,大家明白如何处理生产上遇到的热key问题。

     
  • 相关阅读:
    sharedWorker 实现多页面通信
    cookie跨域那些事儿
    event loop整理
    tsConfig/baseUrl -- 一键告别相对路径import
    vscode配置golang开发环境手把手描述篇
    Vue学习笔记二
    Vue学习笔记
    echarts迁移图动态加载
    病虫害可视化监测平台(一)
    昆虫识别开发进展APP(四)
  • 原文地址:https://www.cnblogs.com/xuwc/p/14013060.html
Copyright © 2020-2023  润新知