Redis内存管理、持久化策略与慢查询排查分析

描述

一、概述

1.1 背景介绍

Redis 在生产环境中承担着缓存、会话存储、消息队列、分布式锁等多种角色。随着数据量增长和并发压力上升,内存碎片、持久化 I/O 抖动、慢查询堆积这三类问题会逐渐显现,直接影响服务延迟和稳定性。Redis 8.x 在内存管理和持久化机制上做了若干改进,但核心调优思路与 7.x 一脉相承。

1.2 技术特点

单线程命令处理:网络 I/O 多线程化(Redis 6.0+),命令执行仍为单线程,避免锁竞争但对大 Key 操作敏感

jemalloc 内存分配:默认使用 jemalloc 5.x,相比 libc malloc 碎片率更低,但长期运行仍需关注 mem_fragmentation_ratio

多种持久化模式:RDB 快照、AOF 追加日志、混合持久化三种模式各有适用场景,选错会导致性能或数据安全问题

1.3 适用场景

场景一:高并发缓存服务,QPS 10万+,需要精细控制内存淘汰策略

场景二:金融/支付类业务,对数据持久化有强要求,需要 AOF everysec 或混合持久化

场景三:大规模 Key 存储,存在 BigKey 风险,需要定期扫描和治理

1.4 环境要求

组件 版本要求 说明
Redis 8.0+ 本文配置基于 Redis 8.x
操作系统 Linux Kernel 4.18+ 需要支持 THP 控制、NUMA 绑定
内存 建议 16GB+ maxmemory 设置为物理内存的 60-70%
存储 SSD(AOF 场景) AOF rewrite 对磁盘 I/O 压力较大

二、详细步骤

2.1 内存模型与碎片率分析

2.1.1 理解 jemalloc 与内存碎片

Redis 使用 jemalloc 作为默认内存分配器。jemalloc 将内存按 size class 分配,小对象(<= 14KB)走 small bin,大对象走 large bin。这种设计在高频分配/释放场景下碎片率远低于 ptmalloc,但在 Key 大小分布极不均匀时仍会产生碎片。

通过 INFO memory 查看关键指标:

 

# 查看内存使用详情
redis-cli -h 127.0.0.1 -p 6379 INFO memory | grep -E "used_memory|mem_fragmentation|allocator"

# 关键输出字段说明:
# used_memory: Redis 实际使用的内存(字节)
# used_memory_rss: 操作系统分配给 Redis 的内存(RSS)
# mem_fragmentation_ratio: RSS / used_memory,正常范围 1.0~1.5
# allocator_frag_ratio: jemalloc 内部碎片率

 

碎片率判断标准

mem_fragmentation_ratio < 1.0:内存被 swap,性能严重下降,需立即处理

1.0 ~ 1.5:正常范围

> 1.5:碎片率偏高,考虑开启主动碎片整理

2.1.2 主动碎片整理配置

Redis 4.0+ 引入 activedefrag,8.x 默认关闭,需根据实际情况开启:

 

# redis.conf 配置
activedefrag yes                    # 开启主动碎片整理
active-defrag-ignore-bytes 100mb    # 碎片超过 100MB 才触发
active-defrag-threshold-lower 10    # 碎片率超过 10% 开始整理
active-defrag-threshold-upper 100   # 碎片率超过 100% 全力整理
active-defrag-cycle-min 1           # 最少占用 CPU 1%
active-defrag-cycle-max 25          # 最多占用 CPU 25%

 

主动碎片整理会占用 CPU,在 CPU 敏感的场景下需要控制 active-defrag-cycle-max,避免影响命令处理延迟。

2.2 maxmemory 策略选择

2.2.1 各策略适用场景

 

# redis.conf
maxmemory 8gb                       # 设置最大内存,建议为物理内存的 60-70%
maxmemory-policy allkeys-lru        # 淘汰策略

 

策略 淘汰范围 适用场景
noeviction 不淘汰,写入报错 数据不可丢失的持久化存储
allkeys-lru 所有 Key,LRU 通用缓存,Key 无过期时间
volatile-lru 有 TTL 的 Key,LRU 混合存储(部分持久 + 部分缓存)
allkeys-lfu 所有 Key,LFU 热点数据明显,访问频率差异大
volatile-lfu 有 TTL 的 Key,LFU 同上,但只淘汰有过期时间的 Key
allkeys-random 随机淘汰 访问模式均匀,无热点
volatile-ttl TTL 最短的 Key 希望优先淘汰即将过期的数据

决策原则:纯缓存场景用 allkeys-lru;有热点数据且访问频率差异明显时用 allkeys-lfu;混合存储(既有缓存又有持久数据)用 volatile-lru,但要确保持久数据不设 TTL。

2.3 持久化策略选择

2.3.1 RDB vs AOF vs 混合持久化

RDB(快照)

 

# redis.conf
save 900 1        # 900秒内有1个Key变更则触发
save 300 10       # 300秒内有10个Key变更则触发
save 60 10000     # 60秒内有10000个Key变更则触发
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis

 

RDB 通过 fork() 子进程生成快照,对主进程影响取决于内存大小和 COW(Copy-On-Write)开销。内存 8GB 的实例,fork 耗时通常在 50~200ms,期间主进程会短暂阻塞。

AOF(追加日志)

 

# redis.conf
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec    # 每秒 fsync,丢失最多 1 秒数据
# appendfsync always    # 每次写入 fsync,最安全但性能最差
# appendfsync no        # 由 OS 决定,性能最好但可能丢失较多数据
no-appendfsync-on-rewrite yes   # AOF rewrite 期间不 fsync,避免 I/O 竞争
auto-aof-rewrite-percentage 100 # AOF 文件增长 100% 时触发 rewrite
auto-aof-rewrite-min-size 64mb  # AOF 文件至少 64MB 才触发 rewrite

 

混合持久化(推荐生产使用)

 

# redis.conf(Redis 4.0+)
aof-use-rdb-preamble yes   # 开启混合持久化
appendonly yes
appendfsync everysec

 

混合持久化在 AOF rewrite 时将当前数据以 RDB 格式写入 AOF 文件头部,后续增量以 AOF 格式追加。恢复速度接近 RDB,数据安全性接近 AOF everysec,是生产环境的最优选择。

2.3.2 持久化性能影响对比

模式 恢复速度 数据安全 写入性能影响 磁盘占用
RDB only 低(分钟级丢失) 低(fork 有抖动)
AOF everysec 高(最多1秒)
AOF always 最高(无丢失) 高(每次 fsync)
混合持久化 高(最多1秒)

2.4 SLOWLOG 配置与慢查询分析

 

# redis.conf
slowlog-log-slower-than 10000   # 记录执行时间超过 10ms 的命令(单位微秒)
slowlog-max-len 1000            # 最多保留 1000 条慢日志
# 查看慢日志
redis-cli SLOWLOG GET 20        # 获取最近 20 条慢日志
redis-cli SLOWLOG LEN           # 查看慢日志总数
redis-cli SLOWLOG RESET         # 清空慢日志

# 慢日志输出格式:
# 1) 1) (integer) 14          # 日志 ID
#    2) (integer) 1706745600  # 执行时间戳
#    3) (integer) 15234       # 执行耗时(微秒)
#    4) 1) "KEYS"             # 命令
#       2) "*user*"
#    5) "127.0.0.1:52341"     # 客户端地址
#    6) ""                    # 客户端名称

 

三、示例代码和配置

3.1 完整配置示例

3.1.1 生产环境 redis.conf

 

# 文件路径:/etc/redis/redis.conf

# 网络
bind 0.0.0.0
port 6379
protected-mode yes
tcp-backlog 511
timeout 300
tcp-keepalive 300

# 内存管理
maxmemory 8gb
maxmemory-policy allkeys-lru
maxmemory-samples 10            # LRU/LFU 采样数,越大越精准但越耗 CPU
activedefrag yes
active-defrag-ignore-bytes 100mb
active-defrag-threshold-lower 10
active-defrag-threshold-upper 100
active-defrag-cycle-min 1
active-defrag-cycle-max 25

# 持久化(混合模式)
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-use-rdb-preamble yes
save 900 1
save 300 10
save 60 10000
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis

# 慢日志
slowlog-log-slower-than 10000
slowlog-max-len 1000

# 延迟监控
latency-monitor-threshold 100   # 记录超过 100ms 的延迟事件

# 线程(Redis 6.0+ I/O 多线程)
io-threads 4                    # I/O 线程数,建议为 CPU 核数的一半
io-threads-do-reads yes

# 客户端
maxclients 10000

 

3.2 大 Key 检测与处理

3.2.1 检测大 Key

 

# 方法一:redis-cli 内置扫描(推荐,不阻塞)
redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 0.1
# -i 0.1 表示每次扫描后休眠 0.1 秒,降低对生产的影响

# 方法二:使用 SCAN 替代 KEYS(生产环境禁止使用 KEYS *)
redis-cli -h 127.0.0.1 -p 6379 --scan --pattern "user:*" | head -100

# 方法三:RDB 离线分析(最全面,零影响)
# 使用 rdb-tools 分析 dump.rdb
pip install rdbtools python-lzf
rdb --command memory /var/lib/redis/dump.rdb | sort -t',' -k4 -rn | head -20

 

3.2.2 大 Key 处理策略

 

# 对于大 Hash(字段数 > 5000):拆分为多个小 Hash
# 原始:HSET user:1000 field1 val1 field2 val2 ... field10000 val10000
# 拆分:按 field hash 分桶
# user0, user1, ... user99

# 对于大 List/Set(元素数 > 10000):分页存储或使用 Stream 替代

# 删除大 Key:使用 UNLINK 替代 DEL(异步删除,不阻塞主线程)
redis-cli -h 127.0.0.1 UNLINK big_key_name

# 批量异步删除
redis-cli -h 127.0.0.1 --scan --pattern "temp:*" | xargs redis-cli UNLINK

 

3.3 Pipeline 与 Lua 脚本优化

3.3.1 Pipeline 减少网络往返

 

# Python 示例:Pipeline 批量操作
import redis

r = redis.Redis(host='127.0.0.1', port=6379)

# 不使用 Pipeline:N 次网络往返
for i in range(1000):
    r.set(f"key:{i}", f"value:{i}")

# 使用 Pipeline:1 次网络往返
pipe = r.pipeline(transaction=False)  # transaction=False 不使用 MULTI/EXEC,性能更高
for i in range(1000):
    pipe.set(f"key:{i}", f"value:{i}")
pipe.execute()

 

3.3.2 Lua 脚本保证原子性

 

-- 原子性 CAS(Compare And Swap)操作
-- 文件:cas.lua
local current = redis.call('GET', KEYS[1])
if current == ARGV[1] then
    redis.call('SET', KEYS[1], ARGV[2])
    return 1
else
    return 0
end
# 执行 Lua 脚本
redis-cli EVAL "$(cat cas.lua)" 1 mykey old_value new_value

# 生产环境推荐使用 SCRIPT LOAD + EVALSHA,避免每次传输脚本内容
redis-cli SCRIPT LOAD "$(cat cas.lua)"
# 返回 SHA1,后续使用 EVALSHA sha1 1 mykey old_value new_value

 

四、最佳实践和注意事项

4.1 最佳实践

4.1.1 性能优化

禁用 THP(透明大页):THP 会导致 fork 时 COW 开销剧增,Redis 启动时会警告

 

# 临时禁用
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

# 永久禁用(写入 /etc/rc.local 或 systemd service)
cat >> /etc/rc.local << 'EOF'
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
EOF

 

调整 vm.overcommit_memory:避免 fork 时因内存不足失败

 

sysctl vm.overcommit_memory=1
echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf

 

连接池配置:避免频繁建立 TCP 连接

 

# Python redis-py 连接池
pool = redis.ConnectionPool(
    host='127.0.0.1', port=6379,
    max_connections=50,       # 最大连接数
    socket_timeout=1.0,       # 命令超时 1 秒
    socket_connect_timeout=1.0
)
r = redis.Redis(connection_pool=pool)

 

4.1.2 安全加固

启用 ACL 访问控制(Redis 6.0+):

 

# redis.conf
aclfile /etc/redis/users.acl

# users.acl 内容
user default off                                    # 禁用默认用户
user appuser on >StrongPassword123 ~app:* +@read +@write +DEL  # 应用用户
user monitor on >MonitorPass ~* +INFO +MONITOR      # 监控用户

 

禁用危险命令

 

# redis.conf
rename-command KEYS     ""        # 禁用 KEYS
rename-command FLUSHALL "FLUSHALL_DISABLED_9x8k2"
rename-command FLUSHDB  "FLUSHDB_DISABLED_7m3n1"
rename-command CONFIG   "CONFIG_9a2b3c"
rename-command DEBUG    ""

 

4.1.3 高可用配置

Sentinel 最小配置(3节点):

 

# sentinel.conf
sentinel monitor mymaster 192.168.1.10 6379 2   # 需要 2 个 sentinel 同意才切换
sentinel down-after-milliseconds mymaster 5000   # 5秒无响应判定下线
sentinel failover-timeout mymaster 60000         # 故障转移超时 60 秒
sentinel parallel-syncs mymaster 1               # 同时只有 1 个 slave 同步新 master

 

4.2 注意事项

4.2.1 配置注意事项

 警告:appendfsync always 在高写入场景下会导致 Redis 吞吐量下降 90% 以上,除非业务对数据零丢失有强制要求,否则不要使用。

 KEYS * 命令在生产环境会阻塞 Redis 数秒,100万 Key 的实例执行 KEYS 约需 300ms,必须用 SCAN 替代

 maxmemory 不设置时 Redis 会无限使用内存直到 OOM,容器环境尤其危险

AOF rewrite 期间磁盘写入量是平时的 2-3 倍,需要预留足够磁盘空间

4.2.2 常见错误

错误现象 原因分析 解决方案
MISCONF Redis is configured to save RDB snapshots 磁盘满或权限问题导致 RDB 保存失败 检查磁盘空间和 dir 目录权限
OOM command not allowed when used memory > maxmemory 内存达到上限且淘汰策略为 noeviction 调整 maxmemory 或更换淘汰策略
LOADING Redis is loading the dataset in memory 重启后正在加载 AOF/RDB 等待加载完成,大文件可能需要数分钟
延迟突增(latency spike) fork 触发 COW,或 AOF rewrite 检查 INFO stats 中 latest_fork_usec,考虑减少 save 频率
内存碎片率 > 2.0 大量 Key 过期后内存未释放 开启 activedefrag 或重启实例

五、故障排查和监控

5.1 故障排查

5.1.1 日志查看

 

# 查看 Redis 日志
tail -f /var/log/redis/redis-server.log

# 查看延迟事件
redis-cli -h 127.0.0.1 LATENCY HISTORY event
redis-cli -h 127.0.0.1 LATENCY LATEST
redis-cli -h 127.0.0.1 LATENCY RESET

# 实时监控命令流(慎用,高并发下会产生大量输出)
redis-cli -h 127.0.0.1 MONITOR | head -100

 

5.1.2 常见问题排查

问题一:Redis 响应延迟突增

 

# 检查是否有慢查询
redis-cli SLOWLOG GET 10

# 检查 fork 耗时
redis-cli INFO stats | grep latest_fork_usec

# 检查是否有阻塞命令
redis-cli INFO clients | grep blocked_clients

# 检查内存使用
redis-cli INFO memory | grep -E "used_memory_human|mem_fragmentation_ratio"

 

问题二:内存持续增长不释放

 

# 检查 Key 数量和过期情况
redis-cli INFO keyspace

# 检查内存分配详情
redis-cli MEMORY DOCTOR

# 分析内存使用
redis-cli MEMORY USAGE keyname   # 查看单个 Key 内存占用

 

问题三:AOF 文件异常增大

 

# 手动触发 AOF rewrite
redis-cli BGREWRITEAOF

# 检查 rewrite 状态
redis-cli INFO persistence | grep aof_rewrite

 

5.2 性能监控

5.2.1 关键指标监控

 

# 综合状态检查脚本
#!/bin/bash
HOST="127.0.0.1"
PORT="6379"

echo "=== Redis 性能快照 ==="
redis-cli -h $HOST -p $PORT INFO all | grep -E 
  "redis_version|uptime_in_days|connected_clients|blocked_clients|
used_memory_human|mem_fragmentation_ratio|total_commands_processed|
instantaneous_ops_per_sec|rejected_connections|keyspace_hits|
keyspace_misses|expired_keys|evicted_keys|latest_fork_usec|
aof_enabled|rdb_last_bgsave_status"

 

5.2.2 监控指标说明

指标名称 正常范围 告警阈值 说明
mem_fragmentation_ratio 1.0~1.5 > 2.0 内存碎片率
blocked_clients 0 > 10 被阻塞的客户端数
keyspace_hit_rate > 90% < 80% 缓存命中率
latest_fork_usec < 200ms > 1000ms 最近一次 fork 耗时
rejected_connections 0 > 0 被拒绝的连接数(达到 maxclients)
evicted_keys 视业务 持续增长 被淘汰的 Key 数,持续增长说明内存不足

5.2.3 Prometheus 监控规则

 

# redis_alerts.yml
groups:
-name:redis
    rules:
      -alert:RedisMemoryFragmentation
        expr:redis_mem_fragmentation_ratio>2.0
        for:5m
        labels:
          severity:warning
        annotations:
          summary:"Redis 内存碎片率过高: {{ $value }}"

      -alert:RedisHighMemoryUsage
        expr:redis_memory_used_bytes/redis_memory_max_bytes>0.9
        for:2m
        labels:
          severity:critical
        annotations:
          summary:"Redis 内存使用率超过 90%"

      -alert:RedisCacheHitRateLow
        expr:|
          rate(redis_keyspace_hits_total[5m]) /
          (rate(redis_keyspace_hits_total[5m]) + rate(redis_keyspace_misses_total[5m])) < 0.8
        for:10m
        labels:
          severity:warning
        annotations:
          summary:"Redis 缓存命中率低于 80%: {{ $value | humanizePercentage }}"

 

5.3 备份与恢复

5.3.1 备份脚本

 

#!/bin/bash
# 文件名:redis_backup.sh
# 功能:Redis RDB 备份,保留 7 天

REDIS_CLI="/usr/bin/redis-cli"
BACKUP_DIR="/data/backup/redis"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7

mkdir -p "$BACKUP_DIR"

# 触发 BGSAVE
$REDIS_CLI -h 127.0.0.1 -p 6379 BGSAVE

# 等待 BGSAVE 完成
while [ "$($REDIS_CLI -h 127.0.0.1 INFO persistence | grep rdb_bgsave_in_progress | awk -F: '{print $2}' | tr -d '
')" = "1" ]; do
    sleep 1
done

# 复制 RDB 文件
cp /var/lib/redis/dump.rdb "$BACKUP_DIR/dump_${DATE}.rdb"

# 压缩
gzip "$BACKUP_DIR/dump_${DATE}.rdb"

# 清理旧备份
find "$BACKUP_DIR" -name "dump_*.rdb.gz" -mtime +$RETENTION_DAYS -delete

echo"备份完成:$BACKUP_DIR/dump_${DATE}.rdb.gz"

 

5.3.2 恢复流程

停止服务:systemctl stop redis

恢复数据:cp dump_20260101_120000.rdb.gz /var/lib/redis/ && gunzip dump_*.rdb.gz && mv dump_*.rdb dump.rdb

验证完整性:redis-check-rdb /var/lib/redis/dump.rdb

重启服务:systemctl start redis && redis-cli PING

六、总结

6.1 技术要点回顾

 内存碎片:mem_fragmentation_ratio > 1.5 时开启 activedefrag,禁用 THP 是前提

 淘汰策略:纯缓存用 allkeys-lru,热点明显用 allkeys-lfu,混合存储用 volatile-lru

 持久化:生产环境首选混合持久化(aof-use-rdb-preamble yes),兼顾恢复速度和数据安全

 慢查询:KEYS * 是最常见的慢查询来源,用 SCAN 替代;大 Key 删除用 UNLINK

6.2 进阶学习方向

Redis Cluster 分片:水平扩展方案,16384 个 slot 的分配策略和 resharding 操作

实践建议:先在测试环境模拟节点故障,理解 cluster-node-timeout 对业务的影响

Redis Stream:替代 List 实现消息队列,支持消费者组和消息确认

实践建议:对比 Kafka 的适用场景,Stream 适合轻量级、低延迟的消息场景

Redis Function(Redis 7.0+):替代 Lua 脚本的新方案,支持库管理和持久化

实践建议:评估是否值得从 EVALSHA 迁移到 Function

6.3 参考资料

Redis 官方文档 - 配置参数权威参考

Redis 内存优化指南 - 官方内存优化建议

redis-rdb-tools - RDB 离线分析工具

附录

A. 命令速查表

 

redis-cli INFO memory              # 内存使用详情
redis-cli INFO stats               # 统计信息(命中率、连接数等)
redis-cli INFO persistence         # 持久化状态
redis-cli SLOWLOG GET 20           # 最近 20 条慢日志
redis-cli LATENCY LATEST           # 最新延迟事件
redis-cli MEMORY DOCTOR            # 内存诊断建议
redis-cli --bigkeys -i 0.1         # 扫描大 Key(带限速)
redis-cli --scan --pattern "key:*" # 安全扫描 Key
redis-cli BGSAVE                   # 触发后台 RDB 保存
redis-cli BGREWRITEAOF             # 触发 AOF rewrite
redis-cli DEBUG SLEEP 0            # 测试延迟基线
redis-cli CLIENT LIST              # 查看所有客户端连接

 

B. 配置参数详解

参数 默认值 推荐值 说明
maxmemory-samples 5 10 LRU/LFU 采样精度,越大越准但越耗 CPU
hz 10 15~20 后台任务频率,影响过期 Key 清理速度
dynamic-hz yes yes 根据客户端数量动态调整 hz
lazyfree-lazy-eviction no yes 异步淘汰 Key,避免阻塞
lazyfree-lazy-expire no yes 异步删除过期 Key
lazyfree-lazy-server-del no yes DEL 命令异步执行

C. 术语表

术语 英文 解释
内存碎片率 mem_fragmentation_ratio RSS 与实际使用内存的比值,反映内存利用效率
写时复制 Copy-On-Write (COW) fork 后父子进程共享内存页,修改时才复制,影响 RDB/AOF rewrite 性能
淘汰策略 Eviction Policy 内存达到 maxmemory 时决定删除哪些 Key 的算法
慢日志 Slow Log 记录执行时间超过阈值的命令,用于性能分析
管道 Pipeline 批量发送命令减少网络往返次数的技术

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分