某银行客户在Redis开发准则中要求,拒绝使用big key。
当 Redis 中存在比较大的键(keys)时,可能会引发一些性能问题,比如内存占用过高、数据操作耗时增加等。
定义
对于big key的定义如下:
vbnet
代码解读
复制代码
Big key:key-value键值对超过10k,则认为是big key。 如果出现了big key,则很容易造成慢查询,阻塞其他的请求,同时,也会对网络造成负担。 所以要求string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。 若非必须,不要使用del删除bigkey,对于非字符串的bigkey建议使用hscan、sscan、zscan渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查))。
扫描
使用lua脚本扫描big key。
创建一个lua脚本文件, scan_big_keys.lua, 内容如下:
lua
代码解读
复制代码
local cursor = "0" local big_keys = {} local limit_size = 10240 -- 10 KB local limit_count = 5000 repeat local result = redis.call("SCAN", cursor) cursor = result[1] local keys = result[2] for _, key in ipairs(keys) do local key_type = redis.call("TYPE", key).ok if key_type == "string" then local size = redis.call("STRLEN", key) if size > limit_size then table.insert(big_keys, key .. " (size: " .. size .. " bytes)") end elseif key_type == "hash" then local count = redis.call("HLEN", key) if count > limit_count then table.insert(big_keys, key .. " (count: " .. count .. ")") end elseif key_type == "list" then local count = redis.call("LLEN", key) if count > limit_count then table.insert(big_keys, key .. " (count: " .. count .. ")") end elseif key_type == "set" then local count = redis.call("SCARD", key) if count > limit_count then table.insert(big_keys, key .. " (count: " .. count .. ")") end elseif key_type == "zset" then local count = redis.call("ZCARD", key) if count > limit_count then table.insert(big_keys, key .. " (count: " .. count .. ")") end end end until cursor == "0" return big_keys
- 执行命令
bash
代码解读
复制代码
./redis-cli -h 192.168.247.201 -p 6379 --eval scan_big_keys.lua > big_keys.txt
这条命令主要用于连接到指定 IP 地址和端口的 Redis 服务器,执行一个名为 scan_big_keys.lua 的 Lua 脚本,并将脚本执行的结果重定向输出到一个名为 big_keys.txt 的文本文件中。
- 执行结果:
解决
压缩数据
假设value中存储的数据为字典对象:
ruby
代码解读
复制代码
> get KYPCACHE:SYS:DICT:default:PIC_JGWJDFMS:1 "["com.hundsun.kyp.platmng.api.sys.params.dto.CacheDict",{"id":"PIC_JGWJDFMS_1","key":"PIC_JGWJDFMS","val":"1","prompt":"\xe6\x9d\x83\xe9\x87\x8d\xe5\x88\xb6"}]"
字典对象中存在15个字段,但是往redis中只存储关键的4个字段(id, key, val, prompt),其他无关的字段不存储,如(createTime, creator等)
将无关的属性去除后,可大幅减少Redis中value的大小。
拆分大的字符串键
- 如果存在单个很大的字符串类型键,比如存储了超长的文本内容等,可以考虑将其拆分成多个较小的键。例如原本有一个键存储了一篇很长的文章内容,可按照段落或者一定字数范围拆分成多个键,每个键对应文章的一部分,使用时再进行组合读取,这样可以降低单个键的内存占用,操作起来也更灵活。
拆分大的集合类型键(如列表、集合、哈希等)
- 列表(List) :假如有一个很长的列表键,里面存储了大量元素,可以根据业务逻辑将其拆分成多个较短的列表键。比如一个记录用户操作历史的列表,按时间范围(如每天一个列表)进行拆分,后续查询时按相应时间范围去获取对应的列表即可。
- 集合(Set) :若集合中元素过多,可以按照一定规则划分。比如存储用户标签的集合,如果用户量极大且标签众多,可按照用户地域、用户类型等分类,把原本一个大集合拆成多个小集合,方便管理和操作,也减小了单个键的规模。
- 哈希(Hash) :对于大的哈希键,可以将其内部的字段(field)进一步分组拆分到不同的哈希键中。例如一个存储商品详细信息的大哈希键,可按照商品的类别(如电子产品、日用品等)分别构建不同的哈希键来存放相应类别的商品信息。
监控与定期清理
-
建立监控机制:
- 可以使用 Redis 的
INFO命令定期获取 Redis 的各种状态信息(如内存使用情况、键数量等),结合一些监控工具(如 Prometheus + Grafana 等组合)对 Redis 进行持续监控,及时发现大键相关的内存异常增长等情况,便于及时采取措施。
- 可以使用 Redis 的
-
定期清理策略:
- 制定定期清理大键的流程,比如每周或每月执行一次脚本去扫描并清理那些符合一定条件(如内存占用超过一定阈值、过期时间已到等)的大键,保持 Redis 内存的健康状态,防止大键持续累积对性能造成严重影响。
通过以上这些方法的综合运用,可以较好地应对 Redis 中存在大键所带来的一系列问题,保障 Redis 服务的高效稳定运行