【Redis从入门到精通】第69篇:Redis性能调优实战——从监控到参数调整的全套方案
上一篇【第68篇】HyperLogLog——用极小内存统计超大基数下一篇【第70篇】Redis未来展望——从7.x新特性到Redis Stack的全栈野心运维“Redis延迟突然飙到100ms了之前一直稳定在1ms以内。”程序员“最近重构了缓存层加了一个比较大的序列化对象。”运维“多大”程序员“也就……20MB。”运维“你知道Redis是单线程的吗一个20MB的序列化操作会阻塞所有请求。”程序员“单线程那CPU多核不是白买了”运维“……今天不是讨论架构的时候先把那个对象拆了。”Redis以快著称但快不是凭空来的。如果配置不当、使用姿势不对Redis同样会慢得让人怀疑人生。本篇文章汇总了从监控、定位到优化的完整调优方法论帮你把Redis从能用折腾到好用。一、调优方法论五步循环性能调优的标准流程 ┌────────────┐ │ 1. 监控 │ ← INFO / LATENCY / SLOWLOG / Prometheus │ 发现问题 │ 构建全面的性能指标仪表盘 └─────┬──────┘ │ ┌─────▼──────┐ │ 2. 定位 │ ← 分析瓶颈是内存CPU网络磁盘 │ 分析根因 │ 用排除法逐层定位 └─────┬──────┘ │ ┌─────▼──────┐ │ 3. 分析 │ ← 具体的参数配置值是什么 │ 参数与配置 │ 内存分配是否合理big key有哪些 └─────┬──────┘ │ ┌─────▼──────┐ │ 4. 优化 │ ← 调整配置 / 修改代码 / 架构调整 │ 落地调整 │ 一次只改一个变量 └─────┬──────┘ │ ┌─────▼──────┐ │ 5. 验证 │ ← benchmark压测 生产灰度验证 │ 效果确认 │ 确认优化有效且不引入新问题 └────────────┘ │ └────────→ 回到步骤1持续循环⚠️ 注意一次只改一个变量这是调优的铁律。如果你同时改了maxmemory、hash-max-ziplist-entries和tcp-backlog性能好了你不知道是谁的功劳性能差了你也找不到谁干的好事。二、INFO命令Redis的全身扫描仪2.1 INFO各Section详解INFO命令是Redis最强大的诊断工具它返回Redis实例的完整运行状态。每个Section代表一个监控维度。# 查看所有信息redis-cli INFO ALL# 查看特定Sectionredis-cli INFO memory redis-cli INFO stats redis-cli INFO replicationServer服务器基本信息127.0.0.1:6379INFO server# Serverredis_version:7.0.12# Redis版本redis_git_sha1:00000000 redis_mode:standalone# standalone/cluster/sentinelos:Linux5.15.0-91-generic# 操作系统arch_bits:64# 64位还是32位32位内存上限4GBmultiplexing_api:epoll# IO多路复用机制uptime_in_seconds:864000# 运行时长秒uptime_in_days:10# 运行天数hz:10# serverCron执行频率configured_hz:10 lru_clock:1234567# LRU时钟executable:/usr/bin/redis-server config_file:/etc/redis/redis.confClients客户端连接信息127.0.0.1:6379INFO clients# Clientsconnected_clients:125# 当前连接数cluster_connections:0# 集群间连接数maxclients:10000# 最大允许连接由配置决定client_recent_max_input_buffer:8# 最近客户端最大输入缓冲byteclient_recent_max_output_buffer:0# 最近客户端最大输出缓冲blocked_clients:3# 正在等待阻塞命令的客户端数BRPOP等Memory内存使用详情这是最需要关注的Section之一。127.0.0.1:6379INFO memory# Memoryused_memory:2097152000# Redis使用的总内存字节≈ 2GBused_memory_human:1.95G used_memory_rss:2300000000# 操作系统角度Redis占了多少物理内存 ≈ 2.3GBused_memory_rss_human:2.14G used_memory_peak:3145728000# 内存使用历史峰值 ≈ 3GBused_memory_peak_human:2.93G used_memory_peak_perc:66.67%# 峰值使用率used_memory_overhead:104857600# 内部管理开销非数据内存used_memory_startup:1048576# 启动占用的初始内存used_memory_dataset:1992294400# 实际存储数据的内存 used- overheadused_memory_dataset_perc:95.00% allocator_allocated:2100000000 allocator_active:2110000000 allocator_resident:2120000000 total_system_memory:17000000000# 系统总内存# 内存碎片率关键指标mem_fragmentation_ratio:1.10# used_memory_rss / used_memory# 1.1 正常1.5 高碎片1 可能交换mem_fragmentation_bytes:100000000 mem_allocator:jemalloc-5.2.1# 内存分配器mem_fragmentation_ratio 判断标准 ratio used_memory_rss / used_memory 0.9 ~ 1.1 → 健康碎片率正常 1.1 ~ 1.5 → 可接受有一些碎片 1.5 → 碎片过多考虑重启或设置activedefrag 1 → 内存被swap了紧急情况 操作系统把部分Redis内存换到了磁盘性能会暴跌Stats运行统计127.0.0.1:6379INFO stats# Statstotal_connections_received:500000 total_commands_processed:100000000 instantaneous_ops_per_sec:12500# 当前每秒处理命令数QPStotal_net_input_bytes:5000000000 total_net_output_bytes:8000000000 instantaneous_input_kbps:1024 instantaneous_output_kbps:2048 rejected_connections:0# 被拒绝的连接数超过maxclientssync_full:0 sync_partial_ok:5 sync_partial_err:2# 部分同步失败次数expired_keys:100000# 已过期key数量expired_stale_perc:0.50# 可能已过期但还没回收的比例evicted_keys:0# 因内存不足被淘汰的key数0很危险keyspace_hits:95000000# 缓存命中keyspace_misses:5000000# 缓存未命中pubsub_channels:10 pubsub_patterns:5 latest_fork_usec:1500# 最近一次fork耗时微秒tracking_total_keys:0 tracking_total_items:0 tracking_total_prefixes:0缓存命中率是衡量Redis缓存效果的最重要指标缓存命中率 keyspace_hits / (keyspace_hits keyspace_misses) 95000000 / 100000000 95% 判断标准 99% → 优秀缓存效率极高 95% ~ 99% → 良好 90% ~ 95% → 一般需要分析未命中原因 90% → 差缓存设计有问题或key过期时间太短2.2 关键监控指标及告警阈值指标来源告警阈值说明内存使用率used_memory / maxmemory 80%接近最大内存可能触发淘汰内存碎片率mem_fragmentation_ratio 1.5碎片过多影响内存使用效率连接数connected_clients 80% maxclients连接数接近上限缓存命中率hits/(hitsmisses) 90%缓存效果不佳QPSinstantaneous_ops_per_sec突降50%可能存在阻塞操作被淘汰Keyevicted_keys 0内存不足已有Key被淘汰被拒绝连接rejected_connections 0连接数已达上限RDB最近保存rdb_last_save_time 配置间隔RDB备份滞后AOF最近fsyncaof_last_write_status“err”AOF写入异常fork耗时latest_fork_usec 1000msfork太慢可能阻塞blocked客户端blocked_clients 持续时间有客户端在等待阻塞命令# 一键获取关键指标脚本#!/bin/bashREDIS_CLIredis-cliHOST127.0.0.1PORT6379get_metric(){$REDIS_CLI-h$HOST-p$PORTINFO$1|grep$2|cut-d:-f2}echo Redis 健康检查 echo版本:$(get_metric server redis_version)echo运行天数:$(get_metric server uptime_in_days)echo内存:$(get_metric memory used_memory_human)echo内存峰值:$(get_metric memory used_memory_peak_human)echo碎片率:$(get_metric memory mem_fragmentation_ratio)echo连接数:$(get_metric clients connected_clients)echoQPS:$(get_metric stats instantaneous_ops_per_sec)echo命中数:$(get_metric stats keyspace_hits)echo未命中:$(get_metric stats keyspace_misses)echo淘汰数:$(get_metric stats evicted_keys)echo拒绝连接:$(get_metric stats rejected_connections)三、内存优化Redis性能的核心战场3.1 内存检查清单排查内存问题的步骤 1. 检查内存使用率是否过高 INFO memory → used_memory vs maxmemory 2. 检查碎片率 INFO memory → mem_fragmentation_ratio 1.5 → 需要整理碎片 3. 检查是否有big key 用redis-cli --bigkeys扫描大key 4. 检查是否有大量未清理的过期key INFO stats → expired_stale_perc 5. 检查淘汰策略是否合理 CONFIG GET maxmemory-policy3.2 Big Key问题Big Key是很多Redis性能问题的根源。一个big key可能吃掉大量内存序列化和网络传输都可能造成阻塞。# 扫描big keysredis-cli--bigkeys# 典型输出# Biggest string found so far cache:user:page with 5242880 bytes# Biggest list found so far queue:backup with 1000000 items# Biggest hash found so far user:session with 500000 fields# 配合--memkeys按内存排序redis-cli--bigkeys-i0.1# 每100条命令间隔0.1秒降低服务压力// 处理big key的策略// 问题一个Hash存储了百万级别的字段// key: user:behavior:log → fields: 100万个// 解决方案1拆分// 原来user:behavior:log → 所有用户的行为日志// 改为user:behavior:log:{userId % 1000} → 按用户分1000个HashStringshardKeyuser:behavior:log:(userId%1000);redis.hset(shardKey,timestamp,behavior);// 解决方案2改用合适的数据结构// 如果日志只保留最近N条改用Listredis.lpush(user:behavior:userId,behavior);redis.ltrim(user:behavior:userId,0,999);// 只保留1000条3.3 内存回收与淘汰策略# 查看当前淘汰策略127.0.0.1:6379CONFIG GET maxmemory-policy1)maxmemory-policy2)noeviction# 可选策略带volatile-前缀的只作用于设置了过期时间的key# noeviction: 不淘汰写操作返回错误默认# allkeys-lru: 对所有key使用LRU淘汰# allkeys-lfu: 对所有key使用LFU淘汰Redis 4.0# volatile-lru: 对带过期时间的key使用LRU淘汰# volatile-lfu: 对带过期时间的key使用LFU淘汰# volatile-ttl: 淘汰即将过期的key# volatile-random: 随机淘汰带过期时间的key# allkeys-random: 随机淘汰所有keyLRU vs LFU 选择 LRU最近最少使用 ┌────────────────────────────────────────┐ │ 适合时间局部性强的场景 │ │ 比如最新发布的文章短暂访问量高 │ │ 缺点偶发大量访问但频率正常的key可能被淘汰 │ │ │ │ key1: ████████░░░░░░░░ → 可能被淘汰 │ │ key2: ░░░░░░░░████████ → 最近访问了保留 │ └────────────────────────────────────────┘ LFU最不经常使用 ┌────────────────────────────────────────┐ │ 适合频率敏感的场景 │ │ 比如长期热门商品的缓存 │ │ 缺点访问频率高但有点老的key优势过大 │ │ │ │ key1: ████████████████ → 频率高保留 │ │ key2: ░░░░░░░░██░░░░░ → 频率低淘汰 │ └────────────────────────────────────────┘# 推荐配置# 场景1纯缓存场景CONFIG SET maxmemory-policy allkeys-lru CONFIG SET maxmemory 2gb# 场景2缓存持久化混用CONFIG SET maxmemory-policy volatile-lru# 给需要持久化的key设置过期时间或不设四、连接数与连接池优化4.1 maxclients与系统限制# 查看当前最大连接数127.0.0.1:6379CONFIG GET maxclients1)maxclients2)10000# 运行时修改127.0.0.1:6379CONFIG SET maxclients20000OK⚠️ 注意maxclients的值受操作系统文件描述符限制。在Linux上可以通过ulimit -n 65535和/etc/security/limits.conf设置。Redis实际的最大连接数 配置的maxclients但不能超过文件描述符上限减去32Redis内部保留32个。4.2 连接池配置// Jedis连接池推荐配置BeanpublicJedisPooljedisPool(){JedisPoolConfigconfignewJedisPoolConfig();// 最大连接数根据Redis配置和并发量估算// 公式maxActive 预估并发线程数 × 1.2config.setMaxTotal(200);// 最大连接数config.setMaxIdle(50);// 最大空闲连接数config.setMinIdle(10);// 最小空闲连接数// 连接耗尽时等待策略config.setMaxWait(Duration.ofSeconds(3));// 最多等待3秒// 健康检查config.setTestOnBorrow(true);// 借出时检查增加延迟但保证可用config.setTestOnReturn(false);config.setTestWhileIdle(true);// 空闲时检查// 空闲连接回收config.setMinEvictableIdleDuration(Duration.ofMinutes(5));config.setTimeBetweenEvictionRuns(Duration.ofSeconds(30));returnnewJedisPool(config,127.0.0.1,6379,2000,password);}连接池参数计算 假设 - Web服务器Tomcat线程池200个线程 - 每个请求平均进行3次Redis操作 - 每次操作平均耗时2ms - 目标QPS10000 最大并发Redis操作 10000 QPS × 3 × 0.002s 60 maxActive 60 × 1.5安全倍数 90 ≈ 100 maxIdle maxActive × 0.3 ~ 0.5 ≈ 30~50五、持久化调优5.1 RDB调优# RDB配置参考redis.conf# 触发条件满足任意一个即触发save9001# 900秒内至少1个key变化save30010# 300秒内至少10个key变化save6010000# 60秒内至少10000个key变化# 关键参数stop-writes-on-bgsave-erroryes# RDB失败时拒绝写入生产环境建议yesrdbcompressionyes# 开启LZF压缩CPU换空间rdbchecksumyes# 校验和保证数据完整性dbfilename dump.rdbdir/data/redis⚠️ 注意RDB的save操作bgsave通过fork子进程完成。fork时Redis会短暂阻塞具体取决于内存大小。在内存数十GB的实例上fork可能耗时数百毫秒。可以通过INFO stats中的latest_fork_usec监控。5.2 AOF调优# AOF配置参考appendonlyyesappendfsync everysec# 每秒fsync性能与安全的折中# appendfsync always # 每次写都fsync最安全但最慢# appendfsync no # 由操作系统决定最快但最不安全no-appendfsync-on-rewriteyes# rewrite期间暂停fsync避免IO阻塞auto-aof-rewrite-percentage100# AOF增长100%时触发rewriteauto-aof-rewrite-min-size 64mb# AOF至少64MB才触发rewriteappendfsync选项对比 always ┌─────────────────────────────────────────┐ │ 每次写入都fsync到磁盘 │ │ 安全性★★★★★ 性能★☆☆☆☆ │ │ 适合金融、支付等数据绝不可丢的场景 │ └─────────────────────────────────────────┘ everysec推荐 ┌─────────────────────────────────────────┐ │ 每秒fsync一次 │ │ 安全性★★★★☆ 性能★★★★★ │ │ 最坏丢失1秒数据性能影响极小 │ │ 绝大多数场景的默认选择 │ └─────────────────────────────────────────┘ no ┌─────────────────────────────────────────┐ │ 完全由操作系统决定何时fsync │ │ 安全性★☆☆☆☆ 性能★★★★★ │ │ 仅适合可以接受大量数据丢失的场景 │ └─────────────────────────────────────────┘5.3 Multi-Part AOFRedis 7.0Redis 7.0引入了Multi-Part AOF用多个文件替代单一AOF文件# AOF目录结构Redis 7.0/data/redis/ ├── appendonly.aof.1.base.rdb# 基础RDB快照├── appendonly.aof.1.incr.aof# 增量AOF└── appendonly.aof.manifest# 清单文件# 优势# 1. rewrite时不用fork也无需遍历所有数据# 2. 增量AOF可以更快地写回基础# 3. 内存开销更低六、网络调优与Benchmark6.1 网络参数优化# 操作系统级别网络优化/etc/sysctl.confnet.core.somaxconn65535# 全连接队列大小net.ipv4.tcp_max_syn_backlog65535# 半连接队列大小net.core.netdev_max_backlog65535# 网卡接收队列net.ipv4.tcp_fin_timeout30net.ipv4.tcp_tw_reuse1net.ipv4.tcp_keepalive_time300net.ipv4.tcp_keepalive_intvl30net.ipv4.tcp_keepalive_probes3# Redis配置tcp-backlog511# TCP全连接队列tcp-keepalive300# 保活检测间隔timeout0# 客户端空闲超时0不超时6.2 Redis Benchmark压测# 基础压测测试PING/SET/GET等基础操作redis-benchmark-h127.0.0.1-p6379-c100-n100000-q# 输出示例# PING_INLINE: 100000.00 requests per second# PING_BULK: 100000.00 requests per second# SET: 98039.22 requests per second# GET: 100000.00 requests per second# INCR: 99009.90 requests per second# LPUSH: 98039.22 requests per second# RPUSH: 99009.90 requests per second# LPOP: 99009.90 requests per second# RPOP: 99009.90 requests per second# SADD: 98039.22 requests per second# SPOP: 100000.00 requests per second# LPUSH (needed to benchmark LRANGE): 99009.90 requests per second# LRANGE_100: 41666.67 requests per second# LRANGE_300: 17543.86 requests per second# LRANGE_500: 11834.32 requests per second# LRANGE_600: 10162.60 requests per second# MSET (10 keys): 78125.00 requests per second# 自定义压测# 1000并发连接100万请求只测SET和GET使用Pipeline(16条/batch)redis-benchmark-h127.0.0.1-p6379\-c1000-n1000000\-tset,get\-P16-q# 模拟实际业务负载写入自定义脚本# 测试大valueredis-benchmark-h127.0.0.1-p6379\-tset,get\-d10240\# 10KB的value-c50-n10000-q⚠️ 注意redis-benchmark和Redis运行在同一台机器上时测出来的是理想情况的性能。实际生产环境中还有网络延迟通常0.11ms所以生产QPS通常比benchmark结果低20%50%。七、LATENCY监控与实战案例7.1 延迟监控工具# 1. 实时延迟监控redis-cli--latency-h127.0.0.1-p6379# 持续输出min/avg/max延迟毫秒# 按CtrlC查看统计分布# 2. 历史延迟分析redis-cli --latency-history-h127.0.0.1-p6379# 每15秒输出一次延迟统计# 3. 延迟分布图redis-cli --latency-dist-h127.0.0.1-p6379# 输出类似# . - * # .01 .125 .25 .5 milliseconds# 1,2,3,...,9 from 1 to 9 milliseconds# 4. 延迟诊断Redis 2.8.13127.0.0.1:6379LATENCY DOCTOR# 自动分析延迟事件并给出建议# 5. 查看延迟事件图表127.0.0.1:6379LATENCY GRAPHcommand# 查看命令执行延迟的时间线图# 6. 重置延迟监控127.0.0.1:6379LATENCY RESET7.2 实际调优案例案例Redis QPS突然从50000降到8000 问题现象 - INFO stats: instantaneous_ops_per_sec 8000正常50000 - INFO cpu: used_cpu_sys显著升高 - 延迟avg 15ms正常 1ms - 日志Asynchronous AOF fsync is taking too long 排查过程 1. 查看持久化状态 INFO persistence → aof_delayed_fsync 32768 → AOF fsync延迟严重 2. 查看磁盘IO iostat -x 1 → disk util 100%磁盘在大量写入 3. 分析AOF配置 appendfsync always每次写都fsync 同时有人做了大量SET操作 解决方案 1. 立即将 appendfsync 从 always 改为 everysec 2. 单独挂载高速SSD给AOF文件使用 3. 合并批量的SET操作为MSET减少写次数 效果 改完后QPS恢复到48000延迟回到 1ms案例内存碎片率高导致OOM 问题现象 - used_memory_human: 2.1G - used_memory_rss_human: 3.8G - mem_fragmentation_ratio: 1.81过高 - 实际可用内存4G但RSS占用了3.8G接近上限 原因分析 - 业务大量使用List的LPUSH/LPOP操作 - jemalloc内存碎片回收不充分 - 大量小块内存的分配与释放交替 解决方案 # Redis 4.0 开启主动碎片整理 CONFIG SET activedefrag yes CONFIG SET active-defrag-ignore-bytes 100mb # 碎片超过100MB才整理 CONFIG SET active-defrag-threshold-lower 10 # 碎片率10%开始整理 CONFIG SET active-defrag-threshold-upper 100 # 碎片率超过100%时全力整理 CONFIG SET active-defrag-cycle-min 5 # 最小整理时间占比 CONFIG SET active-defrag-cycle-max 75 # 最大整理时间占比 效果 - 碎片率从1.81降至1.12 - RSS从3.8G降至2.3G - 内存使用率恢复正常八、调优清单速查表优化方向检查命令目标值优化手段内存使用INFO memoryused_memory 80% maxmemory调整maxmemory/淘汰策略/拆分big key碎片率mem_fragmentation_ratio 1.5activedefrag/重启/减少小对象频繁增删缓存命中hits/(hitsmisses) 95%延长TTL/预热缓存/合理设计key连接数connected_clients 80% maxclients连接池复用/调大maxclientsQPSinstantaneous_ops_per_sec稳定无突降Pipeline/批量操作/减少big keyRDB forklatest_fork_usec 1000ms减少Redis内存/用SSDAOF fsyncaof_delayed_fsync0改为everysec/使用SSDBig Keyredis-cli --bigkeys无超过1MB的string拆分/改用其他数据结构慢查询SLOWLOG GET 100无10ms的查询优化命令/拆分复杂操作网络延迟redis-cli --latencyavg 1ms就近部署/减少带宽消耗上一篇【第68篇】HyperLogLog——用极小内存统计超大基数下一篇【第70篇】Redis未来展望——从7.x新特性到Redis Stack的全栈野心