Redis高可用之热key问题解决方案 学习笔记

255 阅读6分钟

前言

通常,为了提高服务吞吐量,除了在数据库层面做优化之外,我们还可以通过搭配缓存的方式,来减轻数据库的访问压力,避免数据库成为性能瓶颈点。

例如电商大促的商品详情页,由于商品的名称、图片链接、价格等数据会被频繁访问,因此一般会被缓存,以便提升系统的性能。

什么是热key问题?

当从 Redis 读取数据时,Redis 代理服务会基于 Key 进行 Hash 计算,以此定位到 Redis 中实际存放数据的后端 Redis Server。所以,要是某个 Key 的访问 QPS 突然大幅攀升,那么这个 Key 所在的 Redis Server 就会有部分请求被迫排队,进而导致延迟升高,严重时还会出现超时失败的状况。这便是我们通常说的热key问题,热key就是那些被高频率访问的 Key

Redis分片架构如下所示

image.png

解决热key问题各种方案的核心思路殊途同归,便是借助更多的机器资源,分担热key的访问流量,大致上分为2大类:

  1. 在业务层进行改造,从而解决热 Key 问题;
  2. 对业务层透明的,云厂商(阿里云,腾讯云等)提供的Redis数据库层附带方案

业务层解决方案

一、本地缓存全量数据

这是最容易想到的方案,在服务的各个实例内存中,把所有数据都缓存起来。这样当请求访问时,就可以直接从服务实例的本地缓存中获取数据,而不用请求 Redis,防止发生热 Key 问题。

但是缺点和瓶颈也很明显,例如数据量超大,超出内存规格所能承受的范围,那么服务本地就无法缓存全部数据;又或者是为了避开一些GC(垃圾回收)带来的影响,不适合在内存缓存这么多数据。

二、本地缓存部分数据

为了应对上述困境,还存在一种仅对部分数据进行缓存的策略,即专门针对热 Key 数据进行缓存,而将其他数据排除在缓存范围之外。

但是,要实现这个策略,关键在于服务端得有办法知道哪些是热Key?

这得靠一套很完善的热Key探测框架,能又快又准地发现它们。例如,以京东推出的开源热key探测框架为例,主要包含两个关键步骤:

  1. key的访问上报与统计

由于同一个 Key 会在不同的服务实例中被访问,所以仅仅依靠单个客户端或者 Redis Proxy 难以完成热 Key 的统计工作。

为了能够精准地统计热 Key,就必须单独部署一个专门的 worker 服务。当我们访问某个 Key 时,需要调用 JdHotkey 框架提供的 SDK 进行上报。SDK 底层会周期性地把收集到的 Key 访问数据上报至这个统一的 worker 服务,以便开展聚合计算。

需要注意的是,已经热了的 Key 不会再次发送给 worker 服务,除非 Client 本地该 Key 缓存已过期。

针对不同服务实例访问相同 Key 的情况,框架 SDK 会基于一些hash sharing操作,将相同 Key 的访问上报到同一个 worker 实例。这样就能够有效对每个Key访问频率做出精确统计,为后续热 Key 的判定与处理提供可靠的数据支撑。

  1. 热key判断与通知

当专门负责统计的 worker 服务监测到热 Key 出现时,它会主动地将热 Key 信息推送到所有相关的业务服务实例,从而确保各个业务服务实例能够及时知晓热 Key 的情况,并据此做出相应的处理策略调整。

image.png

有了热Key探测框架后,服务的Redis访问流程会变成如下所示

image.png

但这种解决方案也带来了额外的复杂度,例如需要接入特定的热key探测SDK,还需要部署一个额外的 worker 服务来做统计。

云厂商附带解决方案

有没有办法避免对业务层的改造,在 Redis数据库层解决热 Key问题呢?

实际上,我们可以利用各个云厂商的 Redis 所提供的功能,来解决热 Key 问题,从而避免增加业务逻辑的复杂性,主要有2种常见方案:

一、读写分离

给分片 Server 增加更多的从库,让从库来承载 Key 的读访问,从而避免热 Key 访问影响单台 Redis Server 的性能 image.png

不过,由于我们事先没办法确定究竟哪些 Key会成为热 Key,所以为了保险起见,我们就只能对所有的 Redis Server都进行扩充从库的操作。这样一来,必然会使得所耗费的 Redis资源成本大幅增加。

二、Proxy缓存

我们知道,在计算机领域,没有什么是加一层中间层不能解决的。

为了解决上述资源成本问题,不少云厂商都额外提供了一种借助 Proxy来承载热 Key的方案。以阿里云Redis的Proxy Query Cache为例,启用该功能后,代理节点会缓存热点Key对应的请求和查询结果,当在有效时间内收到同样的请求时直接返回结果至客户端,无需和后端的数据分片交互。

这类方案的核心思路在于,并非针对每个 Redis Server 都去扩充从库,而是巧妙地利用 Proxy 来缓存热 Key 数据,承担热 Key 访问所带来的压力。这样在应对热 Key 问题时,既能有效缓解系统压力,又能避免因大规模扩充从库而导致的资源浪费。

image.png

实现步骤大致如下:

  1. 首先,当对 Key 进行访问时,在 Redis Server 端进行 Key 访问频率统计;
  2. 当 Redis Server 判定为热 Key 时,通过推或者拉的模式,将这个热 Key 及其相关数据缓存到 Proxy 之中;
  3. 后续当有热 Key 被访问时,直接从 Proxy缓存获取数据返回,而不再请求 Redis Server,从而减轻单个 Redis Server 的压力;
  4. 最后,当 Proxy 缓存的数据过期时,重新从 Redis Server 获取

参考

《极客时间 Go服务开发高手课》