优化 BigKey 的查询与删除:Redis 大 Key 治理实战

优化 BigKey 的查询与删除:Redis 大 Key 治理实战

什么是 BigKey

BigKey(大键)是指单个 key 对应的值占用大量内存或包含大量元素。BigKey 是 Redis 性能问题的常见元凶。

BigKey 的判断标准

类型 BigKey 判断标准 说明
String value > 10KB 大字符串
Hash field > 10000 哈希字段过多
List 元素 > 10000 列表过长
Set 成员 > 10000 集合成员过多
Sorted Set 成员 > 10000 有序集合元素过多
Stream 条目 > 10000 消息流过长

注意:这个阈值不是绝对的,需要根据业务场景判断。对于某些应用,100 万的列表元素也是”大 key”。

BigKey 的危害

1. 阻塞 Redis

# 获取 100 万成员的 Set
SMEMBERS huge_set    # O(N) 操作,可能阻塞数秒
HGETALL huge_hash    # 返回 100 万个字段,占用大量内存

2. 网络拥塞

# 一次性获取 10MB 的大字符串
data = r.get('huge_string')  # 10MB 数据传输,占用带宽

3. 删除阻塞

# 删除大集合
r.delete('huge_set')  # O(N) 删除,阻塞直到释放完所有内存

4. 主从延迟

BigKey 在主从复制中传输耗时较长,可能导致从库同步延迟。

如何发现 BigKey

使用 redis-cli 的 –bigkeys 扫描

redis-cli --bigkeys

# 输出示例
-------- summary -------
Sampled 100000 keys in the keyspace!
Total key length in bytes is 2450675 (avg len 24.51)

Biggest string found 'bighash:5' has 52425 bytes
Biggest   list found 'biglist' has 150000 items
Biggest    set found 'bigset' has 20000 members
Biggest   hash found 'bighash:1' has 32000 fields
Biggest   zset found 'bigzset' has 25000 members

13 strings with 100000 bytes (00.01% of keys, avg size 7692.23)
1 lists with 150000 items (00.00% of keys, avg size 150000.00)

使用 MEMORY USAGE 命令

# 查看单个 key 的内存占用
MEMORY USAGE user:1001
# (integer) 2560  -- 占用 2560 字节

MEMORY USAGE huge_set
# (integer) 12582912  -- 占用 12MB

使用 SCAN + MEMORY USAGE 扫描

def find_big_keys(redis, threshold_bytes=1024*1024):
    """找出超过阈值的大 key"""
    big_keys = []
    cursor = 0
    while True:
        cursor, keys = redis.scan(cursor, count=1000)
        for key in keys:
            try:
                size = redis.memory_usage(key)
                if size and size > threshold_bytes:
                    big_keys.append((key, size))
            except:
                pass
        if cursor == 0:
            break
    return sorted(big_keys, key=lambda x: x[1], reverse=True)

使用 Redis 慢查询日志

SLOWLOG GET 10
# 如果看到 SMEMBERS、HGETALL、LRANGE 等命令且耗时较长,大概率是 BigKey

删除 BigKey 的正确姿势

# 异步删除,Redis 在后台回收内存
UNLINK huge_set huge_hash  # 立即返回,后台删除

UNLINK 与 DEL 的区别:

# DEL:同步删除,阻塞 Redis
r.delete('big_list')  # 可能阻塞几秒

# UNLINK:异步删除,快速返回
r.unlink('big_list')  # 立即返回,后台线程回收内存

方案二:分批删除

对于大 List、Hash、Set、Sorted Set:

# 分批删除大 List
def trim_big_list(redis, key, batch_size=100):
    while redis.llen(key) > 0:
        redis.ltrim(key, batch_size, -1)  # 保留最后 batch_size 个
        time.sleep(0.01)  # 给其他操作留出时间

# 分批删除大 Hash
def delete_big_hash(redis, key, batch_size=100):
    while True:
        fields = redis.hkeys(key, batch_size)
        if not fields:
            break
        redis.hdel(key, *fields)

# 分批删除大 Set
def delete_big_set(redis, key, batch_size=1000):
    while redis.scard(key) > 0:
        members = redis.spop(key, batch_size)  # 弹出 batch_size 个成员

方案三:过期 + 异步删除

# 设置过期时间,到期后异步删除
EXPIRE huge_key 3600  # 1 小时后过期

预防 BigKey

1. 数据分片

# 将大 Hash 拆分成多个小 Hash
# ❌ 一个 Hash 存全部
r.hset('user:1:friends', mapping={f"u{uid}": uid for uid in range(100000)})

# ✅ 分片存储
SHARD_SIZE = 1000
def get_friends_key(uid, page):
    return f"user:{uid}:friends:{page // SHARD_SIZE}"

for i in range(0, 100000, SHARD_SIZE):
    shard = {f"u{j}": j for j in range(i, min(i+SHARD_SIZE, 100000))}
    r.hset(f"user:1:friends:{i//SHARD_SIZE}", mapping=shard)

2. 限制数据结构大小

# 用 LTRIM 限制 List 长度
redis.lpush('recent_visitors', user_id)
redis.ltrim('recent_visitors', 0, 100)  # 只保留最近 100 条

3. 定时扫描告警

def check_big_keys(redis, alert_threshold=10*1024*1024):
    """定时扫描 BigKey 并告警"""
    cursor = 0
    while True:
        cursor, keys = redis.scan(cursor, count=1000)
        for key in keys:
            size = redis.memory_usage(key) or 0
            if size > alert_threshold:
                alert(f"发现 BigKey: {key} 占用 {size/1024/1024:.2f}MB")
        if cursor == 0:
            break

4. 优化 BigKey 查询

# ✅ 分批查询替代全量查询
def get_hash_fields(redis, key, field_pattern="*", batch_size=100):
    cursor = 0
    result = {}
    while True:
        cursor, fields = redis.hscan(key, cursor, match=field_pattern, count=batch_size)
        result.update(fields)
        if cursor == 0:
            break
    return result

面试要点

  • BigKey 的三大危害:阻塞 Redis、网络拥塞、删除延迟
  • 发现 BigKey 的工具:redis-cli –bigkeys、MEMORY USAGE、SCAN
  • 删除 BigKey 首选 UNLINK(异步删除)
  • 预防比治理更重要:数据分片、结构限制、定时扫描
  • Lazy Freeing(惰性释放)是处理大 key 删除的重要机制
© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容