共计 3675 个字符,预计需要花费 10 分钟才能阅读完成。
这篇“Redis 中怎么应对缓存热 key 问题”文章的知识点大部分人都不太理解,所以丸趣 TV 小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Redis 中怎么应对缓存热 key 问题”文章吧。
背景
热 key 是什么问题,如何导致的?
一般来说,我们使用的缓存 Redis 都是多节点的集群版,对某个 key 进行读写时,会根据该 key 的 hash 计算出对应的 slot,根据这个 slot 就能找到与之对应的分片 (一个 master 和多个 slave 组成的一组 redis 集群) 来存取该 K -V。但是在实际应用过程中,对于某些特定业务或者一些特定的时段(比如电商业务的商品秒杀活动),可能会发生大量的请求访问同一个 key。所有的请求(且这类请求读写比例非常高)都会落到同一个 redis server 上,该 redis 的负载就会严重加剧,此时整个系统增加新 redis 实例也没有任何用处,因为根据 hash 算法,同一个 key 的请求还是会落到同一台新机器上,该机器依然会成为系统瓶颈 2,甚至造成整个集群宕掉,若此热点 key 的 value 也比较大,也会造成网卡达到瓶颈,这种问题称为“热 key”问题。
如下图 1、2 所示,分别是正常 redis cluster 集群和使用一层 proxy 代理的 redis 集群 key 访问。
如上所说,热 key 会给集群中的少部分节点带来超高的负载压力,如果不正确处理,那么这些节点宕机都有可能,从而会影响整个缓存集群的运作,因此我们必须及时发现热 key、解决热 key 问题。
1. 热 key 探测
热 key 探测,看到由于 redis 集群的分散性以及热点 key 带来的一些显著影响,我们可以通过由粗及细的思考流程来做热点 key 探测的方案。
1.1 集群中每个 slot 的 qps 监控
热 key 最明显的影响是整个 redis 集群中的 qps 并没有那么大的前提下,流量分布在集群中 slot 不均的问题,那么我们可以最先想到的就是对于每个 slot 中的流量做监控,上报之后做每个 slot 的流量对比,就能在热 key 出现时发现影响到的具体 slot。虽然这个监控最为方便,但是粒度过于粗了,仅适用于前期集群监控方案,并不适用于精准探测到热 key 的场景。
1.2 proxy 的代理机制作为整个流量入口统计
如果我们使用的是图 2 的 redis 集群 proxy 代理模式,由于所有的请求都会先到 proxy 再到具体的 slot 节点,那么这个热点 key 的探测统计就可以放在 proxy 中做,在 proxy 中基于时间滑动窗口,对每个 key 做计数,然后统计出超出对应阈值的 key。为了防止过多冗余的统计,还可以设定一些规则,仅统计对应前缀和类型的 key。这种方式需要至少有 proxy 的代理机制,对于 redis 架构有要求。
1.3 redis 基于 LFU 的热点 key 发现机制
redis 4.0 以上的版本支持了每个节点上的基于 LFU 的热点 key 发现机制,使用 redis-cli –hotkeys 即可,执行 redis-cli 时加上–hotkeys 选项。可以定时在节点中使用该命令来发现对应热点 key。
如下所示,可以看到 redis-cli –hotkeys 的执行结果,热 key 的统计信息,这个命令的执行时间较长,可以设置定时执行来统计。
1.4 基于 Redis 客户端做探测
由于 redis 的命令每次都是从客户端发出,基于此我们可以在 redis client 的一些代码处进行统计计数,每个 client 做基于时间滑动窗口的统计,超过一定的阈值之后上报至 server,然后统一由 server 下发至各个 client,并且配置对应的过期时间。
这个方式看起来更优美,其实在一些应用场景中并不是那么合适,因为在 client 端这一侧的改造,会给运行的进程带来更大的内存开销,更直接的来说,对于 Java 和 goLang 这种自动内存管理的语言,会更加频繁的创建对象,从而触发 gc 导致接口响应耗时增加的问题,这个反而是不太容易预料到的事情。
最终可以通过各个公司的基建,做出对应的选择。
2. 热 key 解决
通过上述几种方式我们探测到了对应热 key 或者热 slot,那么我们就要解决对应的热 key 问题。解决热 key 也有好几种思路可以参考,我们一个一个捋一下。
2.1 对特定 key 或 slot 做限流
一种最简单粗暴的方式,对于特定的 slot 或者热 key 做限流,这个方案明显对于业务来说是有损的,所以建议只用在出现线上问题,需要止损的时候进行特定的限流。
2.2 使用二级(本地)缓存
本地缓存也是一个最常用的解决方案,既然我们的一级缓存扛不住这么大的压力,就再加一个二级缓存吧。由于每个请求都是由 service 发出的,这个二级缓存加在 service 端是再合适不过了,因此可以在服务端每次获取到对应热 key 时,使用本地缓存存储一份,等本地缓存过期后再重新请求,降低 redis 集群压力。以 java 为例,guavaCache 就是现成的工具。以下示例:
// 本地缓存初始化以及构造
private static LoadingCache String, List Object configCache
= CacheBuilder.newBuilder()
.concurrencyLevel(8) // 并发读写的级别,建议设置 cpu 核数
.expireAfterWrite(10, TimeUnit.SECONDS) // 写入数据后多久过期
.initialCapacity(10) // 初始化 cache 的容器大小
.maximumSize(10)//cache 的容器最大
.recordStats()
// build 方法中可以指定 CacheLoader,在缓存不存在时通过 CacheLoader 的实现自动加载缓存
.build(new CacheLoader String, List Object () {
@Override
public List Object load(String hotKey) throws Exception {
}
});
// 本地缓存获取
Object result = configCache.get(key);
本地缓存对于我们的最大的影响就是数据不一致的问题,我们设置多长的缓存过期时间,就会导致最长有多久的线上数据不一致问题,这个缓存时间需要衡量自身的集群压力以及业务接受的最大不一致时间。
2.3 拆 key
如何既能保证不出现热 key 问题,又能尽量的保证数据一致性呢?拆 key 也是一个好的解决方案。
我们在放入缓存时就将对应业务的缓存 key 拆分成多个不同的 key。如下图所示,我们首先在更新缓存的一侧,将 key 拆成 N 份,比如一个 key 名字叫做 good_100,那我们就可以把它拆成四份,good_100_copy1、good_100_copy2、good_100_copy3、good_100_copy4,每次更新和新增时都需要去改动这 N 个 key,这一步就是拆 key。
对于 service 端来讲,我们就需要想办法尽量将自己访问的流量足够的均匀,如何给自己即将访问的热 key 上加入后缀。几种办法,根据本机的 ip 或 mac 地址做 hash,之后的值与拆 key 的数量做取余,最终决定拼接成什么样的 key 后缀,从而打到哪台机器上;服务启动时的一个随机数对拆 key 的数量做取余。
2.4 本地缓存的另外一种思路 配置中心
对于熟悉微服务配置中心的伙伴来讲,我们的思路可以向配置中心的一致性转变一下。拿 nacos 来举例,它是如何做到分布式的配置一致性的,并且相应速度很快?那我们可以将缓存类比配置,这样去做。
长轮询 + 本地化的配置。首先服务启动时会初始化全部的配置,然后定时启动长轮询去查询当前服务监听的配置有没有变更,如果有变更,长轮询的请求便会立刻返回,更新本地配置;如果没有变更,对于所有的业务代码都是使用本地的内存缓存配置。这样就能保证分布式的缓存配置时效性与一致性。
2.5 其他可以提前做的预案
上面的每一个方案都相对独立的去解决热 key 问题,那么如果我们真的在面临业务诉求时,其实会有很长的时间来考虑整体的方案设计。一些极端的秒杀场景带来的热 key 问题,如果我们预算充足,可以直接做服务的业务隔离、redis 缓存集群的隔离,避免影响到正常业务的同时,也会可以临时采取更好的容灾、限流措施。
一些整合的方案
目前市面上已经有了不少关于 hotKey 相对完整的应用级解决方案,其中京东在这方面有开源的 hotkey 工具,原理就是在 client 端做洞察,然后上报对应 hotkey,server 端检测到后,将对应 hotkey 下发到对应服务端做本地缓存,并且这个本地缓存在远程对应的 key 更新后,会同步更新,已经是目前较为成熟的自动探测热 key、分布式一致性缓存解决方案,京东零售热 key。
以上就是关于“Redis 中怎么应对缓存热 key 问题”这篇文章的内容,相信大家都有了一定的了解,希望丸趣 TV 小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注丸趣 TV 行业资讯频道。