Redis之BigKey与HotKey问题详解

来源:这里教程网 时间:2026-02-28 10:14:17 作者:
概述BigKey 问题什么是 BigKeyBigKey 的危害1. 内存占用不均2. 阻塞主线程3. 网络带宽消耗4. 持久化问题5. 主从同步延迟BigKey 的检测1. 使用 redis-cli --bigkeys2. 使用 SCAN 命令3. 使用 MEMORY USAGE 命令4. 使用 Redis 慢查询日志5. 使用 Redis 模块BigKey 的解决方案1. 拆分 BigKey2. 使用合适的数据结构3. 压缩数据4. 异步删除 BigKey5. 分批次操作6. 设置过期时间HotKey 问题什么是 HotKeyHotKey 的危害1. CPU 负载不均2. 网络带宽瓶颈3. 缓存击穿4. 请求堆积5. 主从同步压力HotKey 的检测1. 使用 Redis INFO 命令2. 使用 MONITOR 命令(谨慎使用)3. 使用 Redis 慢查询日志4. 使用客户端统计5. 使用 Redis 4.0+ 的 LFU 淘汰策略6. 使用第三方工具HotKey 的解决方案1. 本地缓存2. 读写分离3. Key 分片4. 备份 Key5. 使用 Redis Cluster6. 限流保护7. 缓存预热8. 使用多级缓存9. 消息队列削峰最佳实践1. 设计阶段2. 开发阶段3. 运维阶段4. 应急处理工具推荐1. Redis 官方工具2. 第三方工具3. 云服务总结

概述

在 Redis 的实际使用过程中,BigKey(大键)和 HotKey(热键)是两个常见且可能导致严重性能问题的问题。它们可能导致 Redis 实例响应变慢、内存使用不均、甚至服务不可用。

BigKey 指的是单个键值对占用内存过大的情况。

HotKey 指的是某个键的访问频率远高于其他键的情况。

这两个问题往往同时出现,相互影响,需要系统性地进行预防和处理。

BigKey 问题

什么是 BigKey

BigKey 是指单个键值对占用内存过大的键。Redis 官方建议:

String 类型:单个 value 不超过 10KBHash、List、Set、ZSet 类型:元素个数不超过 5000

超过这些阈值的键可以被视为 BigKey。

BigKey 的危害

1. 内存占用不均

BigKey 会占用大量内存,可能导致 Redis 实例内存使用不均衡。在 Redis Cluster 中,这会导致某些节点的内存使用率远高于其他节点,引发内存倾斜。

2. 阻塞主线程

Redis 是单线程模型,BigKey 的操作会阻塞主线程:

操作影响说明DEL删除大键会阻塞主线程,时间复杂度 O(N)HGETALL获取所有字段会阻塞主线程LRANGE大范围获取列表元素会阻塞KEYS遍历所有键会严重阻塞FLUSHDB/FLUSHALL清空数据库会长时间阻塞

3. 网络带宽消耗

BigKey 的读写操作会消耗大量网络带宽,影响其他请求的响应速度。

4. 持久化问题

RDB:生成 RDB 文件时,BigKey 会导致 fork 子进程时内存占用翻倍AOF:BigKey 的写入会导致 AOF 文件膨胀,重写时耗时较长

5. 主从同步延迟

BigKey 的同步会导致主从复制延迟增加,影响数据一致性。

BigKey 的检测

1. 使用 redis-cli --bigkeys

Redis 自带的 --bigkeys 命令可以扫描并统计大键:

redis-cli --bigkeys -i 0.1

参数说明:

-i 0.1:每次扫描间隔 0.1 秒,避免阻塞

输出示例:

-------- summary ------- Sampled 506 keys in the keyspace! Total key length in bytes is 1885 (avg len 3.73) Biggest string found 'user:1001:profile' has 10240 bytes Biggest list found 'order:queue' has 10003 items Biggest set found 'online:users' has 8005 items Biggest hash found 'product:info' has 5012 fields 506 keys with 506 types

2. 使用 SCAN 命令

编写脚本使用 SCAN 命令遍历所有键并检查大小:

#!/bin/bash redis-cli --scan --pattern "*" | while read key; do size=$(redis-cli memory usage "$key") if [ $size -gt 10240 ]; then echo "BigKey: $key, Size: $size bytes" fi done

3. 使用 MEMORY USAGE 命令

redis-cli memory usage your_key

4. 使用 Redis 慢查询日志

配置慢查询阈值:

redis-cli config set slowlog-log-slower-than 10000 # 10ms redis-cli slowlog get 10

5. 使用 Redis 模块

Redis Modules:如 RedisJSON、RedisTimeSeries 等模块提供更详细的内存分析Redis Insight:官方可视化工具,可以查看内存分布

BigKey 的解决方案

1. 拆分 BigKey

Hash 拆分示例

原始结构:

user:1001:info -> {name: "张三", age: 30, address: "...", ...} (5000+ fields)

拆分后:

user:1001:info:base -> {name: "张三", age: 30} user:1001:info:contact -> {phone: "...", email: "..."} user:1001:info:address -> {province: "...", city: "..."}

List 拆分示例

原始结构:

order:queue -> [order1, order2, ..., order10000]

拆分后:

order:queue:0 -> [order1, ..., order1000] order:queue:1 -> [order1001, ..., order2000] ... order:queue:9 -> [order9001, ..., order10000]

2. 使用合适的数据结构

场景推荐数据结构避免使用简单键值对StringHash (少量字段时)对象属性HashString (JSON)去重集合SetList排序集合ZSetSet + 排序计数器String (INCR)Hash

3. 压缩数据

使用更紧凑的序列化格式(如 MessagePack、Protobuf)对 String 类型的值进行压缩使用 Hash 的 ziplist 编码(元素较少时)

4. 异步删除 BigKey

使用 UNLINK 命令替代 DEL

redis-cli unlink your_big_key

UNLINK 会在后台线程中删除键,不会阻塞主线程。

5. 分批次操作

对于大集合的操作,分批次进行:

# 原始方式(会阻塞) redis.hgetall("big_hash") # 改进方式(分批次) cursor = 0 while True: cursor, data = redis.hscan("big_hash", cursor, count=100) process(data) if cursor == 0: break

6. 设置过期时间

为 BigKey 设置合理的过期时间,避免数据无限累积:

redis-cli expire your_key 3600

HotKey 问题

什么是 HotKey

HotKey 是指某个键的访问频率远高于其他键的键。通常表现为:

某个键的 QPS 远超其他键某个键的读写请求集中在短时间内爆发某个键的访问量占整个 Redis 实例的很大比例

HotKey 的危害

1. CPU 负载不均

在 Redis Cluster 中,HotKey 所在节点的 CPU 负载会远高于其他节点:

节点 A: CPU 95% (包含 HotKey)
节点 B: CPU 20%
节点 C: CPU 15%

2. 网络带宽瓶颈

HotKey 的高频访问会消耗大量网络带宽,影响其他请求。

3. 缓存击穿

当 HotKey 过期时,大量请求会同时穿透到后端数据库,导致数据库压力骤增。

4. 请求堆积

由于 Redis 是单线程,HotKey 的处理会导致其他请求排队等待。

5. 主从同步压力

HotKey 的频繁更新会增加主从同步的负担。

HotKey 的检测

1. 使用 Redis INFO 命令

redis-cli info stats

关注 keyspace_hitskeyspace_misses 指标。

2. 使用 MONITOR 命令(谨慎使用)

redis-cli monitor | grep "your_key"

注意MONITOR 会严重影响性能,仅用于调试,不要在生产环境使用。

3. 使用 Redis 慢查询日志

redis-cli config set slowlog-log-slower-than 0 redis-cli slowlog get 100

4. 使用客户端统计

在应用层记录每个键的访问频率:

from collections import defaultdict access_stats = defaultdict(int) def get_redis(key): access_stats[key] += 1 return redis.get(key) # 定期打印统计 def print_stats(): for key, count in sorted(access_stats.items(), key=lambda x: x[1], reverse=True)[:10]: print(f"{key}: {count}")

5. 使用 Redis 4.0+ 的 LFU 淘汰策略

配置 LFU(Least Frequently Used)淘汰策略:

redis-cli config set maxmemory-policy allkeys-lfu

然后使用 OBJECT FREQ 命令查看访问频率:

redis-cli object freq your_key

6. 使用第三方工具

Redis Exporter + Prometheus + Grafana:监控 Redis 指标阿里云 Redis:提供 HotKey 分析功能腾讯云 Redis:提供热 Key 监控

HotKey 的解决方案

1. 本地缓存

在应用层使用本地缓存(如 Guava Cache、Caffeine)缓存 HotKey:

// 使用 Caffeine 本地缓存 Cache<String, String> localCache = Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(1, TimeUnit.MINUTES) .build(); public String get(String key) { // 先查本地缓存 String value = localCache.getIfPresent(key); if (value != null) { return value; } // 再查 Redis value = redis.get(key); if (value != null) { localCache.put(key, value); } return value; }

2. 读写分离

对于读多写少的 HotKey,使用读写分离:

应用 -> 读请求 -> Redis 从节点
应用 -> 写请求 -> Redis 主节点

3. Key 分片

将 HotKey 拆分成多个 Key:

原始方式

hot_product:1001 -> 商品信息

分片方式

hot_product:1001:0 -> 商品信息 hot_product:1001:1 -> 商品信息(副本) hot_product:1001:2 -> 商品信息(副本)

访问时随机选择一个分片:

import random def get_hot_product(product_id): shard = random.randint(0, 2) return redis.get(f"hot_product:{product_id}:{shard}")

4. 备份 Key

为 HotKey 创建多个备份,分散请求:

# 写入时同步更新所有备份 def set_hot_key(key, value): pipe = redis.pipeline() for i in range(3): pipe.set(f"{key}:backup:{i}", value) pipe.execute() # 读取时随机选择一个备份 def get_hot_key(key): backup = random.randint(0, 2) return redis.get(f"{key}:backup:{backup}")

5. 使用 Redis Cluster

在 Redis Cluster 中,HotKey 会分散到不同的节点,但需要注意:

确保数据分片均匀避免使用 Hash Tag 导致数据集中

Hash Tag 示例(会导致数据集中):

user:{1001}:profile user:{1001}:orders user:{1001}:cart

这些键会被分配到同一个节点。

6. 限流保护

对 HotKey 的访问进行限流:

from functools import wraps import time class RateLimiter: def __init__(self, max_calls, period): self.max_calls = max_calls self.period = period self.calls = {} def allow(self, key): now = time.time() if key not in self.calls: self.calls[key] = [] # 清理过期记录 self.calls[key] = [t for t in self.calls[key] if now - t < self.period] if len(self.calls[key]) >= self.max_calls: return False self.calls[key].append(now) return True limiter = RateLimiter(max_calls=1000, period=1) # 每秒最多 1000 次 def rate_limit(func): @wraps(func) def wrapper(key, *args, **kwargs): if not limiter.allow(key): raise Exception("Rate limit exceeded") return func(key, *args, **kwargs) return wrapper @rate_limit def get_hot_key(key): return redis.get(key)

7. 缓存预热

在系统启动或低峰期,提前加载 HotKey 到缓存:

def warm_up_cache(): hot_keys = get_hot_keys_from_db() # 从数据库获取热点键列表 for key in hot_keys: value = db.get(key) redis.set(key, value, ex=3600)

8. 使用多级缓存

构建多级缓存架构:

应用 -> 本地缓存 -> Redis -> 数据库

9. 消息队列削峰

对于写请求较多的 HotKey,使用消息队列削峰:

应用 -> 消息队列 -> 消费者 -> Redis

最佳实践

1. 设计阶段

合理设计 Key 的命名和结构:避免产生 BigKey预估数据量:提前规划数据规模,选择合适的数据结构设置过期时间:为所有 Key 设置合理的过期时间

2. 开发阶段

使用 Pipeline:批量操作减少网络开销避免使用 KEYS:使用 SCAN 替代 KEYS监控 Key 大小:定期检查是否有 BigKey 产生

3. 运维阶段

定期巡检:使用工具定期检查 BigKey 和 HotKey设置告警:对内存使用、慢查询等指标设置告警容量规划:根据业务增长提前规划容量

4. 应急处理

紧急扩容:当发现问题时,及时扩容限流降级:对异常请求进行限流和降级数据迁移:将 BigKey 迁移到独立实例

工具推荐

1. Redis 官方工具

工具用途redis-cli --bigkeys检测 BigKeyredis-cli --memkeys检测占用内存最多的键redis-cli --hotkeys检测 HotKey(LFU 模式下)Redis Insight可视化管理工具

2. 第三方工具

工具特点redis-rdb-tools分析 RDB 文件,找出 BigKeyredis-faina分析 MONITOR 输出,统计访问频率Redis ExporterPrometheus 指标导出器Redis CommanderWeb 管理界面MedisMac 平台的 Redis 客户端

3. 云服务

阿里云 Redis:提供 BigKey 和 HotKey 分析腾讯云 Redis:提供热 Key 监控AWS ElastiCache:提供 CloudWatch 监控

总结

BigKey 和 HotKey 是 Redis 使用中的常见问题,但通过合理的设计、有效的监控和及时的优化,可以很好地避免和解决这些问题。

核心要点

    预防为主:在设计阶段就考虑避免 BigKey 和 HotKey定期巡检:使用工具定期检查,及时发现问题合理拆分:对 BigKey 进行拆分,对 HotKey 进行分散多级缓存:构建多级缓存架构,减轻 Redis 压力监控告警:建立完善的监控和告警机制

到此这篇关于Redis之BigKey与HotKey问题详解的文章就介绍到这了,

相关推荐