在分布式系统架构中,Redis作为核心的缓存和数据存储组件,其性能表现直接影响着整个系统的响应速度和稳定性。但在实际生产环境中,我们经常会遇到Redis响应变慢、内存占用过高、吞吐量下降等问题。这些问题往往源于三个关键因素:热键(Hot Keys)、大键(Big Keys)和慢查询(Slow Queries)。
我曾在多个千万级QPS的生产环境中处理过Redis性能问题,发现90%的性能瓶颈都可以通过分析这三个关键指标来定位。不同于简单的监控指标查看,真正的性能诊断需要结合具体业务场景,采用系统化的分析方法和工具链。
热键是指那些被异常高频访问的Redis键,通常表现为单个键的QPS远高于平均水平。在我处理的一个电商案例中,一个商品详情键的QPS峰值达到了35,000+,而集群平均QPS仅为8,000左右。
识别热键的常用方法:
redis-cli --hotkeys命令(需要Redis 4.0+)redis-cli --bigkeys结合redis-cli --stat分析注意:直接在线上使用MONITOR命令可能导致Redis性能下降,建议在低峰期使用或通过从库执行。
针对识别出的热键,我们通常采用分层解决方案:
java复制// 伪代码示例:使用Caffeine实现二级缓存
LoadingCache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(key -> redisTemplate.opsForValue().get(key));
bash复制# 将热门商品数据拆分为多个子键
SET product:1001:base "{...json数据...}"
SET product:1001:ext "{...json数据...}"
python复制# 配置读写分离
r = redis.StrictRedis(
host='master-host',
port=6379,
db=0,
decode_responses=True)
r_slave = redis.StrictRedis(
host='slave-host',
port=6379,
db=0,
decode_responses=True)
大键通常指占用内存过大的Redis键,常见于以下几种情况:
使用redis-cli --bigkeys命令可以快速扫描:
bash复制$ redis-cli --bigkeys -i 0.1
# 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 (usually not needed).
[00.00%] Biggest string found so far 'user:session:xxxx' with 5123123 bytes
bash复制# 原始大Hash
HMSET user:1001 profile "{...大json...}" orders "[...大数组...]"
# 优化后
HMSET user:1001:basic name "张三" age 30
SADD user:1001:orders 10001 10002 10003
python复制def shard_key(base_key, shard_size=1000):
import hashlib
shard = int(hashlib.md5(base_key.encode()).hexdigest(), 16) % shard_size
return f"{base_key}:shard_{shard}"
java复制// 使用更高效的序列化方案
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
Redis慢查询日志需要合理配置:
bash复制# redis.conf配置示例
slowlog-log-slower-than 10000 # 10毫秒
slowlog-max-len 1024 # 保留1024条记录
查询慢日志命令:
bash复制SLOWLOG GET 10 # 获取最近10条慢查询
bash复制# 错误示例
KEYS user:session:*
# 优化方案
SCAN 0 MATCH user:session:* COUNT 1000
bash复制# 危险操作
SUNIONSTORE result set1 set2 set3
# 优化方案
# 1. 分批执行
# 2. 使用pipeline减少网络开销
lua复制-- 低效脚本
for i=1,100000 do
redis.call('GET', 'key:'..i)
end
-- 优化后
local results = {}
for i=1,1000 do
results[i] = redis.call('MGET', unpack(keys, (i-1)*100+1, i*100))
end
RedisInsight:
rdr:
redis-rdb-tools:
bash复制pip install rdbtools
rdb --command memory dump.rdb --bytes 1024 --largest 10
关键指标关联矩阵:
| 指标 | 关联问题 | 阈值参考 |
|---|---|---|
| CPU使用率 | 热键/复杂命令 | >70%告警 |
| 内存增长率 | 大键/内存泄漏 | 日增>5%告警 |
| 网络输入/输出 | 大值传输/频繁序列化 | >50MB/s告警 |
| 延迟百分比 | 慢查询/系统负载 | P99>100ms告警 |
问题现象:
诊断过程:
redis-cli --hotkeys发现商品库存键QPS达到45,000SLOWLOG显示大量DECR操作排队INFO commandstats显示99%的CPU时间消耗在DECR命令解决方案:
lua复制-- 库存扣减Lua脚本优化
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock <= 0 then
return 0
end
redis.call('SET', KEYS[1], stock - 1)
return 1
问题现象:
诊断过程:
redis-cli --bigkeys发现用户timeline键平均大小3.2MBMEMORY USAGE确认单个键最大达到28MB解决方案:
python复制# Feed分片存储实现
def store_feed(user_id, feed_item):
shard = int(time.time() / (86400*7)) # 按周分片
redis.zadd(f"user:{user_id}:feeds:{shard}",
{feed_item['id']: feed_item['timestamp']})
# 只保留最近4个分片
for old_shard in range(shard-4, shard):
redis.delete(f"user:{user_id}:feeds:{old_shard}")
当发现mem_fragmentation_ratio>1.5时:
bash复制# 手动触发内存整理
redis-cli MEMORY PURGE
# 配置自动整理
config set activedefrag yes
config set active-defrag-ignore-bytes 100mb
config set active-defrag-threshold-lower 10
bash复制# 使用hash tag确保相关键在同一节点
SET {user:1001}:session "data"
SET {user:1001}:profile "data"
bash复制# 批量检查集群节点慢查询
for port in {7001..7006}; do
redis-cli -p $port --cluster call SLOWLOG LEN
done
建立性能基线:
bash复制# 基准测试命令
redis-benchmark -t set,get -n 100000 -q -P 16
# 测试不同数据大小
redis-benchmark -t set -n 100000 -q -d 1024 # 1KB数据
redis-benchmark -t set -n 100000 -q -d 10240 # 10KB数据
python复制def redis_health_check():
# 检查慢查询数量
slow_log_len = redis.slowlog_len()
if slow_log_len > 1000:
alert("慢查询堆积警告")
# 检查内存碎片率
info = redis.info('memory')
if info['mem_fragmentation_ratio'] > 1.5:
alert("内存碎片过高")
code复制所需内存 = (键数量 × 平均键大小) × (1 + 副本因子)
+ (写QPS × 平均值大小) / 持久化频率