大量 Key 与大 Key 问题
概述
Redis 在生产环境中经常遇到两类关键性能问题:大量 Key 的管理和大 Key 的处理。这些问题如果处理不当,会严重影响 Redis 的性能和稳定性。本文将详细介绍这些问题的成因、影响以及完整的解决方案。
1. 危险操作的禁用指令
1.1 高风险命令列表
在生产环境中,以下命令可能导致 Redis 阻塞或性能问题:
# 禁用的危险命令
FLUSHDB # 清空当前数据库
FLUSHALL # 清空所有数据库
KEYS # 遍历所有key
SHUTDOWN # 关闭服务器
DEBUG # 调试命令
1.2 配置禁用方法
1.2.1 redis.conf 配置
# 重命名危险命令
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command KEYS ""
rename-command SHUTDOWN ""
rename-command DEBUG ""
1.2.2 ACL 权限控制
# 创建受限用户
ACL SETUSER app_user on >password123 ~* -@dangerous +@read +@write -flushdb -flushall -keys -config -shutdown -debug
# 查看用户权限
ACL LIST
# 查看当前用户权限
ACL WHOAMI
1.3 安全替代方案
# 替代 KEYS 命令
# 使用 SCAN 代替
SCAN 0 MATCH pattern* COUNT 100
# 替代 FLUSHDB
# 使用 SCAN + DEL 批量删除
redis-cli --scan --pattern "prefix:*" | xargs redis-cli del
# 安全的配置查看
CONFIG GET parameter
2. 大量 Key 的游标扫描
2.1 SCAN 命令详解
2.1.1 基本语法
# 基本扫描
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
# 示例
SCAN 0 MATCH user:* COUNT 1000
SCAN 0 TYPE string COUNT 500
2.1.2 SCAN 工作原理
graph TD
A[开始扫描 cursor=0] --> B[返回部分keys + 新cursor]
B --> C{cursor == 0?}
C -->|否| D[使用新cursor继续扫描]
C -->|是| E[扫描完成]
D --> B
style A fill:#e1f5fe
style E fill:#c8e6c9
2.1.3 SCAN 特性
# 1. 渐进式遍历,不阻塞服务器
# 2. 可能返回重复key(需要客户端去重)
# 3. 可能遗漏在扫描过程中删除的key
# 4. 新增的key可能被扫描到,也可能不被扫描到
2.2 SCAN 在扩容情况下的问题
2.2.1 哈希表扩容影响
sequenceDiagram
participant C as Client
participant R as Redis
participant H1 as Hash Table 1
participant H2 as Hash Table 2
C->>R: SCAN 0
R->>H1: 扫描旧表
R->>C: 返回keys + cursor
Note over R: 触发扩容
R->>H2: 创建新表
R->>R: 渐进式rehash
C->>R: SCAN cursor
R->>H1: 扫描旧表剩余
R->>H2: 扫描新表
R->>C: 可能重复的keys
2.2.2 扩容期间的问题
# 问题1:重复key
# 原因:rehash过程中,key可能同时存在于新旧表
# 解决:客户端去重
# 问题2:遗漏key
# 原因:扫描过程中key被移动
# 解决:多轮扫描确保完整性
# 问题3:性能波动
# 原因:rehash增加CPU开销
# 解决:避免在高峰期进行大量扫描
2.2.3 最佳实践
# Python 示例:安全的SCAN实现
import redis
import time
def safe_scan_keys(redis_client, pattern="*", count=1000):
"""
安全的key扫描,处理重复和遗漏
"""
keys = set() # 使用set去重
cursor = 0
while True:
try:
cursor, batch_keys = redis_client.scan(
cursor=cursor,
match=pattern,
count=count
)
keys.update(batch_keys)
# 避免过快扫描
time.sleep(0.001)
if cursor == 0:
break
except redis.RedisError as e:
print(f"扫描错误: {e}")
break
return list(keys)
# 使用示例
r = redis.Redis(host='localhost', port=6379, db=0)
user_keys = safe_scan_keys(r, "user:*", 500)
print(f"找到 {len(user_keys)} 个用户key")
2.3 不同数据结构的扫描
# Hash 扫描
HSCAN key cursor [MATCH pattern] [COUNT count]
# Set 扫描
SSCAN key cursor [MATCH pattern] [COUNT count]
# Sorted Set 扫描
ZSCAN key cursor [MATCH pattern] [COUNT count]
# 示例
HSCAN user:1001 0 MATCH field* COUNT 100
SSCAN tags 0 MATCH tag* COUNT 50
ZSCAN leaderboard 0 MATCH player* COUNT 200
3. 大 Key 问题分析
3.1 大 Key 的危害
3.1.1 性能影响
graph TD
A[大Key操作] --> B[长时间阻塞]
B --> C[其他命令排队]
C --> D[整体性能下降]
A --> E[内存碎片]
E --> F[内存使用效率低]
A --> G[网络传输慢]
G --> H[客户端超时]
style A fill:#ffcdd2
style D fill:#ffcdd2
style F fill:#ffcdd2
style H fill:#ffcdd2
3.1.2 具体问题
# 1. 阻塞问题
# - 大key的读写操作阻塞Redis主线程
# - 影响其他客户端的请求响应
# 2. 内存问题
# - 占用大量内存空间
# - 可能导致内存不足
# - 增加内存碎片
# 3. 网络问题
# - 大key传输占用大量网络带宽
# - 可能导致网络超时
# 4. 持久化问题
# - AOF重写时处理大key耗时长
# - RDB生成时内存使用翻倍
3.2 各数据类型的大小限制建议
3.2.1 String 类型
# 建议限制
# 单个String: < 10MB
# 理想大小: < 1MB
# 最大理论: 512MB
# 配置检查
CONFIG GET proto-max-bulk-len # 默认512MB
# 监控示例
STRLEN large_string_key
3.2.2 List 类型
# 建议限制
# 元素数量: < 10,000
# 总大小: < 10MB
# 单个元素: < 1MB
# 监控命令
LLEN list_key # 获取长度
MEMORY USAGE list_key # 获取内存使用
# 配置优化
list-max-ziplist-size -2 # 每个节点最大8KB
list-compress-depth 1 # 压缩深度
3.2.3 Hash 类型
# 建议限制
# 字段数量: < 100,000
# 总大小: < 100MB
# 单个字段值: < 1MB
# 监控命令
HLEN hash_key # 获取字段数量
MEMORY USAGE hash_key # 获取内存使用
# 配置优化
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
3.2.4 Set 类型
# 建议限制
# 成员数量: < 50,000
# 总大小: < 50MB
# 单个成员: < 1MB
# 监控命令
SCARD set_key # 获取成员数量
MEMORY USAGE set_key # 获取内存使用
# 配置优化
set-max-intset-entries 512
3.2.5 Sorted Set 类型
# 建议限制
# 成员数量: < 50,000
# 总大小: < 50MB
# 单个成员: < 1MB
# 监控命令
ZCARD zset_key # 获取成员数量
MEMORY USAGE zset_key # 获取内存使用
# 配置优化
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
3.3 大 Key 限制策略
3.3.1 应用层限制
# Python 示例:大key检查装饰器
import redis
import json
from functools import wraps
def check_key_size(max_size_mb=10):
def decorator(func):
@wraps(func)
def wrapper(self, key, value, *args, **kwargs):
# 估算value大小
if isinstance(value, str):
size = len(value.encode('utf-8'))
elif isinstance(value, (list, dict)):
size = len(json.dumps(value).encode('utf-8'))
else:
size = len(str(value).encode('utf-8'))
size_mb = size / (1024 * 1024)
if size_mb > max_size_mb:
raise ValueError(f"Key {key} size {size_mb:.2f}MB exceeds limit {max_size_mb}MB")
return func(self, key, value, *args, **kwargs)
return wrapper
return decorator
class SafeRedisClient:
def __init__(self, redis_client):
self.redis = redis_client
@check_key_size(max_size_mb=5)
def set(self, key, value):
return self.redis.set(key, value)
@check_key_size(max_size_mb=10)
def hset(self, key, field, value):
return self.redis.hset(key, field, value)
3.3.2 代理层限制
# 使用 Twemproxy 配置
# nutcracker.yml
alpha:
listen: 127.0.0.1:22121
hash: fnv1a_64
distribution: ketama
auto_eject_hosts: true
redis: true
server_retry_timeout: 2000
server_failure_limit: 1
# 限制请求大小
server_connections: 1
timeout: 400
servers:
- 127.0.0.1:6379:1
- 127.0.0.1:6380:1
4. 大 Key 发现方法
4.1 redis-cli --bigkeys
4.1.1 基本使用
# 扫描所有大key
redis-cli --bigkeys
# 指定数据库
redis-cli -n 1 --bigkeys
# 指定间隔(避免影响性能)
redis-cli --bigkeys -i 0.1
# 输出到文件
redis-cli --bigkeys > bigkeys_report.txt
4.1.2 输出示例
# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).
[00.00%] Biggest string found so far 'user:profile:1001' with 1048576 bytes
[25.50%] Biggest hash found so far 'session:data:5001' with 50000 fields
[50.25%] Biggest list found so far 'message:queue:urgent' with 25000 items
[75.75%] Biggest set found so far 'tags:popular' with 15000 members
[100.00%] Biggest zset found so far 'leaderboard:global' with 30000 members
-------- summary -------
Sampled 100000 keys in the keyspace!
Total key length in bytes is 2500000 (avg len 25.00)
Biggest string found 'user:profile:1001' has 1048576 bytes
Biggest hash found 'session:data:5001' has 50000 fields
Biggest list found 'message:queue:urgent' has 25000 items
Biggest set found 'tags:popular' has 15000 members
Biggest zset found 'leaderboard:global' has 30000 members
5000 strings with 125000000 bytes (12.50% of keys, avg size 25000.00)
2000 hashs with 40000 fields (20.00% of keys, avg size 20.00)
1500 lists with 37500 items (15.00% of keys, avg size 25.00)
1000 sets with 15000 members (10.00% of keys, avg size 15.00)
500 zsets with 12500 members (05.00% of keys, avg size 25.00)
4.2 MEMORY USAGE 命令
4.2.1 查看单个 Key 内存使用
# 查看key的内存使用(Redis 4.0+)
MEMORY USAGE key_name
# 查看key的内存使用(包含采样)
MEMORY USAGE key_name SAMPLES 5
# 示例
MEMORY USAGE user:profile:1001
# 输出: (integer) 1048576
4.2.2 批量检查脚本
# Python 脚本:批量检查大key
import redis
import time
def find_big_keys(redis_client, threshold_mb=10, pattern="*"):
"""
查找大于阈值的key
"""
big_keys = []
threshold_bytes = threshold_mb * 1024 * 1024
cursor = 0
while True:
cursor, keys = redis_client.scan(cursor=cursor, match=pattern, count=1000)
for key in keys:
try:
# 获取key的内存使用
memory_usage = redis_client.memory_usage(key)
if memory_usage and memory_usage > threshold_bytes:
key_type = redis_client.type(key).decode('utf-8')
# 获取key的大小信息
size_info = get_key_size_info(redis_client, key, key_type)
big_keys.append({
'key': key.decode('utf-8'),
'type': key_type,
'memory_mb': round(memory_usage / (1024 * 1024), 2),
'size_info': size_info
})
except Exception as e:
print(f"检查key {key} 时出错: {e}")
# 避免过快扫描
time.sleep(0.001)
if cursor == 0:
break
return big_keys
def get_key_size_info(redis_client, key, key_type):
"""
获取不同类型key的大小信息
"""
try:
if key_type == 'string':
return {'length': redis_client.strlen(key)}
elif key_type == 'hash':
return {'fields': redis_client.hlen(key)}
elif key_type == 'list':
return {'length': redis_client.llen(key)}
elif key_type == 'set':
return {'members': redis_client.scard(key)}
elif key_type == 'zset':
return {'members': redis_client.zcard(key)}
else:
return {}
except:
return {}
# 使用示例
r = redis.Redis(host='localhost', port=6379, db=0)
big_keys = find_big_keys(r, threshold_mb=5)
print(f"发现 {len(big_keys)} 个大key:")
for key_info in big_keys:
print(f"Key: {key_info['key']}, Type: {key_info['type']}, "
f"Memory: {key_info['memory_mb']}MB, Info: {key_info['size_info']}")
4.3 监控和告警
4.3.1 定期扫描脚本
#!/bin/bash
# bigkey_monitor.sh
REDIS_HOST="localhost"
REDIS_PORT="6379"
REDIS_DB="0"
THRESHOLD_MB="10"
LOG_FILE="/var/log/redis_bigkeys.log"
ALERT_EMAIL="admin@example.com"
# 执行大key扫描
echo "[$(date)] 开始扫描大key..." >> $LOG_FILE
# 使用redis-cli扫描
redis-cli -h $REDIS_HOST -p $REDIS_PORT -n $REDIS_DB --bigkeys > /tmp/bigkeys_$(date +%Y%m%d_%H%M%S).txt
# 检查是否有超过阈值的key
python3 /path/to/bigkey_checker.py --threshold $THRESHOLD_MB --log $LOG_FILE
if [ $? -eq 1 ]; then
echo "发现大key,发送告警邮件" >> $LOG_FILE
mail -s "Redis大key告警" $ALERT_EMAIL < $LOG_FILE
fi
echo "[$(date)] 大key扫描完成" >> $LOG_FILE
5. 大 Key 删除策略
5.1 渐进式删除
5.1.1 String 类型删除
# String类型直接删除
DEL large_string_key
# 或使用UNLINK异步删除
UNLINK large_string_key
5.1.2 Hash 类型渐进式删除
# Python 示例:Hash渐进式删除
def delete_large_hash(redis_client, key, batch_size=1000):
"""
渐进式删除大Hash
"""
cursor = 0
deleted_count = 0
while True:
# 扫描Hash字段
cursor, fields = redis_client.hscan(key, cursor=cursor, count=batch_size)
if fields:
# 批量删除字段
field_names = list(fields.keys())
redis_client.hdel(key, *field_names)
deleted_count += len(field_names)
print(f"已删除 {deleted_count} 个字段")
# 短暂休息,避免阻塞
time.sleep(0.01)
if cursor == 0:
break
# 删除key本身
redis_client.delete(key)
print(f"Hash {key} 删除完成,共删除 {deleted_count} 个字段")
# 使用示例
r = redis.Redis(host='localhost', port=6379, db=0)
delete_large_hash(r, "large_hash_key", batch_size=500)
5.1.3 List 类型渐进式删除
def delete_large_list(redis_client, key, batch_size=1000):
"""
渐进式删除大List
"""
total_length = redis_client.llen(key)
deleted_count = 0
while redis_client.llen(key) > 0:
# 从右侧批量弹出元素
pipe = redis_client.pipeline()
for _ in range(min(batch_size, redis_client.llen(key))):
pipe.rpop(key)
results = pipe.execute()
deleted_count += len([r for r in results if r is not None])
print(f"已删除 {deleted_count}/{total_length} 个元素")
# 短暂休息
time.sleep(0.01)
# 删除key本身
redis_client.delete(key)
print(f"List {key} 删除完成")
5.1.4 Set 类型渐进式删除
def delete_large_set(redis_client, key, batch_size=1000):
"""
渐进式删除大Set
"""
cursor = 0
deleted_count = 0
while True:
# 扫描Set成员
cursor, members = redis_client.sscan(key, cursor=cursor, count=batch_size)
if members:
# 批量删除成员
redis_client.srem(key, *members)
deleted_count += len(members)
print(f"已删除 {deleted_count} 个成员")
time.sleep(0.01)
if cursor == 0:
break
# 删除key本身
redis_client.delete(key)
print(f"Set {key} 删除完成")
5.1.5 Sorted Set 类型渐进式删除
def delete_large_zset(redis_client, key, batch_size=1000):
"""
渐进式删除大Sorted Set
"""
total_count = redis_client.zcard(key)
deleted_count = 0
while redis_client.zcard(key) > 0:
# 获取一批成员
members = redis_client.zrange(key, 0, batch_size - 1)
if members:
# 批量删除成员
redis_client.zrem(key, *members)
deleted_count += len(members)
print(f"已删除 {deleted_count}/{total_count} 个成员")
time.sleep(0.01)
# 删除key本身
redis_client.delete(key)
print(f"Sorted Set {key} 删除完成")
5.2 通用删除工具
# 通用大key删除工具
class BigKeyDeleter:
def __init__(self, redis_client, batch_size=1000, sleep_time=0.01):
self.redis = redis_client
self.batch_size = batch_size
self.sleep_time = sleep_time
def delete_key(self, key):
"""
根据key类型选择删除方法
"""
key_type = self.redis.type(key).decode('utf-8')
print(f"开始删除 {key_type} 类型的key: {key}")
if key_type == 'string':
self._delete_string(key)
elif key_type == 'hash':
self._delete_hash(key)
elif key_type == 'list':
self._delete_list(key)
elif key_type == 'set':
self._delete_set(key)
elif key_type == 'zset':
self._delete_zset(key)
else:
print(f"未知类型: {key_type},使用普通删除")
self.redis.delete(key)
def _delete_string(self, key):
# String类型直接删除
self.redis.unlink(key)
print(f"String {key} 删除完成")
def _delete_hash(self, key):
cursor = 0
deleted_count = 0
while True:
cursor, fields = self.redis.hscan(key, cursor=cursor, count=self.batch_size)
if fields:
field_names = list(fields.keys())
self.redis.hdel(key, *field_names)
deleted_count += len(field_names)
print(f"Hash {key}: 已删除 {deleted_count} 个字段")
time.sleep(self.sleep_time)
if cursor == 0:
break
self.redis.delete(key)
print(f"Hash {key} 删除完成")
# ... 其他类型的删除方法类似
# 使用示例
r = redis.Redis(host='localhost', port=6379, db=0)
deleter = BigKeyDeleter(r, batch_size=500, sleep_time=0.005)
# 删除指定的大key
big_keys = ['large_hash', 'large_list', 'large_set']
for key in big_keys:
if r.exists(key):
deleter.delete_key(key)
6. 惰性删除配置与调优
6.1 惰性删除原理
6.1.1 工作机制
graph TD
A[客户端发送DEL命令] --> B{key大小检查}
B -->|小key| C[同步删除]
B -->|大key| D[标记为删除]
D --> E[后台线程处理]
E --> F[实际释放内存]
C --> G[立即返回]
D --> G
style C fill:#c8e6c9
style E fill:#fff3e0
style F fill:#c8e6c9
6.1.2 触发条件
# 惰性删除触发条件
# 1. 使用UNLINK命令
# 2. 配置了lazyfree相关参数
# 3. key大小超过阈值
# 4. 内存压力大时
6.2 配置参数详解
6.2.1 基础配置
# redis.conf 惰性删除配置
# 过期key的惰性删除
lazyfree-lazy-expire yes
# 内存淘汰时的惰性删除
lazyfree-lazy-eviction yes
# 服务器端删除的惰性删除(如RENAME、MOVE等)
lazyfree-lazy-server-del yes
# 从库接收到DEL命令时的惰性删除
replica-lazy-flush yes
# FLUSHDB/FLUSHALL的异步执行
lazyfree-lazy-user-del yes
6.2.2 高级配置
# 后台线程数量(Redis 6.0+)
io-threads 4
io-threads-do-reads yes
# 内存回收相关
maxmemory-policy allkeys-lru
maxmemory 2gb
# AOF相关的异步操作
aof-rewrite-incremental-fsync yes
aof-use-rdb-preamble yes
6.3 性能调优
6.3.1 监控指标
# 查看惰性删除统计
INFO stats | grep lazy
# 输出示例
lazyfree_pending_objects:0
lazyfreed_objects:12345
# 查看后台任务队列
INFO persistence | grep bio
# 输出示例
bio_pending_jobs:0
bio_processed_jobs:67890
6.3.2 性能测试
# 性能测试脚本
import redis
import time
import threading
def test_delete_performance():
r = redis.Redis(host='localhost', port=6379, db=0)
# 创建测试数据
print("创建测试数据...")
for i in range(1000):
# 创建大Hash
hash_key = f"test_hash_{i}"
for j in range(1000):
r.hset(hash_key, f"field_{j}", f"value_{j}" * 100)
print("测试同步删除...")
start_time = time.time()
for i in range(500):
r.delete(f"test_hash_{i}")
sync_time = time.time() - start_time
print("测试异步删除...")
start_time = time.time()
for i in range(500, 1000):
r.unlink(f"test_hash_{i}")
async_time = time.time() - start_time
print(f"同步删除耗时: {sync_time:.2f}秒")
print(f"异步删除耗时: {async_time:.2f}秒")
print(f"性能提升: {(sync_time/async_time):.2f}倍")
if __name__ == "__main__":
test_delete_performance()
6.3.3 调优建议
# 1. 根据硬件配置调整线程数
# CPU核心数 <= 4: io-threads 2
# CPU核心数 4-8: io-threads 4
# CPU核心数 > 8: io-threads 6-8
# 2. 监控内存使用
# 确保有足够内存用于后台删除
# 建议预留20-30%内存空间
# 3. 网络优化
# 增加网络缓冲区大小
tcp-keepalive 300
timeout 0
# 4. 持久化优化
# 使用异步AOF重写
aof-rewrite-incremental-fsync yes
# 5. 客户端优化
# 使用连接池
# 合理设置超时时间
# 批量操作减少网络往返
6.4 故障排查
6.4.1 常见问题
# 问题1: 惰性删除不生效
# 检查配置
CONFIG GET lazyfree*
# 检查Redis版本(需要4.0+)
INFO server | grep redis_version
# 问题2: 内存持续增长
# 检查后台任务队列
INFO persistence | grep bio
# 检查内存使用
INFO memory
# 问题3: 删除操作仍然阻塞
# 检查key大小
MEMORY USAGE key_name
# 检查是否使用了正确的删除命令
# 使用UNLINK而不是DEL
6.4.2 诊断脚本
# 惰性删除诊断脚本
import redis
import time
def diagnose_lazy_free(redis_client):
"""
诊断惰性删除配置和状态
"""
print("=== Redis 惰性删除诊断 ===")
# 检查Redis版本
info = redis_client.info()
version = info['redis_version']
print(f"Redis版本: {version}")
if version < '4.0.0':
print("警告: Redis版本过低,不支持惰性删除")
return
# 检查惰性删除配置
print("\n=== 惰性删除配置 ===")
lazy_configs = [
'lazyfree-lazy-expire',
'lazyfree-lazy-eviction',
'lazyfree-lazy-server-del',
'replica-lazy-flush'
]
for config in lazy_configs:
try:
value = redis_client.config_get(config)
print(f"{config}: {value.get(config, 'N/A')}")
except:
print(f"{config}: 无法获取")
# 检查统计信息
print("\n=== 惰性删除统计 ===")
stats = redis_client.info('stats')
lazy_stats = {
'lazyfree_pending_objects': '待删除对象数',
'lazyfreed_objects': '已删除对象数'
}
for stat, desc in lazy_stats.items():
value = stats.get(stat, 'N/A')
print(f"{desc}: {value}")
# 检查后台任务
print("\n=== 后台任务状态 ===")
persistence = redis_client.info('persistence')
bio_stats = {
'bio_pending_jobs': '待处理任务数',
'bio_processed_jobs': '已处理任务数'
}
for stat, desc in bio_stats.items():
value = persistence.get(stat, 'N/A')
print(f"{desc}: {value}")
# 内存使用情况
print("\n=== 内存使用情况 ===")
memory = redis_client.info('memory')
memory_stats = {
'used_memory_human': '已使用内存',
'used_memory_peak_human': '内存使用峰值',
'mem_fragmentation_ratio': '内存碎片率'
}
for stat, desc in memory_stats.items():
value = memory.get(stat, 'N/A')
print(f"{desc}: {value}")
# 使用示例
r = redis.Redis(host='localhost', port=6379, db=0)
diagnose_lazy_free(r)
7. 最佳实践总结
7.1 预防措施
# 1. 设计阶段
# - 合理设计数据结构
# - 避免单个key过大
# - 使用分片策略
# 2. 开发阶段
# - 添加key大小检查
# - 使用批量操作
# - 实现渐进式处理
# 3. 部署阶段
# - 配置惰性删除
# - 设置监控告警
# - 定期扫描大key
7.2 监控体系
# Prometheus 监控规则
groups:
- name: redis_bigkey
rules:
- alert: RedisBigKeyDetected
expr: redis_key_size_bytes > 10485760 # 10MB
for: 5m
labels:
severity: warning
annotations:
summary: "Redis大key检测到"
description: "Key {{ $labels.key }} 大小超过10MB"
- alert: RedisLazyFreePending
expr: redis_lazyfree_pending_objects > 1000
for: 2m
labels:
severity: critical
annotations:
summary: "Redis惰性删除队列积压"
description: "待删除对象数量: {{ $value }}"
总结
Redis 大量 Key 和大 Key 问题是生产环境中的常见挑战,需要从预防、发现、处理和监控等多个维度来解决:
- 预防为主:通过合理的数据结构设计和应用层限制来避免问题
- 及时发现:使用多种工具和监控手段及时发现大 Key
- 安全处理:采用渐进式删除和惰性删除来安全处理大 Key
- 持续优化:通过监控和调优不断改进系统性能
通过实施这些策略和最佳实践,可以有效避免大 Key 问题对 Redis 性能和稳定性的影响,确保系统的高可用性和高性能。