赞
踩
通常来说,如果一个键值对大于一定阈值(例如 1MB),它就可以被认为是一个大键或 bigkey。
1.占用大量内存,可能导致内存不足
2.增加内存碎片,降低内存利用效率
3.增加SAVE和复制时间
4.增加AOF重写时间
5.压缩列表元素过多,降低查找效率
所以在使用 Redis 时,应该尽量避免 bigkey 的产生。
1.一个 Hash 类型键值对字段过多
2.一个 Set 类型包含了过多的成员
3.一个 List 类型包含了过多的元素
4.一个 Bitmaps 类型映射了过大的内容
5.一个 Zset 类型包含了过多的成员
一般建议 bigkey 的大小控制在 1MB 以内为宜。
Redis 中存在的BigKey会带来以下几个方面的危害:
BigKey由于值过于庞大,会占用Redis sehr大比例的内存,从而对Redis的内存使用造成浪费和压力。
当需要对BigKey进行操作如删除、更改值时,这些操作需要花费较长时间,会造成Redis主进程阻塞,影响Redis的正常工作。
Redis需要进行持久化将数据写入磁盘保存,BigKey会因其数据量大而导致持久化过程非常缓慢。
当Redis进行主从复制时,同步BigKey也会非常慢,可能导致从服务器的数据同步效率下降。
BigKey的大值会导致Redis内存中产生大量不连续的碎片,降低内存利用效率。
BigKey会导致AOF文件过大,当Redis进行AOF重写时,需要处理大量数据,加重服务器压力。
对于一些集合性的数据结构如Hash、List等,BigKey中的数据量庞大,会降低查找效率。
所以,对于Redis来说,发现并解决BigKey问题是非常重要的。
在Redis中,可以通过以下几种方式发现BigKey:
使用Redis的info命令,查看keyspace部分,观察biggest_key_size的大小,当biggest_key_size过大时,说明存在BigKey。
可以通过analyze命令的keyspace部分来观察biggest_key_size,判断是否存在BigKey。例如:
127.0.0.1:6379> info keyspace
# Keyspace
db0:keys=5,expires=0,avg_ttl=0
db1:keys=8,expires=0,avg_ttl=0
db2:keys=4,expires=0,avg_ttl=0
db3:keys=5,expires=0,avg_ttl=0
db4:keys=4,expires=0,avg_ttl=0
db5:keys=5,expires=0,avg_ttl=0
db6:keys=5,expires=0,avg_ttl=0
db7:keys=4,expires=0,avg_ttl=0
db8:keys=4,expires=0,avg_ttl=0
db9:keys=4,expires=0,avg_ttl=0
db10:keys=5,expires=0,avg_ttl=0
db11:keys=5,expires=0,avg_ttl=0
db12:keys=5,expires=0,avg_ttl=0
db13:keys=4,expires=0,avg_ttl=0
db14:keys=4,expires=0,avg_ttl=0
db15:keys=4,expires=0,avg_ttl=0
# ... 略
biggest_key_size:8192
从上面的biggest_key_size大小可以看出,目前存在value超过8KB的大键。
使用scan命令迭代Redis中的键,同时利用Redis的debug object命令查看key对应的value大小,如果超过阈值即判定为BigKey。
可以通过SCAN命令迭代Redis中的键,并配合DEBUG OBJECT命令判断键值大小,来发现BigKey。例如:
127.0.0.1:6379> scan 0 match * count 10
1) "17"
2) 1) "key:1"
2) "key:2"
3) "key:3"
4) "key:4"
5) "bigkey"
127.0.0.1:6379> debug object bigkey
Value at:0x7fdafb0258e0 refcount:1 encoding:raw serializedlength:1001927
上面先通过SCAN命令扫描出键,然后使用DEBUG OBJECT判断bigkey的值大小,结果显示serializedlength超过1MB。所以这是一个BigKey。我们可以编写一个循环使用SCAN的程序,设置一个大小阈值,所有值超过阈值的键都记录下来,从而扫描出所有BigKey。相比直接遍历键空间,SCAN实现渐进式扫描,可以避免命令阻塞,更安全的发现BigKey。
使用redis-cli工具的–bigkeys参数,该参数会扫描Redis中的键并返回所有大小超过指定阈值的BigKey。
使用一些第三方Redis可视化管理工具,如Redis Enterprise、Astra等,这些工具提供了BigKey检测功能。
分析Redis日志,关注slave缓慢或者持久化被block的情况,这可能与BigKey有关。
可以通过脚本等定期主动扫描Redis键值,当键值超过阈值则记录为BigKey。
下面写一个脚本示例
import redis
import time
# Redis连接
redis_client = redis.Redis(host='localhost', port=6379)
# 大键阈值 - 100MB
BIG_KEY_THRESHOLD = 100 * 1024 * 1024
# 扫描间隔 - 1小时
SCAN_INTERVAL = 3600
def scan_big_keys():
cursor = '0'
big_keys = []
while cursor != 0:
cursor, keys = redis_client.scan(cursor=cursor, count=100)
for key in keys:
size = redis_client.debug_object(key)['serializedlength']
if size > BIG_KEY_THRESHOLD:
big_keys.append({'key': key, 'size': size})
return big_keys
while True:
big_keys = scan_big_keys()
if big_keys:
print(f'Found {len(big_keys)} big keys')
for bk in big_keys:
print(f' {bk["key"]} {bk["size"]}')
time.sleep(SCAN_INTERVAL)
主要步骤包括:
1.使用SCAN命令渐进式扫描键空间
2.调用DEBUG OBJECT命令获取键值大小
3.比较大小判断是否为大键
4.定期循环扫描这个脚本可以灵活调整大键阈值、扫描间隔等参数。
你可以将它部署为持续运行的任务,自动发现Redis中的大键。
综合利用上述各种方式,可以有效发现Redis中存在的BigKey,作为后续优化的依据。
在Redis中删除BigKey可以通过以下几种方法:
直接使用DEL命令删除键即可快速删除单个BigKey,但是这会导致数据丢失。
DEL big_key
如果BigKey是由于键设计不当导致的,那么可以重新设计键结构,拆分BigKey。
例如将原来的一个Hash BigKey拆分为多个Hash小Key。
UNLINK可以异步删除键,不会堵塞服务器。但也存在数据丢失风险。
UNLINK big_key
可以先用MULTI开启事务,再使用GET、DEL等命令处理BigKey,最后用EXEC提交事务。这样可以避免阻塞服务器。
对于List、Hash等结构的BigKey,可以通过区间查找、分段删除的方式进行分批删除,避免一次性删除耗时太长。
可以通过SCAN命令渐进式地扫描并删除BigKey。
可以考虑停止Redis服务,并删除持久化文件(rdb、aof),然后重启,这样可以直接清除所有BigKey,但会损失全部数据。
可以根据实际情况选择最佳方案删除BigKey。
对于Redis中的Hash类型Bigkey,可以通过以下几种方式进行优化:
最直接的方法是将一个大的Hash Key拆分为多个小的Hash Key,防止单个键值对过大。例如
将user:{uid} 拆分为 user:{uid}:info、user:{uid}:data等。
Redis的Hash相比字符串可以大幅度减少内存使用。可以考虑将字符串值转换为Hash结构,字段由字符串改为Hash。
如果Hash中的字段是简单的计数器,可以考虑用Bitmaps替代,由于Bitmaps有压缩特性,可以减少内存使用。
对于Hash的值,如果是数字,可以选择更紧凑的编码方式,如整形float编码转为整数int编码。
对Hash字段数量进行限制,避免单个Hash过多字段,导致结构过大。
调整Redis的ziplist、hash-max-ziplist-value等参数,优化内存使用。
redis>config get hash-max-ziplist-entries
1) "hash-max-ziplist-entries"
2) "512" --默认原来是512
redis>config set hash-max-ziplist-entries 1000
"OK"
redis>config get hash-max-ziplist-entries
1) "hash-max-ziplist-entries"
2) "1000" --调整为1000,但是不建议调整超过1000
如果Hash中存在大量小值字段,可以将这些小值字段拆分出去,单独用一个Hash Key存储。
假设我们有一个 user:{uid} 的 hash 类型 bigkey,里面存储了用户的名称、年龄、手机号等信息。其中手机号字段的值都是数字且较小。
我们可以这样进行海量小值的分离:
为手机号创建新的 hash key
user:{uid}:phone
遍历 user:{uid} 的 hash,提取所有手机号字段和值,存入 user:{uid}:phone
HGETALL user:{uid} # 遍历获取所有字段
...
HSET user:{uid}:phone {phone_field} {phone_value} # 存储到新key
从 user:{uid} 删除这些手机号字段
HDEL user:{uid} {phone_field_1} {phone_field_2} ... # 删除手机号字段
这样我们就将海量的手机号小值从大key中分离出来,单独用一个 hash 存储。
分离后的优点:
1.原来的 user:{uid} 键值对会缩小,不再存在大量小值
2.由于是小值,在新 key 中可以使用更紧凑的内存编码存储
3.查找手机号等小值时,只需要操作 user:{uid}:phone 即可,避免遍历全量大key
综合使用这些优化策略,可以有效减小Hash类型Bigkey的内存占用。
感谢您的支持和鼓励! 本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。