目录
[大 Key 处理流程图](#大 Key 处理流程图)
[1. 预防措施(设计阶段)](#1. 预防措施(设计阶段))
[2. 识别大 Key](#2. 识别大 Key)
[使用 SCAN 命令](#使用 SCAN 命令)
[3. 处理现有大 Key](#3. 处理现有大 Key)
[异步删除(Redis 4.0+)](#异步删除(Redis 4.0+))
[4. 配置优化](#4. 配置优化)
[5. 架构层面优化](#5. 架构层面优化)
Redis 处理大 Key 的主要方法可以分为预防、识别、处理和优化几个方面。下面我将详细说明每一步。
1.什么是大key
没有一个固定的数值可以定义所有场景下的大 Key。通常,我们可以将 String 类型超过 10 KB,集合类型元素超过 5000 作为大 Key 的参考阈值。但更重要的是,需要根据实际的业务场景、Redis 实例的配置和性能要求来定义适合自己的大 Key 阈值。
在不确定的情况下,建议进行性能测试,观察不同大小的 Key 对 Redis 操作延迟的影响,从而确定适合自己业务的大 Key 阈值。
实际案例中的建议
-
阿里巴巴 Redis 开发规范:建议字符串类型控制在 10 KB 以内,集合元素数量不要超过 5000。
-
腾讯云 Redis:建议字符串类型不超过 10 KB,集合元素数量不超过 4000。
大 Key 处理流程图
text
发现大 Key → 评估影响 → 选择策略
↓
预防为主 → 无法避免 → 渐进处理
↓
监控告警 → 异步删除 → 架构优化
-
预防大 Key
-
在设计阶段,避免存储过大的数据。例如,对于存储集合类型的数据,可以拆分多个键来存储。
-
使用合适的数据结构。例如,使用 HyperLogLog 代替集合进行基数统计,使用位图(bitmap)存储布尔值等。
-
对数据进行压缩后再存储(但需要注意,压缩和解压会增加 CPU 开销,而且 Redis 本身不支持压缩,需要客户端处理)。
-
-
识别大 Key
-
使用 Redis 自带的命令:例如
debug object key可以查看某个 key 的序列化长度,但注意这个命令会影响性能,不建议在线使用。 -
使用
redis-cli --bigkeys命令可以扫描整个数据库,并统计出大 key。这个命令是通过扫描所有 key,然后统计它们的大小,注意这个过程会阻塞 Redis,最好在从节点上执行或者低峰期执行。 -
使用 SCAN 命令编写脚本,逐步扫描所有 key,然后使用
strlen、hlen、llen、scard、zcard等命令获取 key 的大小。这样不会阻塞 Redis,但需要自己编写脚本。
-
-
处理大 Key
-
拆分大 Key:将一个大 key 拆分成多个小 key。例如,一个存储了百万个元素的哈希表,可以拆分成多个哈希表,每个哈希表存储一部分数据。然后通过分片的方式(例如使用固定的哈希策略)来访问。
-
删除大 Key:如果大 Key 不再需要,删除它。但是直接删除一个大的 key 可能会阻塞 Redis(因为 Redis 是单线程的)。所以建议使用渐进式删除:
-
对于哈希、列表、集合、有序集合,可以使用对应的命令进行渐进式删除。例如,对于哈希,可以使用
hscan每次获取一部分字段,然后使用hdel删除。 -
对于字符串,直接使用
del命令,但如果是非常大的字符串,也会阻塞一段时间。可以考虑使用unlink命令(Redis 4.0 以上),它会在后台异步删除。
-
-
对于集合类型,可以使用对应的扫描命令(如
sscan、hscan、zscan)来逐步删除。
-
-
优化大 Key 的使用
-
对于读取,可以考虑使用分片的方式,将请求分散到多个 key 上,减少单个 key 的压力。
-
对于写入,同样使用分片,避免对单个 key 频繁写入导致性能问题。
-
考虑使用 Redis 集群,将数据分布到多个节点上,这样每个节点上的 key 就不会太大。
-
-
使用 Redis 4.0 的新特性
-
Redis 4.0 引入了异步删除(unlink 命令),可以用于删除大 key 而不阻塞主线程。
-
Redis 6.0 引入了多线程,可以在删除大 key 时使用后台线程处理,减少阻塞。
-
-
监控和告警
-
定期监控 Redis 的内存使用情况,以及大 key 的情况。可以使用
INFO memory命令查看内存使用情况。 -
设置告警,当发现大 key 时及时处理。
-
下面是一个使用 SCAN 和 hscan 删除大哈希表的示例脚本(假设我们要删除一个名为 big_hash 的 key,它包含大量字段):每次扫描100个字段,然后删除它们**(渐进式删除)**
# 使用 hscan 每次扫描 100 个字段,然后删除它们
cursor=0
while true; do
# 使用 hscan 命令,每次获取 100 个字段
redis-cli hscan big_hash $cursor count 100 > /tmp/hscan_output.txt
# 读取输出,第一行是下一个游标,第二行是字段列表
cursor=$(head -1 /tmp/hscan_output.txt)
fields=$(tail -1 /tmp/hscan_output.txt)
# 将字段转换为数组,然后逐个删除
# 注意:这里假设字段之间用空格分隔,但实际上 hscan 输出的字段是每行一个,所以需要根据实际情况调整
# 以下是一个简单的处理方式,但可能不适用于所有情况
for field in $fields; do
redis-cli hdel big_hash $field
done
# 如果游标为0,表示扫描结束
if [ $cursor -eq 0 ]; then
break
fi
done
注意:上面的脚本是一个简单的示例,实际使用时需要根据实际情况调整,比如处理 hscan 输出的格式,以及考虑网络连接和错误处理。
另外,也可以使用编程语言(如 Python、Java)来编写更复杂的脚本,利用相应 Redis 客户端的 SCAN 功能。
总结:处理大 Key 的核心思想是预防为主,及时发现并处理。在处理时,尽量避免阻塞 Redis 主线程,采用渐进式删除或异步删除。同时,通过拆分、分片等方式优化数据结构和访问模式。
Redis 处理大 Key 主要有以下几种方式:
1. 预防措施(设计阶段)
数据结构优化
- 拆分大 Key:将一个大的哈希/集合/列表拆分成多个小的 Key
bash
# 原始大 Key
hset user:1_data field1 value1 ... field10000 value10000
# 拆分成多个 Key
hset user:1:part1 field1 value1 ... field1000 value1000
hset user:1:part2 field1001 value1001 ... field2000 value2000
-
使用合适的数据结构
-
HyperLogLog 替代大集合进行基数统计
-
Bitmaps 替代布尔值数组
-
考虑使用其他存储(如关系数据库)存储大对象
-
2. 识别大 Key
使用工具检测
# Redis 自带命令
redis-cli --bigkeys
# 扫描模式
redis-cli --bigkeys -i 0.1 # 每100毫秒扫描一次,减少对业务影响
# RDB 分析工具
redis-rdb-tools # 分析 RDB 文件找出大 Key
使用 SCAN 命令
使用 SCAN 命令编写脚本,逐步扫描所有 key,然后使用 strlen、hlen、llen、scard、zcard 等命令获取 key 的大小。这样不会阻塞 Redis,但需要自己编写脚本。
# 编写脚本定期扫描
EVAL "
local cursor = 0
repeat
local result = redis.call('SCAN', cursor, 'COUNT', 100)
cursor = tonumber(result[1])
local keys = result[2]
for i, key in ipairs(keys) do
local type = redis.call('TYPE', key)
local size = 0
if type == 'string' then
size = redis.call('STRLEN', key)
elseif type == 'hash' then
size = redis.call('HLEN', key)
elseif type == 'list' then
size = redis.call('LLEN', key)
elseif type == 'set' then
size = redis.call('SCARD', key)
elseif type == 'zset' then
size = redis.call('ZCARD', key)
end
if size > 10000 then -- 设置阈值
print(key, type, size)
end
end
until cursor == 0
" 0
3. 处理现有大 Key
渐进式删除
# 对于大 Hash - 使用 hscan + hdel
cursor=0
while true; do
result=$(redis-cli HSCAN big_hash $cursor COUNT 100)
cursor=$(echo "$result" | head -1)
fields=$(echo "$result" | tail -n +2 | awk '{print $1}')
for field in $fields; do
redis-cli HDEL big_hash $field
done
if [[ $cursor -eq "0" ]]; then
break
fi
done
# 对于大 Set - 使用 sscan + srem
# 对于大 ZSet - 使用 zscan + zrem
# 对于大 List - 分批次 ltrim
redis-cli LTRIM big_list 0 99 # 保留前100个元素
异步删除(Redis 4.0+)
# 使用 UNLINK 替代 DEL(异步删除)
redis-cli UNLINK big_key
# 配置异步删除参数(redis.conf)
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
replica-lazy-flush yes
4. 配置优化
# 监控告警
# 设置内存使用阈值
maxmemory 4gb
maxmemory-policy allkeys-lru
# 客户端缓冲区限制
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
5. 架构层面优化
使用集群模式
# 将数据分散到不同节点
# 大 Key 会自动分布到不同实例
redis-cli --cluster create node1:6379 node2:6379 ...
6.总结建议
-
设计阶段避免大 Key:合理拆分数据
-
定期扫描检测:建立监控体系
-
使用渐进式操作:避免阻塞 Redis
-
升级到 Redis 4.0+:利用异步删除特性
-
考虑数据迁移:将大对象存储到其他存储系统
-
容量规划:提前预估数据增长
记住:预防胜于治疗,良好的数据模型设计是避免大 Key 问题的根本。