概述 在 Redis 的实际使用中,BigKey 和 HotKey 这两个问题,说它是“老生常谈”一点不为过,但偏偏又是最容易让人栽跟头的地方。一个搞不好,Redis 实例的响应速度瞬间拉胯,内存使用变得极其不均匀,严重时甚至直接让服务挂掉——这些都不是危言耸听。 BigKey,说白了就是单个键值对
在 Redis 的实际使用中,BigKey 和 HotKey 这两个问题,说它是“老生常谈”一点不为过,但偏偏又是最容易让人栽跟头的地方。一个搞不好,Redis 实例的响应速度瞬间拉胯,内存使用变得极其不均匀,严重时甚至直接让服务挂掉——这些都不是危言耸听。

长期稳定更新的攒劲资源: >>>点此立即查看<<<
BigKey,说白了就是单个键值对太“胖”了,占用的内存过大。HotKey,则是某个键被访问的频率高得离谱,远远甩开其他所有键。
而且有意思的是,这俩问题常常“抱团”出现,你中有我、我中有你,互相影响。所以,必须得系统性地去预防和处理,不能头痛医头、脚痛医脚。
BigKey 的定义很直接:某个键值对占用的内存超出了合理范围。Redis 官方给出了一个比较保守的参考建议:
一旦突破这个阈值,基本就可以判定它是 BigKey 了。当然,具体阈值还得结合业务场景来调整,但这确实是个很好的起点。
BigKey 吃掉大量内存,最直接的影响就是导致整个实例的内存分布严重失衡。特别是在 Redis Cluster 里,某个节点可能因为存了几个 BigKey,内存使用率就遥遥领先;其他节点却很闲。这就是典型的内存倾斜。
Redis 是单线程模型,这个限制很多人都知道。但 BigKey 的操作就是专门来挑战这个限制的。来看几个典型操作的影响:
| 操作 | 影响说明 |
|---|---|
| DEL | 删除大键会阻塞主线程,时间复杂度 O(N) |
| HGETALL | 获取所有字段会阻塞主线程 |
| LRANGE | 大范围获取列表元素会阻塞 |
| KEYS | 遍历所有键会严重阻塞 |
| FLUSHDB/FLUSHALL | 清空数据库会长时间阻塞 |
一旦主线程被阻塞,其他所有请求都得排队等着,服务响应自然就慢了。
BigKey 的读写操作,动不动就要传输几兆甚至几十兆的数据。这一下就把网络带宽给“霸占”了,其他小请求只能干瞪眼。
BigKey 在主从之间同步,速度肯定比普通键慢得多。这直接导致主从复制延迟增加,数据一致性就无法保证。
这个命令是 Redis 自带的,扫一下就能统计出大键分布。用法很简单:
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 (a vg len 3.73)Biggest string found 'user:1001:profile' has 10240 bytesBiggest list found 'order:queue' has 10003 itemsBiggest set found 'online:users' has 8005 itemsBiggest hash found 'product:info' has 5012 fields506 keys with 506 types
如果觉得 --bigkeys 不够灵活,可以自己写脚本用 SCAN 遍历所有键,逐个检查大小:
#!/bin/bashredis-cli --scan --pattern "*" | while read key; do size=$(redis-cli memory usage "$key") if [ $size -gt 10240 ]; then echo "BigKey: $key, Size: $size bytes" fidone
针对单个键,直接查看其内存占用:
redis-cli memory usage your_key
配置一个较低的慢查询阈值,把那些执行时间长的操作记录下来,BigKey 的操作常常会出现在这里面:
redis-cli config set slowlog-log-slower-than 10000 # 10msredis-cli slowlog get 10
Hash 拆分示例:
原始结构:
user:1001:info -> {name: "张三", age: 30, address: "...", ...} (5000+ fields)
拆分后,按业务维度分成多个小 Hash:
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]
拆分后,设置多个分片 List:
order:queue:0 -> [order1, ..., order1000]order:queue:1 -> [order1001, ..., order2000]...order:queue:9 -> [order9001, ..., order10000]
选对数据结构,能避免很多 BigKey 的产生:
| 场景 | 推荐数据结构 | 避免使用 |
|---|---|---|
| 简单键值对 | String | Hash (少量字段时) |
| 对象属性 | Hash | String (JSON) |
| 去重集合 | Set | List |
| 排序集合 | ZSet | Set + 排序 |
| 计数器 | String (INCR) | Hash |
别再直接用 DEL 了,换成 UNLINK:
redis-cli unlink your_big_key
UNLINK 会把删除操作交给后台线程处理,主线程不会等它完成,自然就不会被阻塞。
对大集合的操作,避免一次性全量读取:
# 原始方式(会阻塞)redis.hgetall("big_hash")# 改进方式(分批次)cursor = 0while True: cursor, data = redis.hscan("big_hash", cursor, count=100) process(data) if cursor == 0: break
对于有明确有效期的数据,务必设置 TTL:
redis-cli expire your_key 3600
这样数据就不会无限累积,BigKey 也就不会“养”出来了。
HotKey 的典型特征是“访问量爆表”:
在 Redis Cluster 中,HotKey 所在节点的 CPU 会直接拉满,而其他节点却很清闲。数据模型大概是这样的:
节点 A: CPU 95% (包含 HotKey)
节点 B: CPU 20%
节点 C: CPU 15%
资源浪费和性能瓶颈同时出现,这谁受得了?
高频访问自然意味着高频网络传输,带宽很快被 HotKey 占满,其他请求的正常通信都会受到影响。
当 HotKey 过期的一瞬间,成千上万的请求同时穿透到后端数据库,数据库直接就被打挂了——这就是经典的缓存击穿。
因为 Redis 是单线程,HotKey 的读写要排队处理。一个 HotKey 就能让其他所有请求排起长长的队伍,整体响应时间直线上升。
HotKey 的频繁更新,会让主从同步的负担变得很重,同步延迟也随之增加。
redis-cli info stats
重点关注 keyspace_hits 和 keyspace_misses 这两个指标,它们能反映整体访问热度。
redis-cli monitor | grep "your_key"
注意:MONITOR 对性能影响非常大,只适合用来做短时间调试,绝对不要在生产环境长时间运行。
redis-cli config set slowlog-log-slower-than 0redis-cli slowlog get 100
把慢查询阈值设为 0,可以记录所有命令,虽然开销大,但诊断时很有效。
在应用层做统计,最简单也最灵活:
from collections import defaultdictaccess_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}")
启用 LFU(Least Frequently Used)策略后,可以用 OBJECT FREQ 查看具体键的访问频率:
redis-cli config set maxmemory-policy allkeys-lfuredis-cli object freq your_key
在应用层使用本地缓存(如 Guava Cache、Caffeine),把 HotKey 的数据缓存到 JVM 内存里,Redis 的访问压力就能大大减轻:
// 使用 Caffeine 本地缓存CachelocalCache = 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;}
对于读多写少的 HotKey,可以把读请求分散到从节点:
应用 -> 读请求 -> Redis 从节点
应用 -> 写请求 -> Redis 主节点
把单个 HotKey 拆成多个副本 Key,访问时随机选一个:
原始方式:
hot_product:1001 -> 商品信息
分片方式:
hot_product:1001:0 -> 商品信息hot_product:1001:1 -> 商品信息(副本)hot_product:1001:2 -> 商品信息(副本)
访问时随机选择一个分片:
import randomdef get_hot_product(product_id): shard = random.randint(0, 2) return redis.get(f"hot_product:{product_id}:{shard}")
写操作同步更新多个备份,读操作随机选一个:
# 写入时同步更新所有备份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}")
Redis Cluster 能把数据分散到不同节点,但要注意别踩 Hash Tag 的坑。下面的写法会把所有相关键集中到同一个节点:
user:{1001}:profileuser:{1001}:ordersuser:{1001}:cart
这就等于主动制造了 HotKey 节点。
对 HotKey 的访问做限流,避免流量过载:
from functools import wrapsimport timeclass 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 Truelimiter = 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_limitdef get_hot_key(key): return redis.get(key)
在系统启动或低峰期,提前把 HotKey 数据加载到 Redis 中:
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)
构建“应用 -> 本地缓存 -> Redis -> 数据库”的多级架构,每层都是防火墙。
对于写请求较多的 HotKey,让流量先进消息队列,再慢慢消费写入 Redis:
应用 -> 消息队列 -> 消费者 -> Redis
| 工具 | 用途 |
|---|---|
| redis-cli --bigkeys | 检测 BigKey |
| redis-cli --memkeys | 检测占用内存最多的键 |
| redis-cli --hotkeys | 检测 HotKey(LFU 模式下) |
| Redis Insight | 可视化管理工具 |
| 工具 | 特点 |
|---|---|
| redis-rdb-tools | 分析 RDB 文件,找出 BigKey |
| redis-faina | 分析 MONITOR 输出,统计访问频率 |
| Redis Exporter | Prometheus 指标导出器 |
| Redis Commander | Web 管理界面 |
| Medis | Mac 平台的 Redis 客户端 |
BigKey 和 HotKey 是 Redis 使用中绕不过去的坎,但完全可以通过合理的设计、有效的监控和及时的优化来化解。
核心要点:
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述