写点什么

如何快速排查发现 redis 的 bigkey?4 种方案一次性给到你!

发布于: 2021 年 05 月 08 日
如何快速排查发现redis的bigkey?4种方案一次性给到你!

本篇文章将以 redis 的 bigkey 为主题进行技术展开,通过从认识 redis 的高性能,bigkey 的危害、存在原因、4 种解决方案,到模拟实战演练的介绍方式,来跟大家一起认识、探讨和学习 redis。

先认识一下 redis 和 bigkey 吧

redis——互联网的宠儿


redis 作为一款优秀的工业级内存型数据库,自诞生后便逐渐成为互联网的宠儿,支撑起了互联网丰富多彩的功能和巨大的 QPS(每秒查询率),并和 nginx 一样成为高性能的代名词,比如微博上的每一条热搜背后都有着 redis 默默守候。从某种意义上来说,redis 的使用量也代表着一家互联网公司的流量。


在冯诺伊曼计算体系中,内存是一种重要的存在。计算和存储之间存在着一定的辩证关系,通过计算可以减少存储,通过存储可以减少一定量的消耗。因此,缓存的思想也成为系统性能减少计算量的一种重要优化手段。


我们看两张对比图。

这张是 cpu、内存、磁盘的性能对比。内存的读写性能是磁盘的近一千倍,redis 作为内存型存储介质,对系统性能提升具有革命性的变化,经济基础决定上层建筑,所以经济效益永远是第一位的。


再看下各种存储的价格。

我们简单评估分析下:内存折算大约 30 元 1G,磁盘大约 0.5 元 1G,相比性能的差别,经济效益的投入产出比还是很高的。


redis 的存储数据结构:redis 是一种 key/value 结构的存储型数据库,内部组织形式使用了 hashMap 的这种数据存取索引结构,数据的读取时间复杂度为 O(1)。hashMap 的 key 为 string 类型,valuek 可以为 string、list、hashMap、set 和 zset 五种基本数据结构。这些数据都存储在内存中,具有高效的读写性能,常被用来做缓存处理,提高系统的性能。


底层存储结构如下:

支撑起 redis 高性能的另一个设计实现就是单线程的任务处理设计。


在面对一个巨大工作量时,为了尽快完成工作任务,通常都会选择加人,把任务拆解成多份并行处理。这就是一个简单的多线程并发处理的思想,多线程可以提高任务处理的吞吐量。


那么为什么 redis 却反其道而行之,使用单线程的方式?这里的单线程是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。


 先来看下,如果使用多线程,在处理网络 IO 时会存在哪些问题?线程是 cpu 调度的基本单位,cpu 通过时钟中断,进行时间片的切换,对外表现出并行处理的效果,任务切换必然消耗资源。当开启很多的线程,到一定量后,这些切换就会达到性能的瓶颈。


还有一个就是多线程下的资源共享问题,这也是并发编程的核心问题。并发环境下存在资源竞争,就需要对共享资源的临界区进行加锁处理,把并发处理转化为串行化处理,redis 的数据结构中,底层使用的 hashMap 索引数据结构,在多线程处理情况下,必然出现资源争夺的问题,这即又变成一个串行同步化的处理过程。因此,我们在此否定了多线程。

那么来看看单线程在这种场景下有什么好处

请求端和服务端通过 tcp 三次握手后会建立网络连接,请求数据从网卡被写入到操作系统的内核缓冲区中,用户程序执行 read 操作,会将数据从内核空间写入到用户程序执行的变量中,如果内核空间还未收到数据,这里就会发生阻塞等待。


这种处理方式我们显然是不能够接受的,为了解决这个技术问题,一种被叫做 IO 多路复用的技术被创造出来,在 linux 下一共有三种实现,select、poll、epoll,,简单来说,该机制允许内核中同时存在多个监听套接字和已连接套接字,再完成读写就绪,通知用户态来执行。具体这里不做展开,有兴趣的朋友可以网上搜下。


nodejs、nginx 这些网关层的技术,都是单线程的设计,在处理网络 IO 上,单线程要比多线程优异。但是单线程也有其致命的弱点,一旦处理中的某个请求任务处理过长,就会阻塞后边的请求。这也是后文要展开的 bigkey 危害的主要原因。就像单行道上,某辆车出现了故障,就会出现堵车现象一个道理。

一、什么是 bigkey?

从上图的 redis 底层数据存储结构中,可以看到 value 具有多种数据结构的实现,因此,value 大小在字符串类型,表现为字符串的长度;value 为复合类型,则表现为元素的个数。


bigkey 就是 redis key/value 体系中的大 value 问题。根据数据类型的划分,bigkey 体现在两点:


  • 存储数据为 string 类型, value 值长度过大;

  • value 为复合类型,包含元素个数过多。


在 redis 中,一个字符串最大 512MB,一个二级数据结构(例如 hash、list、set、zset)可以存储大约 40 亿个(2^32-1)个元素,这是一个理论值,实际使用时,我们可以通过运维给到的数据来综合衡量限制数,一般 string 类型控制在 10KB 以内,复合类型 hash、 list,、set,和 zset 元素个数不超过 5000 个。

二、bigkey 有什么危害?产生原因?

看到这里,我们对 bigkey 已经有一个初步的了解。接下来,我们针对 bigkey 的危害和产生原因一一进行介绍。

1、bigkey 的四大危害

俗话说“一颗老鼠屎坏掉一锅汤”,对于 redis 而言,bigkey 就像是老鼠屎的存在。其危险性主要表现为以下四个方面:

1.内存空间不均匀

在集群模式中,由于 bigkey 的存在,会造成主机节点的内存不均匀,这样会不利于集群对内存的统一管理,存在丢失数据的隐患。

2.超时阻塞

由于 redis 单线程的特性,操作 bigkey 通常比较耗时,也就意味着阻塞 redis 可能性越大,这样会造成客户端阻塞或者引起故障切换。慢查询通常就会有它们的身影。

3.网络拥塞

bigkey 也就意味着每次获取要产生的网络流量较大。假设一个 bigkey 为 1MB,客户端每秒访问量为 1000,那么每秒产生 1000MB 的流量,对于普通的千兆网卡(按照字节算是 128MB/s)的服务器来说简直是灭顶之灾。

4.阻塞删除

有个 bigkey,对它设置了过期时间,当它过期后会被删除,如果使用 Redis 4.0 之前的版本,过期 key 是异步删除,就会存在阻塞 redis 的可能性,而且这个过期删除不会从慢查询发现(因为这个删除不是客户端产生的,是内部循环事件)。

2、bigkey 怎么产生的?

bigkey 的产生主要是由于程序的设计不当所造成的,如以下几种常见的业务场景


  • 社交类:粉丝列表,如果某些明星或者大 v 不精心设计下,必是 bigkey。

  • 统计类:例如按天存储某项功能或者网站的用户集合,除非没几个人用,否则必是 bigkey。

  • 缓存类:将数据从数据库 load 出来序列化放到 redis 里,这个方式经常常用,但有两个地方需要注意:第一,是不是有必要把所有字段都缓存;第二,有没有相关关联的数据。


由此可见,在程序设计中,我们要对数据量的增长和边界有一个基本性的评估,做好技术选型和技术架构。

三、4 种排查发现 bigkey 的解决方案

先看一个思考题:


今天年初,石家庄陆续爆发新冠疫情,对于一个有着 1000 万多人口的中大型城市,疫情防控面临着巨大的压力,怎么高效的发现病毒感染人群和接触人群,成为疫情防控取胜的关键。政府做了以下几方面工作,简单概括为 4 点:


  1. 禁止人员流通,居家隔离;

  2. 制定风险等级;

  3. 网格化管理;

  4. 核算检测。


根据流行病医学特点,出现症状必须主动上报。这种是主动上报,因为新冠疫情具有一定的潜伏期,很多无症状患者,因此就需要通过核算检测的机制,主动地发现,这其实就体现了一种计算机处理扫描的思想。

发现、处理 bigkey 的思想和疫情防控的做法有些相似,常规做法也有四种。

1、redis 客户端工具

redis-cli 提供了--bigkeys 来查找 bigkey,例如下面就是一次执行结果。

从上图可以看出这种方式给出了每种数据结构的 top 1 bigkey,同时给出了每种数据类型的键值个数以及平均大小。但如果我们需要更多的 bigkey,这种方式就无法做到。它的内部是通过 scan 的方式进行的,有一定的性能开支,为了不影响业务,可把该任务放到从节点上进行执行。

2、debug object

redis 提供了一个 debug object key 的命令。假设有一个“找出 redis 中大于 10KB 以上的 key"需求,为了得到该结果数据,就需要先扫描出所有的 key,然后通过循环调用 debug object key 得到所有 key 的字节大小。

由于 debug object key 的执行效率很慢,会存在阻塞 redis 线程的可能。因此该种方案,对业务也会存在一定的损伤,在使用时,可将该执行程序运行到从节点之上。

3、RDB 文件扫描

我们知道,redis 有一种持久化的方案叫做 RDB 持久化,它是 redis 内存存储数据的一个磁盘化快照,通过 RDB 工具对 RDB 文件进行扫描,可以查找出存在的 bigkey。


在选择这种方案时,首先需要做 RDB 文件持久化。RDB 持久化是一种内存快照的形式,按照一定的频次进行快照落盘,这种方案是一种理想化的选择,不会影响 redis 主机的运行,但在对数据可靠性要求很高的场景,不会选择 RDB 持久化方案,也因此它不具有普遍适用性。

4、DataFlux bigkey 的扫描设计思想

前面几种方案,要么由客户端发现,要么需要进行全量数据扫描,扫描是很消耗计算资源的一种行为。类比疫情,就好比是某千万人口的城市出现病例后,不分等级地对全员进行核酸检测,这不但消耗巨大的物力、财力、人力,效率还十分低下,跟需要和时间赛跑的疫情防控背道而驰。


上文我们也分析了 redis bigkey 产生的原因,很多都是业务的设计不合理和评估不足所导致的。因此 DataFlux 产品在设计中,就让 datakit 的 redis 采集器使用了一种自主配置潜在 bigkey 进行扫描发现的方案,支持固定 key 值和 key pattern。在 key pattern 中,通过 scan pattern 得到一定范围的 key,再通过 length 函数对每种类型的 key("HLEN""LLEN""SCARD""ZCARD""PFCOUNT""STRLEN")取值,得到对应的 key 的 length,上报 DataFlux 平台进行监控存储。


这种做法的好处就两点:


一、由于是针对目标 key 得到 length,而 redis 中各种数据类型的 length 值获取都是 O(1)时间复杂度。因此执行效率很高;

二、采集到的结果数据上报到 DataFlux 存储平台,在该平台下,可以对指标数据做各种图表展示,监控告警。


接下来,我们通过对该方案进行简单的业务场景模拟来展开说明。

四、实战演练,等的就是这刻!

在一个业务系统中,使用 redis 的 string 类型存储用户的认证 token, 使用 list 数据类型做异步消息队列。


业务分析:存储 token 的 key 数据是一个定长数据,不会有数据量的变化,不会形成 bigkey。再来看消息队列,如果消费端出现故障,消息生产端在这一时刻涌入了大量数据,这时使用消息队列的 redis key 就会成为一个潜在的 bigkey,因此,我们需要对该 key 进行监控。


我们假设消息队列的键名为 queue。


我们按官方教程,安装 datakit 工具。


官方教程:《如何安装 DataKit》

https://help.dataflux.cn/doc/ef29e8365e18d813a8ec5800bbcb1adf1f39ab37


安装好后,进入 DataKit 安装目录下的 conf.d/db 目录,复制 redis.conf.sample 并命名为 redis.conf。做下图配置:

模拟初始队列 push 10 个 value

数据上报到 DataFlux 平台

再 push 一定量的数据

最终可在 DataFlux 后台上看到如下采集结果。


在 DataFlux 平台上,通过指标可对监控的 key 进行图表展示、监控报警以及可视化展示等等,最大幅度呈现数据价值。

用户头像

守护每一个系统,成就每一个客户 2021.02.08 加入

一站式数据监测云平台

评论

发布
暂无评论
如何快速排查发现redis的bigkey?4种方案一次性给到你!