深入研究bigkey问题与解决方案

Posted by WGrape的博客 on May 15, 2021

文章内容更新请以 WGrape GitHub博客 : 深入研究bigkey问题与解决方案 为准

前言

本文原创,著作权归WGrape所有,未经授权,严禁转载

一、什么是bigkey

Redis是基于内存的Key-Value数据存储系统,如果Value大小超过阈值,那么此时存储这个Value的Key就是bigkey,至于阈值则根据不同场景需求不尽相同

image

二、bigkey的危害

1、数据倾斜

bigkey所在的Redis服务器,会相比Redis切片集群中其他服务器占用明显过多的内存,这就是数据倾斜。数据倾斜现象与切片设计初衷背道而驰,影响切片集群整体性能

image

2、服务器资源耗费严重

(1) 网络带宽

bigkey会明显需要更长的传输时间,在整个传输时间内,占用大量的带宽,导致网络阻塞

(2) CPU和内存

对于bigkey的操作,Redis处理Value所需要的时间复杂度也会增加,会占用更多的CPU和内存

3、主线程阻塞

如果对bigkey的操作由主线程执行,由于bigkey的处理需要更多时间,就会出现主线程阻塞的情况。一旦在Redis高峰期触发此场景,服务会有明显卡顿,甚至瘫痪

三、bigkey常见问题

1、HASH数据删除原理

对于Redis中每一个HASH类型的数据来说,其数据结构如下所示,主要有两张哈希表ht[0]ht[1]ht[1] 主要用于扩容,所以一般只有第一张哈希表中有数据,其中 table 字段指向一个数组,下标为不同Key经过运算后的哈希值 image

无论是同步还是异步删除,最终都会执行 dictRelease 函数,依次遍历两张哈希表 ht[0]ht[1],最后再释放掉当前根节点所占用的内存

image

依次遍历每一个元素,并释放掉其占用的内存 image

由此可见,上述删除操作所耗费的时间主要是 数据结构遍历内存回收

2、bigkey删除需要多久时间

(1) 官方描述

官方提供的信息中描述删除 String 类型时间复杂度为 O(1),删除复杂类型如 ListSetSorted SetHASH 的时间复杂度为 O(M),其中M表示其中元素的数量

(2) 本地测试

在本地的测试结果如下,随着元素和内存占用的增长,DEL耗时也会增加

序号 HASH 元素个数 内存占用 DEL耗时(5次测试的均值)
1 100/1000/1万/10万 1.17M/2.54M/16M/153M
2 40万 609M 0.82s
3 80万 1.19G 1.75s
4 100万 1.48G 2.24s
5 120万 1.79G 2.65s
6 140万 2.08G 3.17s
7 200万 2.97G 4.66s

注 :由于本地电脑性能原因,上述测试结果在性能更高机器上的耗时大概可以降低50%

(3) 测试结果

在保持相同Value值,元素数量从80万增长到140万的情况下,Redis自身的耗时几乎呈线性关系

image

(4) 测试推论

在不考虑内存等外部因素的影响下,删除耗时存在下述关系

  • 删除1G的数据,Redis需要大概1秒钟
  • 删除包含100万个元素的数据,Redis大概需要1秒钟

    (5) 耗时因素分析

    ① 网络堵塞

    当网络出现严重堵塞时,导致命令无法正常传输至Redis,会导致耗时的异常增高

    ② 时间复杂度高

    通过查看HASH数据删除源码,可知对于集合类型数据的删除操作,时间复杂度为O(N),因为需要遍历每一个元素,所以元素越多,不可避免的会消耗更多时间

    ③ 内存回收异常

    Redis内存管理器在回收大量的内存的时候,性能明显下降,主要有以下两个方面

  • 内部性能 :面对更大数据时,内存管理器自身性能的消耗
  • 内存碎片 :大量元素的删除,引发内存碎片率增高,降低内存管理器的工作效率

    ④ Swap引起延迟

    在服务器内存不足时,操作系统会将Redis部分内存移至swap空间,当Redis再次需要此部分内存的时候,操作系统会再次把磁盘中的数据读入内存,由于磁盘IO速度远小于内存IO,所以Swap引起延迟会导致Redis出现明显延迟

    ⑤ 总结

    在网络正常情况下,它们之间的耗时关系为 :Swap > 内存回收 > 数据结构的遍历

    3、bigkey删除导致服务瘫痪

    如果删除bigkey导致服务瘫痪,则说明此时删除操作一定是在主线程中触发,可能是手动执行DEL命令,或者是未开启Lazy Free机制导致过期时在主线程触发删除(过期事件在主线程中执行)

    四、bigkey解决方案

    1、避免出现bigkey

    (1) 规范使用

    bigkey的出现一般是程序设计不规范和滥用的原因,所以根本上避免bigkey需要从规范使用上入手

  • 尽量添加TTL
  • 使用“拆分存储”的方式
  • 更大的数据的存储需求,不要使用Redis

    (2) 监控报警

    提供Redis监控机制,当某些Key的占用内存超过设定阈值时,及时报警通知

    (3) 强制删除

    当key的占用内存超过最大阈值,并在规定时间内未处理时,则触发强制删除策略

    2、bigkey内存优化

    (1) 减少内存碎片

    通过减少内存碎片的方式,间接提高内存管理器工作效率

  • 尽量保持数据对齐
  • 重启Redis服务,通过内存重排减少碎片
  • 开启Redis的 activedefrag 自动整理碎片功能

    (2) 避免出现Swap

    释放不必要的内存空间,避免出现Swap

    (3) 丰富监控指标

    监控Redis的内存碎片率、Linux当前的Swap值,及时发现异常

    2、安全删除bigkey

    (1) 异步删除

  • 开启lazy free功能,如果触发自动过期删除,则会异步执行
  • 使用unlink命令手动触发,会异步执行删除操作

    (2) 使用分批删除

    对于集合类型的数据,可以通过scan轮询的方式,每次只删除一部分的数据