redis热key 秒杀 redis的热key_实时计算


在 Redis 中,热Key指的是那些在一段时间内访问频次比较高的Key。由于热Key会对系统稳定性和可用性造成影响,最终引发用户不满,因此,在日常的工作中,开发者需要着重避免这种情况的出现。

近日,饿了么 CI 框架工具部后端专家韩亮在InfoQ分享了快速定位Redis热Key的经验,供你参考。

可能的方案

由于热Key不可能完全避免,因此,需要有一种方法能够在出现问题的时候快速定位问题根源。以下是业界常见的几种方案:

1. 客户端收集上报

通过改动 Redis SDK,记录每个请求,定时上报收集到的数据,然后由统一的服务进行聚合计算。这种方案直观简单,但没法适应多语言架构,一方面多语言SDK对齐是个问题,另一方面后期SDK的维护升级会面临较大困难,成本很高。

2. 代理层收集上报

如果所有的 Redis 请求都经过代理的话,可以考虑改动 Proxy 代码进行收集,思路与客户端基本类似。该方案对使用方完全透明,能够解决客户端 SDK 的语言异构和版本升级问题,不过开发成本高。

3. Redis数据定时扫描

Redis 在 4.0 版本之后添加了 Hotkeys 查找特性,但由于需要扫描整个 keyspace,因此,此方案实时性比较差。另外,扫描耗时与 key 的数量正相关,如果 key 的数量比较多,耗时可能会非常长。

4. Redis节点抓包解析

在可能存在热 key 的节点上 (通过流量倾斜判断),通过 TCPDump 抓取一段时间内的流量并上报,然后由一个外部的程序进行解析、聚合和计算。该方案无需侵入现有的 SDK 或者 Proxy 中间件,开发维护成本可控,但是,热 key节点的网络流量和系统负载已经比较高了,如果此时再进行抓包操作,可能会使情况进一步恶化。

饿了么的选择

饿了么选择了方案二,即在代理层进行收集上报。在饿了么内部,所有的Redis请求都要经过透明代理Samaritan。由于该代理是由团队内部开发维护的,因此,在代理层改造上成本完全受控。

不过我们也需要考虑几个具体的细节,比如:

  • 1、记录所有请求,如何保证不占用过多的内存,甚至 OOM ?
  • 2、记录所有请求,如何保证代理的性能及请求耗时不会有明显的上升?

针对第一个细节,由于只关心热key而不是要统计所有key的Counter,因此,可以使用LFU(最近最久未使用算法) 只保留访问频次最高的。对于第二个细节,则需要结合代理的具体实现去考虑。

下图是代理内部的实现方案,这里略去了一些无关的细节:


redis热key 秒杀 redis的热key_redis 如何查看某个库的key_02


每个 Redis节点都会创建与之对应的唯一 Client,其上的所有请求都采用 Pipeline 执行。

每个 Client 内部都有自己的 Hotkey Collector,不同 Collector 间相互独立。

Hotkey Collector 包含 LFU Counter、Syncer 和 Etrace Client 三部分。LFU Counter 负责记录 key 的访问频次,Syncer 会定期将统计数据通过 Etrace Client 发送给远端的服务器。

另外,为了避免向服务端发送过多无效的数据,内部会预先设置一个阈值,超过阈值的数据才会被发送到服务端。

按照预先的设计,会有一个实时计算的服务去拉取 Etrace 上的数据,进行聚合计算得到当前的热点 key。但不幸的是,代理中间件改造上线后的很长一段时间内,这个实时计算服务的开发都未被提上日程,主要是因为投资回报率低和维护成本高,因此,在业务上如果要查热 key 就只能在 Etrace 上手动戳 event 碰运气。

由于这样使用起来很麻烦,因此,用户在第一次体验之后就基本放弃了。

最终的方案

上述方案之所以最后放弃,主要有两个方面的问题没有解决:

  • 1、如何在不增加实时计算组件提升成本的前提下高效地聚合数据?
  • 2、如何提升用户体验,让用户方便地使用?

因此,如果我们能优化上述两个问题,或许就能找到最终的方案。

下面我们来看看。

针对第一点,我们可以把聚合逻辑放在代理进程内,这样就不用再依赖任何外部组件,可以降低整个系统的复杂度和维护成本。

针对第二点,如果聚合的数据在进程内,可以提供 Hotkey 类似的自定义命令,让用户通过 redis-cli 直接获取。

所以最终的方案如下:


redis热key 秒杀 redis的热key_redis 如何查看某个库的key_03


每个集群会有一个全局的 Hotkey Collector,每个 Client 上有自己独立的 Counter,Counter 依旧采用前面提到的 LFU 算法,Collector 会定时收集每个 Counter 的数据并进行聚合,聚合的时候不会使用真实的计数,而是使用概率计数,并且为了适应访问模式的变化,Counter 值会随着时间衰减,整体上与 Redis LFU 非常类似。

此方案虽然能够快速定位系统中的热 key,但实际上并没有真正解决热 key 本身带来的问题,仍然需要业务方自行改造或者将那些热点 key 调度到单独的节点上,因此,成本较高。此外,也需要不断优化内存、数据一致性和性能的问题,所以,后续可能会考虑在代理内实现热点 key 的缓存。