如何快速定位 Redis 热 key?

背景

 

在 Redis 中,热 key 指的是那些在一段时间内访问频次比较高的键值,具体到业务上,商品的限时抢购、瞬时的新闻热点或某个全局性的资源,都极有可能产生热点 key。html

 

热点 key 的出现可能会对系统的稳定性和可用性形成影响,好比对应节点的网卡带宽被打满,出现丢包重传,请求波动耗时大幅上升,甚至影响到业务的正常使用,引起用户的不满。所以,在平常的工做中,咱们须要着重避免这种状况的出现,好比在设计和编码阶段避免引入全局性热 key,或者在设计时考虑热 key 出现时的应对方案。node

 

可能的方案

 

热点 key 即便咱们在设计和开发时已经极力避免,然而在真实的生产环境中仍是可能依旧存在的,致使其继续出现的缘由有如下几种:redis

  • 有一些边界 case 没有考虑到
  • 异常或非预期的流量

 

既然不可能彻底避免,咱们就须要有一种方法可以在出问题的时候快速定位有没有热 key 以及热 key 具体是啥,来帮助业务快速排障,定位问题的根源。若是要设计定位方案的话,咱们能够从 Redis 请求路径上的节点来着手,好比在客户端、中间层和服务端,具体来讲以下:算法

  1. 客户端收集上报改动 Redis SDK,记录每一个请求,定时把收集到的数据上报,而后由一个统一的服务进行聚合计算。方案直观简单,但无法适应多语言架构,一方面多语言 SDK 对齐是个问题,另一方面后期 SDK 的维护升级会面临比较大的困难,成本很高。
  2. 代理层收集上报若是全部的 Redis 请求都通过代理的话,能够考虑改动 Proxy 代码进行收集,思路与客户端基本相似。该方案对使用方彻底透明,可以解决客户端 SDK 的语言异构和版本升级问题,不过开发成本会比客户端高些。
  3. Redis 数据定时扫描Redis 在 4.0 版本以后添加了 hotkeys 查找特性[1],能够直接利用 redis-cli --hotkeys 获取当前 keyspace 的热点 key,实现上是经过 scan + object freq 完成的。该方案无需二次开发,可以直接利用现成的工具,但因为须要扫描整个 keyspace,实时性上比较差,另外扫描耗时与 key 的数量正相关,若是 key 的数量比较多,耗时可能会很是长。
  4. Redis 节点抓包解析在可能存在热 key 的节点上(流量倾斜判断),经过 tcpdump 抓取一段时间内的流量并上报,而后由一个外部的程序进行解析、聚合和计算。该方案无需侵入现有的 SDK 或者 Proxy 中间件,开发维护成本可控,但也存在缺点的,具体是热 key 节点的网络流量和系统负载已经比较高了,抓包可能会状况进一步恶化。

Redis 的 Monitor 命令不在考虑之列,缘由是开销比较大,单个 monitor 的 client 会下降 50% 的系统吞吐,更多详情见: https://redis.io/commands/monitor缓存

 

咱们的选择

 

因为在饿了么内部,全部的 Redis 请求都是通过透明代理 Samaritan[2] 的,而且该代理是由咱们本身开发维护的,在代理层改造的成本彻底受控,所以咱们选择了方案二,即在代理层进行收集上报。服务器

 

大的方向肯定以后,须要考虑具体的细节,好比:网络

  1. 记录全部请求如何可以保证不占用过多的内存甚至 OOM ?
  2. 记录全部请求如何可以保证代理的性能, 请求耗时不会有明显的上升?

 

针对第 1 点,既然咱们只关心热 key 而不是要统计全部 key 的 counter,那么就能够用 LFU 只保留访问频次最高的,第 2 点则须要结合代理具体的实现去考虑。架构

 

下图是代理内部的实现方案, 略去了一些无关的细节:负载均衡

如何快速定位 Redis 热 key?

 

注:tcp

  • 每一个 redis node 会建立一个与之对应的惟一的 client,其上的全部请求都采用 pipeline 执行
  • 每一个 client 内部都有本身的 Hotkey Collector,不一样 Collector 间相互独立

 

Hotkey Collector 内部结构以下所示,包含 LFU Counter、Syncer 和 Etrace Client 三部分:

如何快速定位 Redis 热 key?

 

Etrace 是一个内部的应用监控平台,相似的开源产品是 CAT [3]

基本的工做流程是,LFU Counter 负责记录 key 的访问频次,Syncer 会按期将统计数据经过 Etrace Client 发送给远端的服务器。另外,为了不向服务端发送过多无效的数据,内部会预先设置一个阈值,超过阈值的才发送到服务端。

 

按照预先的设计,咱们将会有一个实时计算的服务去拉取 Etrace 上的数据,进行聚合计算获得当前的热点 key。但不幸地是代理中间件改造上线后的很长一段时间内,这个实时计算服务的开发都未被提上日程,分析下来主要是 ROI 低和维护成本高,所以在业务上若是要查热 key 就只能在 Etrace 上手动戳 event 碰运气好比:

如何快速定位 Redis 热 key?

 

因为使用起来很麻烦,用户在第一次体验以后基本就放弃了,不会再用第二次,甚至连咱们本身都不肯意使用… 在当时咱们急须要找到一种更好的方案去解决用户体验和系统复杂度的问题,让该特性能真正地赋能于业务。

 

最终的方案

 

对前面方案进行优化的话,能够从如下两个方面入手:

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

 

针对第一点,当时第一个想法是能不能把聚合逻辑放在代理进程内,这样的话就不用再依赖任何外部组件,能够下降整个系统的复杂度和维护成本。但这里会有个问题,以前设计外部聚合组件的初衷是为了聚合不一样机器的数据,如今采用单机数据会不会有问题,逻辑是否是站得住脚?

 

仔细思考下来,逻辑上是成立的,由于到达业务的流量是负载均衡过的,不一样实例上的流量是比较均匀的,差不太多的,基于这个局部能够表明总体的原则,那么单实例上的热 key 就能够表明全局的一个状况。

另外,就易用性和使用体验上来讲,若是聚合的数据在进程内,咱们能够提供 HOTKEY 相似的自定义命令,让用户经过 redis-cli 直接获取。

 

最终的方案以下,已略去无关细节:

如何快速定位 Redis 热 key?

 

实现上来讲,每一个集群会有一个全局的 Hotkey Collector,每一个 client 上有本身独立的 Counter,Counter 依旧采用前面提到的 LFU[4] 算法,Collector 会定时地去收集每一个 Counter 的数据并进行聚合,聚合的时候不会使用真实的计数,而是使用几率计数[5],而且为了适应访问模式的变化 counter 的值会随着时间衰减,总体上与 redis lfu[6]很是相似。

 

下面是一个生产环境的真实例子,展现了近一段时间内比较热的 key:

如何快速定位 Redis 热 key?

 

注:

  1. 默认使用的 log factor 因子是 10,counter 值每分钟衰减一半
  2. Collector 默认的容量是 32,只记录请求频次最高的 32 个 key
  3. 输出的结果与 redis-cli --hotkeys 很是相似,counter 具体含义能够参考 Using Redis as an LRU cache[7] 一文末尾表格

 

后续的规划

 

当前的方案虽然可以快速定位系统中的热 key,但并无真正解决热 key 自己带来的问题,仍旧须要业务方自行改造或者将那些热点 key 调度到单独的节点上,成本是比较高的,甚至有的业务还会本身作 local cache。

 

本着更好地服务于客户的原则,咱们后面将会考虑在代理内实现热点 key 的缓存,不过在代理内实现缓存的话须要先解决内存占用、数据一致性和性能的问题,这块暂时尚未很是好的方案,仍旧在调研之中,好的消息是 Redis 6 计划实现 server-assisted client side caching[8],若是有可能的话咱们会第一时间考虑对接。

 

最后,热 key 实时收集的功能已经上线,而且也进行了开源,相关源代码能够在 Samaritan 中找到,有兴趣的朋友能够进行尝试,有问题和想法也欢迎提 issue 或者直接与我交流。

相关文章
相关标签/搜索