📌 本文由 7 篇相关文章智能合并整理而成
Redis 客户端连接异常处理
Redis 客户端连接异常处理
常见连接异常类型
1. 连接超时
# 错误信息
# redis.exceptions.TimeoutError: Timeout connecting to server
# 或
# Cannot assign requested address (Java: ConnectException)
# 原因
# - Redis 负载过高,无法接受新连接
# - 网络延迟过高
# - 防火墙阻止了连接
# - 连接数超过 maxclients
2. 连接被拒绝
# 错误信息
# redis.exceptions.ConnectionError: Error 111 connecting to host:6379. Connection refused.
# 原因
# - Redis 服务未启动
# - Redis 绑定到错误的 IP/端口
# - 防火墙拦截
# - maxclients 达到上限
3. 连接中断
# 错误信息
# redis.exceptions.ConnectionError: Connection closed by server.
# 原因
# - Redis 超时关闭空闲连接(timeout 配置)
# - Redis 关闭连接(client-output-buffer-limit 触发)
# - 网络断开
# - Redis 重启
4. 读取超时
# 错误信息
# redis.exceptions.TimeoutError: Timeout reading from socket
# 原因
# - 慢查询导致响应延迟
# - 网络延迟波动
# - Redis CPU 过高
# - 返回大量数据
排查步骤
第一步:检查 Redis 服务状态
# 1. 检查 Redis 是否运行
systemctl status redis-server
# Active: active (running) since ...
# 2. 检查 Redis 是否可访问
redis-cli PING
# PONG
# 3. 检查绑定配置
redis-cli CONFIG GET bind
# 1) "bind"
# 2) "0.0.0.0" ← 监听所有接口
# 或 "127.0.0.1" ← 只能本机访问
# 4. 检查保护模式
redis-clI CONFIG GET protected-mode
# "yes" ← 保护模式下有限制
第二步:检查 Redis 统计
# 1. 连接信息
redis-cli INFO Clients
# connected_clients:5000
# client_longest_output_list:0
# client_biggest_input_buf:0
# blocked_clients:0
# 2. 拒绝连接统计
redis-cli INFO Stats | grep rejected_connections
# rejected_connections:100 ← 时间窗口内拒绝了 100 次连接
# 3. 最大连接数
redis-cli CONFIG GET maxclients
# "10000"
第三步:检查客户端连接
# 查看所有客户端连接
redis-cli CLIENT LIST | head -20
# 统计各 IP 连接数
redis-cli CLIENT LIST | awk '{print $2}' | cut -d= -f2 | cut -d: -f1 | sort | uniq -c | sort -nr
第四步:检查应用日志
# Java 应用日志
tail -100 /var/log/app/application.log | grep -i redis
# Redis connection error - Max retries reached
# Python 应用日志
tail -100 /var/log/app/app.log | grep -i redis
# Connection to Redis lost, reconnecting...
常见原因及解决方案
原因 1:连接池耗尽
# Java Jedis 连接池配置检查
# 问题配置
maxTotal: 10 # 池子很小
maxWaitMillis: 100 # 等待时间很短
# 症状
# - 高峰期大量连接超时
# - 应用日志出现 "Could not get a resource from the pool"
# 解决方案
maxTotal: 50 # 根据 QPS 和响应时间计算
maxIdle: 20 # 合适空闲连接数
minIdle: 5 # 保持基本连接
maxWaitMillis: 3000 # 合理的等待时长
testOnBorrow: true # 获取时验证连接有效性
原因 2:Redis 连接空闲超时
# redis.conf
timeout 300 # 默认 300 秒,连接空闲 5 分钟关闭
# 如果应用使用长连接,但长时间没有请求
# Redis 会关闭连接
# 解决方案
# 1. 增大 timeou(或设为 0 禁止超时)
timeout 0
# 2. 应用侧使用心跳(保持活跃)
# Jedis 示例:测试连接是否有效
jedis.ping()
原因 3:输出缓冲区溢出
# redis.conf
client-output-buffer-limit normal 0 0 0 # 普通客户端
client-output-buffer-limit pubsub 32mb 8mb 60 # 发布订阅客户端
client-output-buffer-limit slave 256mb 64mb 60 # 从库客户端
场景:订阅者消费速度跟不上,输出缓冲区满,连接被强制关闭。
# 解决方案
# 1. 增加缓冲区限制
client-output-buffer-limit pubsub 64mb 16mb 120
# 2. 优化消费者逻辑(消费速度 > 生产速度)
# 3. 使用 Stream 替代 Pub/Sub(支持消费者组、ACK)
原因 4:DNS 解析异常
# 症状
# 连接 Redis 时出现 "Name or service not known"
# 原因
# - DNS 记录变更
# - /etc/hosts 配置错误
# - DNS 服务器不可用
# 解决方案
# 1. 使用 IP 地址代替域名
# 2. 设置合理的 DNS 缓存
# 3. 配置 /etc/hosts 静态解析
echo "10.0.0.1 redis-master" >> /etc/hosts
原因 5:TCP 连接未释放
# 症状
# 应用端出现 "Too many open files"
# 检查
lsof -p $(pgrep -f java | head -1) | wc -l
# 5000 ← 文件描述符过多
# 解决方案
# 1. 使用连接池(避免短连接)
# 2. 设置 TCP keepalive
sysctl -w net.ipv4.tcp_keepalive_time=120
sysctl -w net.ipv4.tcp_keepalive_intvl=30
sysctl -w net.ipv4.tcp_keepalive_probes=3
# 3. 增大 ulimit
ulimit -n 65535
客户端最佳实践
Java (Jedis/Lettuce)
// 1. 使用连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(50);
poolConfig.setMaxIdle(20);
poolConfig.setMinIdle(5);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);
poolConfig.setTestWhileIdle(true);
// 2. 设置合理的超时
JedisPool pool = new JedisPool(poolConfig, "localhost", 6379,
2000, // connectionTimeout
2000, // soTimeout
null); // password
// 3. 使用 try-with-resources 确保归还连接
try (Jedis jedis = pool.getResource()) {
jedis.get("key");
}
// 4. 配置重试机制(Lettuce 支持自动重连)
// 5. 监控连接池
Python (redis-py)
# 1. 连接池配置
pool = redis.ConnectionPool(
host='localhost',
port=6379,
db=0,
max_connections=50,
socket_connect_timeout=2,
socket_timeout=2,
retry_on_timeout=True,
health_check_interval=30, # 定期健康检查
)
# 2. 使用连接池
r = redis.Redis(connection_pool=pool)
# 3. 重试装饰器
from retry import retry
@retry(redis.exceptions.RedisError, tries=3, delay=0.1)
def get_redis_data(key):
return r.get(key)
监控与告警
# Prometheus 监控指标
- metrics:
- redis_connected_clients # 连接数
- redis_rejected_connections_total # 拒绝连接数
- redis_uptime_in_seconds # 运行时间
- redis_total_connections_received # 累计连接数
- redis_mem_fragmentation_ratio # 内存碎片
# 告警规则
- alert: RedisConnectionRejected
expr: rate(redis_rejected_connections_total[1m]) > 0
for: 1m
annotations:
summary: "Redis 拒绝连接,可能是连接数或内存已满"
面试要点
- 连接异常三大类:超时、拒绝、中断
- 排查四步走:服务状态 → 连接统计 → 客户端列表 → 应用日志
- 连接池是根因:配置不当是大部分连接异常的源头
- 超时设置:connectTimeout、socketTimeout 必须合理设置
- 重试机制:必须有但不可无限重试(防雪崩)
- 监控指标:connected_clients、rejected_connections
- 资源管理:连接使用完毕及时归还,避免泄漏
Redis OOM command not allowed 错误排查
Redis OOM command not allowed 错误排查
错误现象
redis-cli SET order:10001 "new order"
# (error) OOM command not allowed when used memory > 'maxmemory'.
这是 Redis 内存达到 maxmemory 上限时,禁止写入操作的经典报错。
错误触发条件
# 触发条件:
used_memory >= maxmemory AND maxmemory-policy = noeviction
# 查看当前状态
redis-cli INFO Memory | grep -E "used_memory:|maxmemory:|maxmemory_policy"
# used_memory_human:7.50G
# maxmemory_human:7.50G ← 内存已满
# maxmemory_policy:noeviction ← 不淘汰策略
排查步骤
第一步:确认内存使用情况
# 详细内存信息
redis-cli INFO Memory
# used_memory:8053063680 # 8GB
# used_memory_rss:10066329600 # RSS 10GB
# used_memory_peak:8589934592 # 峰值 8.5GB
# maxmemory:8053063680 # 最大 8GB
# maxmemory_policy:noeviction # 不淘汰
# mem_fragmentation_ratio:1.25 # 碎片率
# 内存使用率 = 8GB / 8GB = 100%
第二步:检查淘汰策略
# 当前淘汰策略
redis-cli CONFIG GET maxmemory-policy
# "noeviction"
# 可选策略对比
# noeviction → 内存满时写入报错(默认,安全但不实用)
# allkeys-lru → 淘汰最近最少使用的 key(缓存场景推荐)
# volatile-lru → 只淘汰有 TTL 的 key(适用于混合存储)
# allkeys-lfu → 淘汰使用频率最低的 key
# volatile-ttl → 淘汰最快过期的 key
# allkeys-random → 随机淘汰
第三步:查找内存消耗来源
# 1. 查看 keyspace 统计
redis-cli INFO Keyspace
# db0:keys=500000,expires=300000,avg_ttl=7200
# 50 万 key,其中 30 万有 TTL
# 2. 查找大 key
redis-cli --bigkeys
# Biggest string found 'session:10001' has 10485761 bytes
# 3. 按前缀统计内存
# 使用 redis-rdb-tools (rdb -c memory) 或开发模式扫描
第四步:检查业务代码
// 常见场景:业务逻辑中大量写入但没有过期时间
public void saveSession(String sessionId, Object data) {
String key = "session:" + sessionId;
// ❌ 没有设置过期时间
redisTemplate.opsForValue().set(key, data);
// ✅ 应该设置过期时间
redisTemplate.opsForValue().set(key, data, 30, TimeUnit.MINUTES);
}
紧急处理
方案 1:临时扩容
# 运行时增大 maxmemory
redis-cli CONFIG SET maxmemory 12G
# 注意:需要有足够的物理内存
free -h
# total=16G, available>12G
方案 2:修改淘汰策略
# 临时改为淘汰策略,允许写入
redis-cli CONFIG SET maxmemory-policy allkeys-lru
# 检查淘汰是否生效
redis-cli INFO Stats | grep evicted_keys
# 注意:
# - 这会淘汰一些 key,可能导致缓存失效
# - 生产环境谨慎操作,评估影响
方案 3:立即清理
# 删除无用的 key
redis-cli DEL temp:batch_001
redis-cli UNLINK temp:batch_002 # 异步删除(推荐)
# 批量清理特定前缀(小心使用)
redis-cli SCAN 0 MATCH "temp:*" COUNT 1000 | while read key; do
redis-cli UNLINK "$key"
done
# 批量设置过期时间
redis-cli SCAN 0 MATCH "session:*" COUNT 1000 | while read key; do
redis-cli EXPIRE "$key" 3600 # 1 小时过期
done
方案 4:在从库执行强制操作
# 如果主库不能写,但在从库可以读
# 检查从库配置
redis-cli -p 6380 CONFIG GET maxmemory
redis-cli -p 6380 CONFIG GET maxmemory-policy
# 从库通常也有内存限制
# 可以考虑从从库读取数据评估影响
长期预防
1. 配置合理的淘汰策略
# redis.conf
# 缓存场景推荐
maxmemory 8G
maxmemory-policy allkeys-lru
maxmemory-samples 10 # LRU 采样数
# 混合场景(部分数据不能丢)
# 设置 volatile-lru,并确保所有 key 都已设置 TTL
maxmemory-policy volatile-lru
2. 监控预警
# 内存预警阈值
# - WARNING: 80% maxmemory
# - CRITICAL: 95% maxmemory
# 可以用 redis_exporter + Prometheus 监控
3. 业务规范
# 业务代码规范
# 1. 所有缓存 key 必须设置过期时间
def set_cache(key, value, ttl_seconds=3600):
redis.set(key, value, ex=ttl_seconds)
# 2. 定期清理垃圾 key
def cleanup_temp_keys():
cursor = 0
while True:
cursor, keys = redis.scan(cursor, match="temp:*", count=100)
if keys:
redis.unlink(*keys)
if cursor == 0:
break
# 3. 使用最大内存限制的本地缓存
from functools import lru_cache
@lru_cache(maxsize=10000)
def get_user_profile(user_id):
return redis.get(f"user:{user_id}")
4. 容量规划
# 增长率监控
# 如果每天增长 500MB
# maxmemory = 8G
# 剩余容量 = 8G - 7.5G = 0.5G
# 可支撑天数 = 0.5G / 0.5GB/天 = 1 天
# 需要 3 天内扩容或优化
与淘汰策略的配合
# 不同策略下的 OOM 行为
# noeviction(默认)
# → 内存满时 IO 写命令全部返回 OOM
# → 读命令正常
# allkeys-lru / volatile-lru
# → 内存满时自动淘汰旧 key
# → 淘汰量突然增大 → 缓存命中率下降
# allkeys-lfu
# → 按照使用频率淘汰
# → 适合数据访问频率差异大的场景
面试要点
- OOM 只影响写:读操作不受影响
- 根本原因:maxmemory 满了 + noeviction 策略
- 紧急处理:扩容 / 改策略 / 清理
- 长期方案:合理设置淘汰策略 + 业务代码规范
- 关键监控指标:内存使用率、淘汰速率、maxmemory
- 与操作系统 OOM 不同:Redis OOM 是写保护,不是进程被 kill
- 碎片坑:RSS 可能比 used_memory 大很多,maxmemory 基于 used_memory
Redis 集群通信超时排查
Redis 集群通信超时排查
集群通信机制
Redis Cluster 中各节点通过 gossip 协议进行通信。每个节点定时向其他节点发送 PING 消息,并等待 PONG 响应。
集群节点 A ── PING ──→ 集群节点 B
└── PONG ←─┘
关键配置
# redis.conf(集群相关)
cluster-enabled yes # 启用集群模式
cluster-node-timeout 15000 # 节点超时时间(15 秒)
cluster-require-full-coverage no # 部分节点故障时继续服务
cluster-migration-barrier 1 # 从库迁移门槛
通信超时的常见原因
原因 1:网络问题
# 检查节点间延迟
redis-cli -h node1 -p 6379 PING
# PONG
# 返回时间 < 1ms → 正常
# 返回时间 > 100ms → 有问题
# 检查节点间连通性
nmap -p 6379,16379 node1 node2 node3
# PORT STATE SERVICE
# 6379/tcp open redis
# 16379/tcp open redis-cluster-bus
集群总线端口:每个节点需要两个端口
– 6379:客户端连接端口
– 16379:集群总线端口(= 客户端端口 + 10000)
原因 2:节点负载过高
# 检查节点负载
redis-cli INFO CPU
# used_cpu_sys:1200.50 # 内核态 CPU 时间
# used_cpu_user:3500.30 # 用户态 CPU 时间
# used_cpu_sys_children:100.20 # 子进程 CPU
# used_cpu_user_children:200.10
# 节点负载过高导致:
# - 无法及时响应集群总线的 PING
# - gossip 消息处理被延迟
# - 其他节点判定该节点超时
原因 3:节点间时钟不同步
# 时钟不同步的影响
# Redis Cluster 使用时间戳判断消息的新旧
# 检查节点时间
date
# 节点 A: Mon May 18 16:00:00 CST 2026
# 节点 B: Mon May 18 15:59:30 CST 2026 ← 慢了 30 秒
# 时间差 > cluster-node-timeout 时,节点可能被误判为故障
# 解决方案:配置 NTP 时间同步
ntpdate -u ntp.aliyun.com
systemctl enable ntpd
原因 4:集群规模过大
# 集群节点数与 gossip 流量关系
N 个节点:每个节点需要维护 N-1 个连接
gossip 消息量 ≈ N²
# 节点数 gossip PING 频率
# 3 低
# 10 中等
# 50 较高
# 100+ 很高,网络开销显著
# 大集群的优化
cluster-node-timeout 30000 # 增大超时
# 或者拆分为多个小集群
原因 5:槽位迁移期间
# 槽位迁移过程中:
# - 源节点和目标节点的数据在同步
# - 这期间节点可能响应变慢
# 查看槽位迁移状态
redis-cli CLUSTER INFO
# cluster_state:ok
# cluster_slots_assigned:16384
# cluster_slots_ok:16384
# cluster_slots_pfail:0
# cluster_slots_fail:0
# cluster_known_nodes:6
# cluster_size:3
# 如果 cluster_slots_pfail > 0,说明部分节点正在迁移
原因 6:节点故障
# 查看节点状态
redis-cli CLUSTER NODES
# 节点 ID IP:端口 角色 状态
# 7e5e8289f1c1cf2b0d08c5dfb6e0c22522f77f88 10.0.0.1:6379@16379 master - 0 1700000000000 0 connected
# 8f6f9390a2d2df3c1d09c6dfb7f1d33633g88e99 10.0.0.2:6379@16379 slave 7e5e8289... 0 1700000000001 3 connected
# 9g7g0401b3e3eg4d2e10c7dfb8g2d44744h99f00 10.0.0.3:6379@16379 master - 0 1700000000002 2 connected
# 状态说明
# connected:正常连
# disconnected:断开
# fail:故障
# handshake:正在握手中
排查步骤
第一步:确认超时范围
# 查看所有节点状态
for node in node1 node2 node3; do
echo "=== $node ==="
redis-cli -h $node CLUSTER INFO | grep -E "cluster_state|cluster_slots"
redis-cli -h $node CLUSTER NODES | awk '{print $1, $2, $3, $8}'
done
第二步:检查节点日志
# 查看 Redis 日志
tail -100 /var/log/redis/redis-server.log | grep -E "timeout|fail|error|cluster"
# 关键日志
# "*** FAIL ***" → 节点被标记为故障
# "Connecting to Node" → 节点重连
# "Unable to connect" → 连接失败
# "hours without contact" → 长时间失联
第三步:网络诊断
# 1. 节点间延迟
mtr -r node1 node2
# 2. 检查集群总线端口
ss -tlnp | grep 16379
# 3. 查看 TCP 连接状态
netstat -ant | grep 16379 | awk '{print $6}' | sort | uniq -c
# 10 ESTABLISHED ← 正常
# 3 TIME_WAIT ← 可能有抖动
第四步:检查资源使用
# CPU 使用率
top -bn1 | grep redis
# 内存使用
redis-cli INFO memory | grep used_memory_human
# 网络带宽
sar -n DEV 1 5 | grep eth0
# 磁盘 I/O(持久化导致)
iostat -x 1 5 | grep sda
解决方案
1. 调整超时配置
# 根据网络环境调整
cluster-node-timeout 30000 # 从 15 秒增大到 30 秒
2. 优化网络
# 同机房部署(推荐)
# 集群节点间的延迟应 < 1ms
# 跨机房部署时
# - 使用专线连接
# - 增大 cluster-node-timeout
3. 均衡负载
# 热点 key 分布不均导致单个节点负载过高
# 查看槽位分布
redis-cli CLUSTER KEYSLOT hot_key
# (integer) 12345 ← 该 key 在 12345 号槽
redis-cli CLUSTER SLOTS | grep "12345"
# 找到该槽位所属节点
# 解决方案
# - 业务上拆分热点 key
# - 使用 hash tag 做数据分片
4. 踢出故障节点
# 如果节点确实无法恢复
# Step 1: 在主库迁移槽位
redis-cli CLUSTER SETSLOT slot IMPORTING node-id
redis-cli CLUSTER SETSLOT slot MIGRATING node-id
redis-cli MIGRATE target-host target-port key 0 5000
# Step 2: 移除故障节点
redis-cli CLUSTER FORGET node-id
# Step 3: 添加新节点
redis-cli CLUSTER MEET new-host new-port
面试要点
- 集群总线端口:客户端端口 + 10000(常被防火墙忽略)
- gossip 协议:节点间通过 PING/PONG 维护状态
- 超时机制:cluster-node-timeout 内无响应 → PFAIL → FAIL
- 时钟同步:NTP 时间同步对集群稳定至关重要
- 大集群问题:节点越多,gossip 流量约大
- 槽位迁移影响:迁移期间节点负载上升
- 防火墙常见坑:漏开放 16379 端口导致集群通信失败
Redis 主从复制断开恢复
Redis 主从复制断开恢复
主从复制断开的常见原因
网络问题
Redis 主库 ──── 网络中断 ──── 从库
↓
复制连接断开
从库进入断线重连状态
- 网络抖动、防火墙中断
- 跨机房专线故障
- 带宽打满导致超时
主库问题
主库
├── BGSAVE 期间响应变慢 → 从库心跳超时
├── 内存满导致写阻塞 → 从库无法同步
├── 主库负载过高 → 处理复制请求延迟
└── 主库宕机 → 从库完全断开
从库问题
从库
├── 从库 BGSAVE/AOF 重写 → 同步延迟
├── 从库内存满 → 无法写入新数据
└── 从库配置问题 → repl-timeout 设置不合理
断开后的行为
从库的重连机制
复制断开
↓
从库进入重连状态(reconnecting)
↓
从库定时尝试重连(repl-timeout / 2 间隔)
↓
连接恢复?
├── Yes → 检查复制积压缓冲区
│ ├── 数据还在积压缓冲区 → 部分同步(PSYNC)
│ └── 数据已过期 → 全量同步(FULL RESYNC)
└── No → 继续重试
查看复制状态
# 检查复制状态
redis-cli INFO replication
# role:slave
# master_host:10.0.0.1
# master_port:6379
# master_link_status:down ← 关键指标
# master_last_io_seconds_ago:120 ← 上次通信时间
# master_sync_in_progress:0
# slave_read_repl_offset:100000
# slave_repl_offset:100000
# master_link_down_since_seconds:3600 ← 断开已 1 小时
# 主库侧查看
redis-clI INFO replication
# role:master
# connected_slaves:2
# slave0:ip=10.0.0.2,port=6380,state=online,offset=100000,lag=0
# slave1:ip=10.0.0.3,port=6381,state=offline ← 从库离线
恢复方式
自动恢复
# 网络恢复后,从库会自动重连
# 大部分情况下不需要人工干预
# 监控重连状态
watch -n 5 "redis-cli INFO replication | grep -E 'master_link_status|master_last_io'"
手动强制重连
# 如果自动重连失败,手动触发
redis-cli SLAVEOF NO ONE # 断开当前主库
redis-cli SLAVEOF master-ip 6379 # 重新建立主从关系
全量同步触发
# 如果复制积压缓冲区中的数据已过期,会触发全量同步
# 全量同步过程:
# 1. 主库 BGSAVE 生成 RDB
# 2. 传输 RDB 到从库
# 3. 从库加载 RDB
# 4. 从库追赶增量数据
# 触发全量同步(清空从库数据)
redis-cli SLAVEOF NO ONE
redis-cli FLUSHALL
redis-cli SLAVEOF master-ip 6379
复制积压缓冲区(repl-backlog)
什么是积压缓冲区
复制积压缓冲区是主库维护的一个环形缓冲区,存最近N字节的写命令。
主库 repl-backlog 缓冲区(环形)
┌─────────────────────────────────┐
│ offset=100 offset=200 offset=300 │
│ [SET key1 v1][SET key2 v2][...] │
└─────────────────────────────────┘
↑
从库当前读取位置
缓冲区配置
# redis.conf
repl-backlog-size 1mb # 缓冲区大小,默认 1MB
repl-backlog-ttl 3600 # 没有从库时保留 N 秒后释放
缓冲区大小计算
# 估算需要的缓冲区大小
# 高峰期复制速率:10MB/s
# 最大容忍中断时间:60 秒
# 所需缓冲区 = 10MB/s × 60s = 600MB
# 配置建议
repl-backlog-size 512mb # 给个余量
缓冲区不足的后果
# 当从库的断线时间超过了缓冲区能容纳的数据量
# 自动转为全量同步(FULL RESYNC)
# 从库日志
# "MASTER <-> SLAVE sync: Starting partial resynchronization"
# "MASTER <-> SLAVE sync: Full resync requested"
# 全量同步的代价:
# - 主库需要 BGSAVE(大内存时很慢)
# - 传输 RDB 文件(大带宽消耗)
# - 从库加载 RDB(期间不可服务)
优化复制稳定性
1. 增大超时设置
# redis.conf
repl-timeout 120 # 默认 60 秒,网络差时增大
repl-ping-slave-period 10 # 心跳间隔,默认 10 秒
2. 增大积压缓冲区
# redis.conf
repl-backlog-size 512mb # 增大缓冲区减少全量同步
repl-backlog-ttl 7200 # 从库断开后保持缓冲区更久
3. 磁盘不阻塞复制
# redis.conf
repl-diskless-sync yes # 无盘同步(直接 socket 传输)
repl-diskless-sync-delay 5 # 延迟 5 秒等待更多从库
4. 从库只读配置
# redis.conf
replica-read-only yes # 从库只读,避免写入不一致
replica-serve-stale-data no # 复制中断时不返回旧数据
故障恢复场景实战
场景 1:网络抖动导致断开
# 现象:master_link_status:down
# 原因:短暂网络抖动
# 恢复:自动恢复,几分钟内自动重连
# 检查
redis-cli INFO replication | grep master_link_status
# 几分钟后变为 master_link_status:up
场景 2:从库宕机重启
# Step 1: 从库启动后,自动向主库发送 PSYNC 请求
# Step 2: 主库检查 repl-backlog
# - 数据在缓冲区内 → 部分同步(快)
# - 数据不在缓冲区内 → 全量同步(慢)
# 从库日志
# Connecting to MASTER 10.0.0.1:6379
# MASTER <-> SLAVE sync started
# Non blocking connect for SYNC fired the event.
# Full resync from master: 7e5e8289f1c1cf2b0d08c5dfb6e0c22522f77f88
场景 3:主库宕机
# 从库断开,等待新主库
# 如果有哨兵:哨兵自动切换,从库自动重新指向新主库
# 无哨兵:手动将某个从库提升为主库
# 手动提升
redis-cli -p 6380 SLAVEOF NO ONE # 从库 6380 变为主库
# 其他从库重新指向新主库
redis-cli -p 6381 SLAVEOF 10.0.0.2 6380
监控指标
# 关键监控指标
redis-cli INFO replication | grep -E \
"master_link_status|master_last_io|slave_repl_offset|master_repl_offset"
# 复制延迟 = master_repl_offset - slave_repl_offset
# 延迟 > repl-backlog-size 时必然触发全量同步
# 全量同步次数监控(过高说明配置有问题)
redis-cli INFO Stats | grep sync_full
面试要点
- 断开自动重连:从库有自动重连机制
- PSYNC vs 全量同步:积压缓冲区决定是部分还是全量同步
- repl-backlog 关键:大小决定能容忍多长的断开时间
- 全量同步代价:占用主库 CPU、带宽、磁盘,要尽量避免
- repl-timeout 配置:跨机房场景需要增大超时
- 监控指标:master_link_status、复制延迟、全量同步次数
- 恢复策略:自动重连 ➝ 手动重连 ➝ SLAVEOF NO ONE 提升
Redis 哨兵频繁切换主节点排查
Redis 哨兵频繁切换主节点排查
问题现象
哨兵频繁切换主节点(也称哨兵漂移或主从颠簸),表现为:
现象 1:短时间内主节点多次变化
现象 2:应用频繁断连,需要不断重连到新主库
现象 3:哨兵日志显示多次 failover
排查步骤
第一步:查看哨兵日志
# 查看哨兵日志
tail -100 /var/log/redis/sentinel.log
# 关注关键日志
# +sdown:主观下线(哨兵认为主库挂了)
# +odown:客观下线(多个哨兵确认主库挂了)
# +try-failover:开始故障转移
# +failover-end:故障转移结束
# +switch-master:切换主库
# -sdown:主观下线恢复
第二步:检查故障原因
# 查看哨兵配置
redis-cli -p 26379 SENTINEL MASTER mymaster
# 1) "name"
# 2) "mymaster"
# 3) "ip"
# 4) "10.0.0.1"
# 5) "port"
# 6) "6379"
# 7) "quorum"
# 8) "2" ← 判定客观下线的票数要求
# 9) "failover-timeout"
# 10) "180000" ← 超时时间 3 分钟
# 11) "down-after-milliseconds"
# 12) "30000" ← 主观下线判定时间
常见原因
原因 1:哨兵配置过于敏感
# 问题配置
down-after-milliseconds: 1000 # 1 秒没响应就认为下线(太敏感!)
quorum: 1 # 1 个哨兵就判定客观下线(太容易触发)
# 推荐配置
down-after-milliseconds: 30000 # 30 秒(需要 3 个心跳周期)
quorum: 2 # 至少 2 个哨兵同意
原因 2:网络抖动
主库 → 哨兵 A:心跳正常
主库 → 哨兵 B:心跳超时(网络抖动)
哨兵 B 向群组宣布:"主库挂了"
→ 如果 quorum=1,直接触发切换!
原因 3:CPU 毛刺
# 主库 CPU 瞬间飙高导致心跳超时
# 但 1 秒后恢复,哨兵却已经触发了切换
# 检查 CPU 峰值
sar -u -f /var/log/sysstat/saXX | grep -E "max|avg"
原因 4:GC 暂停(Java 应用 + 共享主机)
JVM Full GC 暂停了 10 秒
→ 应用无法访问主库 → 但实际上 Redis 正常
→ 哨兵监控的是 "可连接性" 而不是 GC 暂停
原因 5:哨兵节点分布不合理
❌ 错误:3 个哨兵全部在同一个机房
↓
该机房网络故障 → 所有哨兵同时失联 → 集体触发切换
↓
另一个机房以为主库挂了 → 切换成功
↓
原机房恢复 → 又发现"主库"不在原位置 → 再次切换
✅ 正确:哨兵分布在 3 个不同机房/可用区
解决方案
1. 调整哨兵配置
# sentinel.conf 优化配置
# 延长下线判定时间
sentinel down-after-milliseconds mymaster 30000
# 合理设置:30 秒 = 3 × 哨兵检查周期
# 增加 quorum
sentinel quorum mymaster 2
# 至少 2 个哨兵同意才能判定客观下线
# 设置故障转移超时
sentinel failover-timeout mymaster 180000
# 3 分钟:防止短时间内重复切换
2. 部署策略优化
# 节点部署建议
# 3 个哨兵分布在 3 个不同的可用区
哨兵 A: 可用区 1
哨兵 B: 可用区 2
哨兵 C: 可用区 3
# 主从节点
主库: 可用区 1
从库 A: 可用区 2
从库 B: 可用区 3
3. 监控告警调优
# 避免误告警
# ❌ 太敏感:CPU 超过 50% 就告警
# ✅ 合理:CPU 持续 90% 超过 5 分钟
# ❌ 太敏感:PING 延迟 10ms 就告警
# ✅ 合理:PING 失败超过 30 秒
4. 升级到 Redis Cluster(推荐长期方案)
# Redis Cluster 的分片机制
# - 单个节点故障不影响全局
# - 自动 failover 比哨兵更稳定
# - 无哨兵组件,减少故障点
# 迁移方式:
# redis-trib.rb 在线迁移
# 或使用 Redis-Shake 逐步迁移
预防措施
配置检查列表
# 哨兵配置检查清单
✅ down-after-milliseconds: 30000 # 不能太短
✅ quorum: (N/2 + 1) # 超过半数的哨兵同意
✅ failover-timeout: 180000 # 切换超时
✅ sentinel runid 唯一 # 每个哨兵唯一 ID
✅ 哨兵数量 >= 3 # 奇数部署
✅ 哨兵分布在不同故障域 # 避免单点
✅ sentinel notification-script 配置 # 切换通知
监控哨兵事件
import redis
def monitor_sentinel_events():
sentinel = redis.Sentinel([('localhost', 26379)])
master = sentinel.discover_master('mymaster')
previous_master = master
while True:
current_master = sentinel.discover_master('mymaster')
if current_master != previous_master:
print(f"ALERT: 主节点切换 {previous_master} → {current_master}")
previous_master = current_master
time.sleep(10)
面试要点
- 频繁切换 = 哨兵太敏感:down-after-milliseconds 设置不当是主因
- 网络抖动诱发:短暂的网络延迟不应该触发切换
- quorum 配置原则:quorum = N/2 + 1(超过半数)
- 奇数哨兵节点:3 个哨兵容忍 1 个故障,5 个容忍 2 个
- 跨可用区部署:避免单机房故障集体失联
- failover-timeout:足够长的超时防止震荡切换
- 长期方案:考虑迁移到 Redis Cluster
Redis 内存用满的表现及处理
Redis 内存用满的表现及处理
内存用满的典型场景
Redis 作为内存数据库,当内存使用达到配置的 maxmemory 上限时,会触发特定的行为。生产环境中,内存用满是最常见的故障之一。
内存满时的表现
1. 写入操作失败
# 表现:写命令返回 OOM 错误
redis-cli SET key value
# (error) OOM command not allowed when used memory > 'maxmemory'.
# 但读操作不受影响
redis-cli GET existing_key
# "value" ← 读操作正常
2. 淘汰策略触发
# 如果配置了淘汰策略,Redis 会自动删除 key 释放内存
# 这可能导致缓存命中率下降
# 检查当前淘汰策略
redis-cli CONFIG GET maxmemory-policy
# "allkeys-lru" ← 从所有 key 中淘汰最近最少使用的
# 查看淘汰统计
redis-cli INFO Stats | grep evicted_keys
# evicted_keys:150000 ← 已淘汰 15 万个 key
3. 性能下降
# 淘汰策略执行本身消耗 CPU
# 特别是在 volatile-ttl 策略下,需要遍历 key 检查过期时间
# 大量淘汰导致:
# - 请求延迟增加
# - 缓存命中率急剧下降
# - 数据库负载飙升(缓存失效后请求打到数据库)
4. 内存碎片化
# 内存满 + 频繁淘汰 = 内存碎片
redis-cli INFO Memory | grep mem_fragmentation_ratio
# mem_fragmentation_ratio:2.5 ← 严重碎片化
# 实际使用 4GB,但 RSS 占用了 10GB
5. 触发 Swap
# 系统内存不足时,部分 Redis 数据被换到磁盘
# 检查 swap 使用
redis-cli INFO Memory | grep used_memory
# used_memory_human:10.00G ← 声称用了 10G
redis-cli INFO Memory | grep maxmemory
# maxmemory_human:8.00G ← 只配了 8G
# 系统层面检查
cat /proc/$(pgrep -f redis-server)/status | grep VmSwap
# VmSwap: 2097152 kB ← 使用了 2GB swap!
# swap 导致性能断崖式下降(访问时间从微妙级变成毫秒级)
监控与预警
关键监控指标
# 1. 内存使用率
redis-cli INFO Memory | grep -E "used_memory|maxmemory|mem_fragmentation"
# used_memory_human:7.50G # 当前使用
# used_memory_rss_human:10G # 实际 RSS 内存
# maxmemory_human:8.00G # 最大限制
# mem_fragmentation_ratio:1.33 # 碎片率
# 使用率 = 7.5G / 8G = 93.75%
# 2. 淘汰统计
redis-cli INFO Stats | grep evicted_keys
# evicted_keys:50000
# 3. 过期 key 统计
redis-cli INFO Stats | grep expired_keys
# expired_keys:100000
告警阈值
# Prometheus 告警规则
groups:
- name: redis_memory
rules:
- alert: RedisMemoryHigh
expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.8
for: 5m
annotations:
summary: "Redis 内存使用超过 80%"
- alert: RedisMemoryCritical
expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.95
for: 1m
annotations:
summary: "Redis 内存使用超过 95%,即将 OOM"
- alert: RedisEvictionsHigh
expr: rate(redis_evicted_keys_total[5m]) > 100
annotations:
summary: "Redis 淘汰速率超过 100/s"
处理步骤
第一步:紧急扩容
# 方案 A:修改 maxmemory(运行时生效)
redis-cli CONFIG SET maxmemory 12G
# 方案 B:扩容物理内存 + 重启(需要停机)
# 1. 增大云主机规格
# 2. 启动 Redis 时指定更大的 maxmemory
# 3. 从旧节点迁移数据
第二步:清理非必要数据
# 1. 查找并删除大 Key
redis-cli --bigkeys
# 2. 清理特定模式的 key
redis-cli SCAN 0 MATCH "temp:*" COUNT 1000 | while read key; do
redis-cli UNLINK "$key" # 异步删除
done
# 3. 清理 TTL 过长的 key
redis-cli SCAN 0 | while read key; do
ttl=$(redis-cli TTL "$key")
if [ "$ttl" = "-1" ]; then # 没有过期时间
echo "Key $key has no TTL, consider adding one"
fi
done
第三步:优化淘汰策略
# 查看当前策略
redis-cli CONFIG GET maxmemory-policy
# 可能的值:
# noeviction - 不淘汰,写入返回 OOM(默认)
# allkeys-lru - 所有 key,LRU 淘汰(推荐)
# volatile-lru - 有 TTL 的 key,LRU 淘汰
# allkeys-random - 全部 key,随机淘汰
# volatile-ttl - 有 TTL 的 key,快过期的淘汰
# allkeys-lfu - 所有 key,LFU 淘汰(Redis 4.0+)
# volatile-lfu - 有 TTL 的 key,LFU 淘汰
# 生产推荐:allkeys-lru 或 allkeys-lfu
redis-cli CONFIG SET maxmemory-policy allkeys-lru
第四步:分析内存使用
# 按数据类型统计内存
redis-cli INFO Keyspace
# db0:keys=500000,expires=300000,avg_ttl=7200
# 使用 redis-rdb-tools 分析 RDB 文件
rdb -c memory /var/lib/redis/dump.rdb --bytes 100000
# database,type,key,size_in_bytes,encoding,num_elements,len_largest_element
# db0,string,session:10001,10485761,string,10485761,10485761
# db0,hash,user:info,5242880,hashtable,10000,100
预防措施
1. 合理设置 maxmemory
# redis.conf
maxmemory 10G # 设置内存上限
maxmemory-policy allkeys-lru # 淘汰策略
maxmemory-samples 10 # LRU 抽样数(越大越精确)
2. 设置合理的 TTL
// 业务代码中设置合理过期时间
// 缓存场景:过期时间与业务容忍度相关
redisTemplate.opsForValue().set(
"user:profile:" + userId,
userProfile,
1, TimeUnit.HOURS // 1小时过期
);
// 会话场景:根据业务设置
redisTemplate.opsForValue().set(
"session:" + sessionId,
sessionData,
30, TimeUnit.MINUTES // 30分钟过期
);
3. 定期巡检
#!/bin/bash
# 每日内存巡检脚本
MAXMEM=$(redis-cli CONFIG GET maxmemory | tail -1)
USEDMEM=$(redis-cli INFO Memory | grep used_memory_human | cut -d: -f2 | sed 's/M//')
RATIO=$((USEDMEM * 100 / MAXMEM))
echo "最大内存: ${MAXMEM}MB"
echo "已用内存: ${USEDMEM}MB"
echo "使用率: ${RATIO}%"
if [ $RATIO -gt 80 ]; then
echo "WARNING: 内存使用超过 80%"
fi
if [ $RATIO -gt 95 ]; then
echo "CRITICAL: 内存使用超过 95%"
fi
# 检查淘汰速率
EVICTED=$(redis-cli INFO Stats | grep evicted_keys | cut -d: -f2)
echo "已淘汰 key 数: ${EVICTED}"
4. 容量规划
# 内存增长的预估
# 当前增长率 ≈ 100MB/天
# 当前 maxmemory = 8GB
# 当前使用 = 6GB
# 剩余天数 = (8G - 6G) / (100MB/天) = 20 天
# 建议:15 天内完成扩容或优化
面试要点
- OOM 写保护:内存满时,写操作报错,读操作正常
- 淘汰策略:默认 noeviction(写失败),推荐 allkeys-lru
- Swap 是性能杀手:内存不足触发 swap 导致性能断崖
- 碎片率:mem_fragmentation_ratio > 1.5 需要考虑优化
- 淘汰 ≠ 释放内存:淘汰 key 后碎片还在,RSS 不一定会减少
- 扩容不能掉链子:提前规划,设置 80% 告警、95% 紧急
- 监控是关键:内存使用率、淘汰速率、碎片率都要监控
Redis 响应变慢原因排查
Redis 响应变慢原因排查
响应慢的定位方法
Redis 响应变慢是一个典型的面试和运维高频问题。排查需要从多个维度入手。
快速诊断命令
# 第一梯队:查看 Redis 自身统计
redis-cli INFO Commandstats
redis-cli INFO Stats
redis-cli SLOWLOG GET 10
# 第二梯队:系统层面
top -p $(pgrep -f redis-server)
iostat -x 1
# 第三梯队:网络
netstat -ant | grep 6379 | wc -l
ping redis-host
常见原因及排查
原因 1:慢查询(慢命令)
# 查看慢查询
redis-cli SLOWLOG GET 10
# 1) 1) (integer) 1 # ID
# 2) (integer) 1700000000 # 时间戳
# 3) (integer) 50000 # 耗时(微秒)= 50ms
# 4) 1) "KEYS" # 命令
# 2) "user:*"
# 查看慢查询阈值
redis-cli CONFIG GET slowlog-log-slower-than
# "10000" # 默认 10 毫秒
# 优化:避免 O(N) 命令
# ❌ KEYS user:* → O(N) 扫描所有 key
# ✅ SCAN 0 MATCH user:* → 游标分批扫描
# ❌ SMEMBERS big_set → 大集合全部返回
# ✅ SSCAN big_set 0 → 分批扫描
# ❌ DEL big_key → 删除大 key 阻塞
# ✅ UNLINK big_key → 异步删除
原因 2:大 Key 问题
# 查找大 Key
redis-cli --bigkeys
# Biggest string found 'session:10001' has 10485761 bytes
# Biggest list found 'log:2024' has 1000000 items
# 大 key 的影响:
# 1. GET/SET 延迟高——传输大量数据
# 2. DEL/EXPIRE 阻塞——操作大 key 耗时
# 3. 内存碎片——频繁变更大数据
# 4. 网络带宽——每次操作都传输大量数据
# 解决方案:
# 拆分大 key:hash → 多个分片字段
# 压缩存储:String 数据使用压缩
# 限制大小:业务上控制单个 value 不超过 10KB
原因 3:CPU 瓶颈
# Redis 是单线程模型,CPU 是核心瓶颈
# 检查 CPU 使用率
top -p $(pgrep -f redis-server)
# %CPU 150% ← Redis 超过 100% 说明有持久化子进程
# CPU 过高时的表现
# - 请求响应时间普遍上升
# - 所有命令都变慢(不是特定命令)
# - 网络吞吐量达到极限
# 解决方案:
# - 拆分 Redis 实例(使用多实例 + 集群)
# - 改用 Redis Cluster 分摊负载
# - 业务上减少不必要的命令调用
原因 4:内存问题
内存碎片
# 查看内存碎片率
redis-cli INFO Memory | grep mem_fragmentation_ratio
# mem_fragmentation_ratio:1.5 ← 超过 1.5 说明碎片严重
# 碎片率指标
# < 1.0:swap 使用了虚拟内存
# 1.0-1.5:正常范围
# > 1.5:碎片严重,需要整理
# 解决方案
# - 重启 Redis(内存清零重新加载)
# - 使用 MEMORY PURGE 命令(整理碎片)
# - 调整 jemalloc 配置
内存达到 maxmemory
# 检查是否达到内存上限
redis-cli INFO Memory | grep -E "used_memory|maxmemory"
# used_memory_human:8.00G
# maxmemory_human:8.00G ← 已用满!
# 内存满时
# - 写操作可能失败或触发淘汰策略
# - 淘汰策略执行时可能阻塞
# - 读取大 key 可能触发 swap
# 解决方案
# 增大 maxmemory
# 检查淘汰策略配置
redis-cli CONFIG GET maxmemory-policy
原因 5:网络问题
# 网络延迟检查
# 从应用服务器 ping Redis 服务器
ping redis-server
# 64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.5 ms ← 同机房
# 64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=50 ms ← 跨机房
# 网络带宽监控
sar -n DEV 1
# 检查网络吞吐是否接近带宽上限
# TCP 连接问题
netstat -s | grep -E "retransmit|timeout"
# 如果重传率高,说明网络不稳定
# 解决方案
# - 同机房部署应用和 Redis
# - 使用连接池复用连接
# - 使用 Pipeline 减少网络往返
原因 6:持久化导致的延迟
# Fork 延迟
redis-cli INFO Persistence | grep latest_fork_usec
# latest_fork_usec:500000 ← fork 耗时 500ms
# AOF fsync 延迟
# 如果 appendfsync=always,每次写入都 fsync
# 磁盘 I/O 慢会导致写入变慢
# BGSAVE/AOF 重写期间
# - 子进程写 RDB 占用磁盘 I/O
# - COW 机制导致缺页中断
# - 父进程间因内存页争用而变慢
# 解决方案
# - 从库执行持久化
# - appendfsync 设为 everysec
# - 使用 SSD 替代 HDD
# - 将 AOF 放在不同磁盘
原因 7:连接数过多
# 检查连接数
redis-cli INFO Clients
# connected_clients:5000
# 连接过多导致的问题
# - CPU 上下文切换频繁
# - 内核维护大量 TCP 连接的开销
# - Redis 事件循环处理大量 I/O
# 解决方案
# - 合理设置连接池大小
# - 使用连接池(不要短连接)
# - 考虑使用 Redis 代理(Twemproxy/Redis Proxy)
系统级排查
检查慢查询日志
# 配置慢查询
redis-cli CONFIG SET slowlog-log-slower-than 5000 # 记录 >5ms 的查询
redis-cli CONFIG SET slowlog-max-len 1000 # 保留 1000 条
# 分析慢查询
SLOWLOG GET | python3 -c "
import sys, json
for line in sys.stdin:
data = json.loads(line.strip())
print(f'耗时: {data[\"duration\"]/1000:.1f}ms | 命令: {\" \".join(data[\"args\"])}')"
性能基线
# 用 redis-benchmark 测试性能基线
redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 10000
# ====== SET ======
# 100000 requests completed in 0.86 seconds
# 116279.06 requests per second
# 对比变慢后的性能
redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 10000
# ====== SET ======
# 100000 requests completed in 2.50 seconds
# 40000.00 requests per second ← 性能下降 65%
排查流程总结
症状:Redis 响应变慢
↓
第一步:Info 统计检查
├── CPU 高?→ 是否持久化子进程?→ 从库备份
├── 内存满?→ maxmemory 策略?→ 扩容/优化
└── 连接多?→ 空闲连接?→ 清理/池化
↓
第二步:Slowlog 检查
├── 有慢查询?→ KEYS/DEL 大 key → 优化命令
└── 无慢查询?→ 检查网络/系统
↓
第三步:系统层面
├── 网络延迟?→ ping、带宽
├── 磁盘 I/O?→ iostat
└── 系统负载?→ top、vmstat
↓
第四步:大 Key 检查
└── redis-cli --bigkeys
面试要点
- 慢查询是排查入口:先看 SLOWLOG
- 单线程模型:CPU 瓶颈常见的瓶颈
- 大 Key 是万恶之源:导致慢查询、阻塞、内存碎片
- 网络不可忽略:跨机房部署时网络延迟可能是主因
- 持久化影响:AOF fsync、BGSAVE fork、子进程 CPU 消耗
- 内存碎片:mem_fragmentation_ratio > 1.5 需要处理
- 性能基线:提前做好基线,变慢时才有对比依据
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容