大 key 会拖慢删除、迁移、复制,得先找到它们。找的时候不能反过来把线上服务拖慢。这篇手写一个大 key 扫描器(SCAN + MEMORY USAGE),在一个埋了 5 个大 key 的库里量它:能不能找全、要多久、对其它请求有多大影响。
key 类型 元素数 MEMORY USAGE 字节 SAMPLES 默认(5)耗时 µs SAMPLES 0 耗时 µs SAMPLES 5 结果 big:string string 5242880 5259293 95 94 5259293 big:list list 200000 1700350 109 103 1700350 big:hash hash 100000 5450727 103 104 5450727 big:set set 150000 6784700 105 97 6784700 big:zset zset 50000 4254435 105 103 4254435
MEMORY USAGE key 返回 key 及其值占用的字节数。按字节排序和按元素个数排序结论不同:字节最多的是 15 万成员的 set(6.78 MB),元素最多的是 5 MB 字符串。所以只看元素数会漏掉「元素少但每个很大」的 key。每次调用约 0.1 ms;SAMPLES 0(理论上是精确遍历)在这些数据上耗时和默认值一样、结果完全相同——这与文档里对 SAMPLES 的说明不一致,我没有追究原因。
扫描 300006 个 key 用了 0.72 s(416k key/秒);期间另一个连接 PING 最大停顿 2.40 ms;超过 100 KB 的 key: [('big:set', 6784700), ('big:hash', 5450727), ('big:string', 5259293), ('big:zset', 4254435), ('big:list', 1700350)]SCAN 分批取 key(COUNT 1000),每批用管道发 MEMORY USAGE,把超过阈值(100 KB)的记下来。300006 个 key 扫描 0.72 s,PING 最大停顿只有 2.4 ms;5 个大 key 一个不漏。这是本机、小 key 为主的库;key 数更多、单批里有大 key(MEMORY USAGE 变慢)时要相应加大批与批之间的间隔。
big:list: Value at:0x76c2c2e940 refcount:1 encoding:quicklist serializedlength:776101 lru:12323709 lru_seconds_idle:1 ql_nodes:207 ql_avg_node:966.18 ql_listpack_max:-2 ql_compressed:0 ql_uncompressed_size:1690339 big:hash: Value at:0x76c2c2e840 refcount:1 encoding:hashtable serializedlength:1877785 lru:12323709 lru_seconds_idle:1
DEBUG OBJECT(需要 enable-debug-command)给出编码、引用计数、serializedlength(RDB 序列化后的长度)等。serializedlength 与 MEMORY USAGE 口径不同:big:list 是 776101 对 1700350。