[Redis]bigkey
redis中的大key和unlink操作
Redis中的BigKey通常指的是那些包含大量元素的复合数据类型,如一个列表包含数百万个元素,或一个字符串的大小超过512MB。这些BigKey会在执行操作时消耗大量的CPU和内存资源,影响Redis的响应时间。
BigKey的产生原因
- 不合理的数据设计
一个典型的例子是将用户的所有行为数据存放在一个大的List中,而不是分拆成多个小List按时间或者事件类型存储。 - 业务逻辑变化
随着业务发展,原本预计不会存储大量数据的Key,可能因为用户量的增长或业务逻辑的变化变成了BigKey。
什么是大key?
首先大key不是key很大而是key对应的value值很大,一般而言如果String类型值大于10KB,Hash,Set,Zset,List类型的元素的个数大于5000个都可以称之为大key.
大key的危害
-
客户端超时等待:由于Redis执行命令是单线程处理,然后在操作大key时会比较耗时,那么就会阻塞Redis,从客户端这一视角来看就是很久很久都没有响应
-
引发网络阻塞:每次获取大key产生的流量较大,如果一个key的大小是1MB,每秒访问量为1000,那么每秒会产生1000MB的流量这对于普通千兆网卡是灾难的
-
阻塞工作线程:如果使用del删除大key,会阻塞工作线程这样就没有办法处理后续的命令
-
内存分布不均匀:集群模型在slot分片均匀的情况下,会出现数据和查询倾斜情况,部分有大key的Redis节点占用内存多,QPS比较大
定位大key
redi-cli --bigkeys
使用时注意事项:最好在从节点上执行该命令或者在Redis实例业务压力的低峰阶段进行扫描查询,因为key很多时会很慢
不足之处
这个方法只能返回每种类型中最大的那个bigkey
,无法得到大小排到前N位的bigkey
对于集合类型来说,这个方法只统计集合元素的多少,而不是实际占用的内存量
。因为一个集合中元素个数多,并不一定占用内存就多
使用SCAN命令查找大key
使用SCAN命令对数据库进行扫描。然后用TYPE命令获取返回的每一个key的类型
对于String类型,可以直接使用STRLEN
命令获取字符串长度,也就是占用的内存空间字节数
对于集合类型来说可以使用MEMORY USAGE
命令,查询有关键值对占用的内存空间 基于redis的keys、scan删除ttl为-1的key
使用RdbTools
使用第三方开源工具,可以解析Redis快照,找到其中的大key
删除大key
分批次删除
使用SCAN扫描key,比如删除Hash,先取100字段删除删除再取
异步删除
在Redis4.0版本开始,可以采用异步删除法用unlink
命令代替del删除
这样Redis会将这个key放入到一个异步线程中进行删除,这样不会阻塞主线程
被动删除
前面两种都是主动删除,这一种是通过配置参数,当认为需要删除的时候就删除了
- lazyfree-lazy-eviction:表示当 Redis 运行内存超过 maxmeory 时,是否开启 lazy free 机制删除;
- azyfree-lazy-expire:表示设置了过期时间的键值,当过期之后是否开启 lazy free 机制删除;
- lazyfree-lazy-server-del:有些指令在处理已存在的键时,会带有一个隐式的 del 键的操作,比如 rename 命令,当目标键已存在,Redis 会先删除目标键,如果这些目标键是一个 big key,就会造成阻塞删除的问题,此配置表示在这种场景中是否开启 lazy free 机制删除;
- slave-lazy-flush:针对 slave (从节点) 进行全量数据同步,slave 在加载 master 的 RDB 文件前,会运行 flushall 来清理自己的数据,它表示此时是否开启 lazy free 机制删除。
建议开启其中的 lazyfree-lazy-eviction、lazyfree-lazy-expire、lazyfree-lazy-server-del 等配置,这样就可以有效的提高主线程的执行效率
如何避免大key
对大key进行拆分
将一个Big Key拆分为多个key-value这样的小Key,并确保每个key的成员数量或者大小在合理范围内,然后再进行存储,通过get不同的key或者使用mget批量获取。
对大key进行清理
对Redis中的大Key进行清理,从Redis中删除此类数据。Redis自4.0起提供了UNLINK命令,该命令能够以非阻塞的方式缓慢逐步的清理传入的Key,通过UNLINK,你可以安全的删除大Key甚至特大Key
监控Redis内存、网络带宽、超时等指标
通过监控系统并设置合理的Redis内存报警阈值来提醒我们此时可能有大Key正在产生,如:Redis内存使用率超过70%,Redis内存1小时内增长率超过20%等。
压缩value
使用序列化、压缩算法将key的大小控制在合理范围内,但是需要注意序列化、反序列化都会带来一定的消耗。如果压缩后,value还是很大,那么可以进一步对key进行拆分。
什么是 bigkey
- Key 本身的数据量过大:一个 String 类型的 Key,它的值为 5 MB。
- Key 中的成员数过多:一个 ZSET 类型的 Key,它的成员数量为 10,000 个。
- Key 中成员的数据量过大:一个 Hash 类型的 Key,它的成员数量虽然只有 1,000 个但这些成员的 Value(值)总大小为 100 MB。
bigkey 的影响
- 集群架构下,某个数据分片的内存使用率远超其他数据分片,无法使数据分片的内存资源达到均衡
- 如果 bigkey 一直在增长,会导致 Redis 内存 OOM,影响读写性能
- 因为 Redis 是单线程的,对 bigkey 的增删改操作,会导致超时,甚至阻塞服务
- 对大 Key 执行del删除操作,易造成主库较长时间的阻塞,进而可能引发同步中断或主从切换
如何避免 bigkey
如果是 hash 的话,通过一定规则拆分到不同 hash 中,特别是在集群中很有效果
堆积大量过期数据会造成大 Key 的产生,例如在 HASH 数据类型中以增量的形式不断写入大量数据而忽略了数据的时效性。可以通过定时任务的方式对失效数据进行清理。
对大 Key 进行直接删除
如何查看 bigkey
可以通过 Redis 客户端命令查看:
redis-cli --bigkeys
# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).
[00.00%] Biggest string found so far '"frontLastPullSysMsgId:1257:964350"' with 5 bytes
[00.00%] Biggest string found so far '"exam_data:1233:913172:39017:140692"' with 8885 bytes
[00.01%] Biggest string found so far '"exam_data:319:877777:14274:126728"' with 14875 bytes
[00.03%] Biggest list found so far '"live_merge:154_159588"' with 9 items
[00.06%] Biggest string found so far '"exam_data:319:1101505:52328:151653"' with 19125 bytes
[00.07%] Biggest hash found so far '"timer_ids:company_id:1134:sys_id:5"' with 11 fields
[00.17%] Biggest string found so far '"exam_data:908:644980:22627:146279"' with 31320 bytes
[00.30%] Biggest hash found so far '"timer_ids:company_id:927:sys_id:5"' with 130 fields
[00.73%] Biggest hash found so far '"timer_ids:company_id:264:sys_id:2"' with 551 fields
[01.19%] Biggest string found so far '"exam_data:973:693508:33936:128891"' with 40643 bytes
[03.09%] Biggest hash found so far '"timer_ids:company_id:1183:sys_id:5"' with 625 fields
[03.29%] Biggest hash found so far '"timer_ids:company_id:1183:sys_id:2"' with 1410 fields
[06.39%] Biggest string found so far '"exam_data:301:104065:51535:165675"' with 127069 bytes
[06.75%] Biggest hash found so far '"websocket_duration"' with 1560568 fields
[37.36%] Biggest string found so far '"exam_data:301:104065:51536:165675"' with 128746 bytes
[81.08%] Sampled 1000000 keys so far
-------- summary -------
Sampled 1233274 keys in the keyspace!
Total key length in bytes is 48419022 (avg len 39.26)
Biggest list found '"live_merge:154_159588"' has 9 items
Biggest hash found '"websocket_duration"' has 1560568 fields
Biggest string found '"exam_data:301:104065:51536:165675"' has 128746 bytes
14 lists with 47 items (00.00% of keys, avg size 3.36)
2736 hashs with 1726892 fields (00.22% of keys, avg size 631.17)
1230524 strings with 1649082886 bytes (99.78% of keys, avg size 1340.15)
0 streams with 0 entries (00.00% of keys, avg size 0.00)
0 sets with 0 members (00.00% of keys, avg size 0.00)
0 zsets with 0 members (00.00% of keys, avg size 0.00)
优点:方便、快速、安全
缺点:分析结果不可定制化,准确性与时效性差
可以使用 redis-rdb-tools 或者 xueqiu/rdr,有对应的图形界面看着也很清晰
优点:支持定制化分析,对线上服务无影响
缺点:时效性差,RDB 文件较大时耗时较长
如何删除 bigkey
Redis 4.0 及之后版本:您可以通过 UNLINK 命令安全地删除大 Key 甚至特大 Key,该命令能够以非阻塞的方式,逐步地清理传入的 Key。
Redis 4.0 之前的版本:建议先通过 SCAN 命令读取部分数据,然后进行删除,避免一次性删除大量 key 导致 Redis 阻塞。
清理 HASH 数据时,建议通过 HSCAN 命令配合 HDEL 命令对失效数据进行清理,避免清理大量数据造成 Redis 阻塞。
UNLINK
Redis 4.0 开始引入了 UNLINK 命令,他和 del 的作用是一样的。但是区别就在于 del 是单线程的,如果删除的是 bigkey 的话,会导致整个服务都被占用。
UNLINK 其实就是把 del 的操作分到了 2 个线程里面:
- 在主线程中,从整个键空间中删除键
- 在另一个线程回收内存
虽然分在了 2 个线程里,但是操作也是安全的,并不会带来我删着删着又被读到了的并发问题。因为它(在主线程中)从键空间中删除了对象,因此任何 Redis 命令都无法访问它。
如果你有很大的值,速度会显著提高——UNLINK 是一个 O(1) 操作(每个键;在主线程中),而不管键中保存的值的大小。使用 DEL 删除一个大值可能需要几百毫秒或更长时间,而 UNLINK 将在不到一毫秒的时间内完成(包括网络往返)。
但是并不是说 del 无用武之地了,当需要实时响应的时候最好还是 del,因为毕竟 UNLINK 是一个异步操作,需要父线程和子线程之间的一些同步操作,在删除小key的时候,这些同步操作甚至比删除键值本身开销更大,所以用del更合适。