Redis内存碎片率问题
Redis实例运行一段时间后,INFO memory中的mem_fragmentation_ratio(内存碎片率)可能远大于1.5。这意味着Redis向操作系统申请了远超实际存储数据所需的内存。在4GB数据量的实例上,碎片率1.8意味着额外占用了约3.2GB内存,这对于内存资源紧张的服务器是不可接受的浪费。
碎片率正常范围在1.0到1.5之间。低于1.0通常表示操作系统swap正在发生,性能会急剧下降。高于1.5则需要分析原因并处理。以下是Redis内存碎片的分析和治理方法。
内存碎片成因分析
Redis默认使用jemalloc作为内存分配器。当频繁修改和删除不同大小的key时,分配器释放的内存并不一定会归还给操作系统,而是保留在进程的空闲链表中以备复用。产生的根本原因:
- 频繁删除大key:删除一个大hash或list后,释放的内存空间可能无法被新的小key复用
- 过期key扫描:大量key同时过期,释放的内存空间碎片化
- 数据大小分布不均匀:大小差异悬殊的key交替写入
- 持久化fork:BGSAVE或BGREWRITEAOF时fork子进程,COW机制下父子进程内存页分离,加剧碎片
检查当前碎片状况:
redis-cli INFO memory
# 关键字段:
# used_memory:8589934592 # Redis分配器分配的内存(逻辑使用量)
# used_memory_rss:15461882265 # 操作系统视角下进程占用的物理内存
# mem_fragmentation_ratio:1.80 # used_memory_rss / used_memory
# mem_fragmentation_bytes:6.7GB # 碎片占用字节数
# allocator:jemalloc-5.2.1 # 当前使用的内存分配器
# 按数据类型查看内存分布
redis-cli MEMORY STATS
# 查看单个key的精确内存占用
redis-cli MEMORY USAGE user:1001 SAMPLES 0
active defrag自动碎片整理
Redis 4.0及以上版本内置了active defrag(主动碎片整理)功能。它在后台线程中扫描碎片化内存区域,将分散的数据对象移动到连续的内存空间中,同时释放空闲页给操作系统。这个过程中Redis正常处理命令请求,不需要停机。
开启和配置active defrag:
# redis.conf 配置参数
activedefrag yes # 开启主动碎片整理
# 触发阈值——只有碎片率达到以下条件时才开始整理
active-defrag-ignore-bytes 100000000 # 碎片字节数超过100MB才开始
active-defrag-threshold-lower 10 # 碎片率超过10%才开始
active-defrag-threshold-upper 100 # 碎片率超过100%时全力整理
# CPU使用控制——避免碎片整理占用过多CPU影响正常请求
active-defrag-cycle-min 1 # 碎片整理最少占用CPU百分比
active-defrag-cycle-max 25 # 碎片整理最多占用CPU百分比
# 运行时动态调整(无需重启)
redis-cli CONFIG SET activedefrag yes
redis-cli CONFIG SET active-defrag-cycle-max 15
active-defrag-cycle-max是最重要的调优参数。设得太高会导致正常请求延迟升高(CPU被碎片整理线程占用),设得太低则碎片回收速度跟不上碎片产生速度。建议从5开始逐步观察调整:
# 监控碎片率变化和请求延迟
watch -n 5 'redis-cli INFO memory | grep mem_fragmentation_ratio; redis-cli --latency'
# 如果碎片率持续不降,逐步提高cycle-max
redis-cli CONFIG SET active-defrag-cycle-max 10
# 观察5分钟后继续调整
redis-cli CONFIG SET active-defrag-cycle-max 15
手动碎片整理命令
对于不支持active defrag的旧版本Redis,或需要一次性处理严重碎片的情况,使用MEMORY PURGE命令:
# 手动触发内存分配器的purge操作
# 促使jemalloc将空闲内存归还给操作系统
redis-cli MEMORY PURGE
# 检查效果
redis-cli INFO memory | grep mem_fragmentation_ratio
MEMORY PURGE只是建议分配器释放空闲页,实际效果取决于分配器实现和碎片分布。更彻底的方式是使用FLUSHALL清空数据(仅限非生产环境或可重建的缓存场景)。
另一个方案是主从切换:在从节点上执行DEBUG RELOAD(会触发RDB加载,内存重新紧凑分配),然后切换主从角色。这个操作有短暂不可用风险,需在低峰期执行。
从源头减少碎片产生
碎片整理是被动手段,从使用模式上优化才是根本方案。
选择合理的数据结构:
# 不推荐:大量小string key
SET user:1001:name "Alice"
SET user:1001:email "alice@example.com"
SET user:1001:age "30"
# 每个key单独分配内存,删除时产生碎片
# 推荐:使用hash结构聚合
HSET user:1001 name "Alice" email "alice@example.com" age "30"
# 一个key,连续内存分配,碎片少
# hash的ziplist编码在field数量少时非常紧凑
# redis.conf配置
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
避免大key操作:
# 危险:一次性删除大key,释放大量碎片化内存
DEL big_list_key # 如果该list有百万元素,DEL会阻塞数秒
# 推荐:分批删除
# Redis 4.0+使用UNLINK异步删除
UNLINK big_list_key
# 对于需要逐步清理的大list
while true; do
# 每次弹出100个元素
elements=$(redis-cli LRANGE big_list_key 0 99)
redis-cli LTRIM big_list_key 100 -1
[ -z "$elements" ] && break
sleep 0.1
done
redis-cli DEL big_list_key
控制key过期策略:
# 不推荐:大量key同时过期
# 使用EXPIRE设置相同的TTL,同一时刻集中过期
for i in $(seq 1 100000); do
redis-cli SET "cache:$i" "data" EX 3600
done
# 1小时后10万个key同时过期,产生碎片高峰
# 推荐:随机化TTL,分散过期时间
for i in $(seq 1 100000); do
ttl=$(( 3000 + RANDOM % 1200 )) # 3000-4200秒之间随机
redis-cli SET "cache:$i" "data" EX $ttl
done
监控与告警配置
将碎片率纳入监控体系,在问题恶化前预警:
# Prometheus redis_exporter 已采集该指标
# 关键指标:redis_memory_fragmentation_ratio
# 告警规则
groups:
- name: redis_alerts
rules:
- alert: RedisHighFragmentation
expr: redis_memory_fragmentation_ratio > 1.5
for: 10m
labels:
severity: warning
annotations:
summary: "Redis碎片率过高 {{ $labels.instance }}"
description: "碎片率: {{ $value }},建议检查并开启active defrag"
- alert: RedisSwapDetected
expr: redis_memory_fragmentation_ratio < 1.0
for: 5m
labels:
severity: critical
annotations:
summary: "Redis可能发生swap {{ $labels.instance }}"
description: "碎片率低于1.0,rss小于used_memory,可能存在swap"
定期巡检脚本,自动汇总各实例碎片状况:
#!/bin/bash
# scan_redis_fragment.sh
INSTANCES=("10.0.1.10:6379" "10.0.1.11:6379" "10.0.1.12:6379")
echo "Instance | Fragmentation | Used(MB) | RSS(MB)"
echo "--------------------------------------------"
for inst in "${INSTANCES[@]}"; do
info=$(redis-cli -h $(echo $inst | cut -d: -f1) \
-p $(echo $inst | cut -d: -f2) \
INFO memory)
ratio=$(echo "$info" | grep mem_fragmentation_ratio | cut -d: -f2 | tr -d '\r')
used=$(echo "$info" | grep used_memory: | cut -d: -f2 | tr -d '\r')
rss=$(echo "$info" | grep used_memory_rss: | cut -d: -f2 | tr -d '\r')
used_mb=$(echo "scale=1; $used/1048576" | bc)
rss_mb=$(echo "scale=1; $rss/1048576" | bc)
echo "$inst | $ratio | $used_mb | $rss_mb"
done
建议每周运行一次巡检,对碎片率超过1.5的实例执行碎片整理或排查是否有大key集中写入。对超过2.0的实例应立即处理,避免内存浪费加剧导致OOM。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/redis-nei-cun-sui-pian-zhi-li-shi-zhan-activedefrag-pei-zhi/