*Redis 监控与告警方案:生产环境可观测性实战指南
本文深入讲解 Redis 生产环境监控的核心维度、底层原理与可观测性架构,涵盖 Prometheus + Grafana + Redis Exporter 的完整部署方案,提供 Shell、Python、Java 多语言监控采集代码,以及从延迟告警到自动故障自愈的实战案例。
*目录
- 一、为什么 Redis 需要专业化监控
- 二、Redis 监控的六大核心维度
- 三、监控原理与关键指标解析
- 四、可观测性架构设计
- 五、核心命令与指标采集
- 六、实战:Prometheus + Grafana 监控体系搭建
- 七、多语言客户端监控采集
- 八、故障排查与告警规则
- 九、FAQ 常见问题
- 十、总结与下一步
*一、为什么 Redis 需要专业化监控
Redis 作为内存数据库,性能极高但容错窗口极短。一次内存耗尽、一次主从切换失败、一次慢查询堆积,都可能在秒级导致服务雪崩。不同于磁盘型数据库有较多缓冲时间,Redis 的问题往往在业务可感知之前就已恶化。
生产环境中,仅靠 redis-cli ping 判断存活是远远不够的。你需要知道:
- 内存使用率是否接近上限,驱逐策略是否已触发?
- 连接数是否达到瓶颈,是否存在连接泄露?
- 哪些命令执行缓慢,是否拖垮了主线程?
- 主从复制延迟多少,是否已影响读写一致性?
- 持久化(RDB/AOF)是否阻塞了正常请求?
专业化监控的目标不是"看图表",而是提前发现趋势、快速定位根因、自动触发止损。本文将从监控维度、底层原理、采集方法、可视化方案到告警规则,给出可直接落地的完整方案。
*二、Redis 监控的六大核心维度
| 维度 | 关键指标 | 风险等级 | 说明 |
|---|---|---|---|
| 内存 | used_memory, used_memory_rss, maxmemory |
🔴 高 | 内存耗尽直接导致 OOM 或数据丢失 |
| 连接 | connected_clients, rejected_connections |
🟡 中 | 连接泄露会耗尽文件描述符 |
| 命令 | instantaneous_ops_per_sec, slowlog |
🟡 中 | 慢查询和热点命令影响吞吐量 |
| 延迟 | latency, latest_fork_usec |
🔴 高 | 延迟是 Redis 最核心的 SLA 指标 |
| 持久化 | rdb_last_bgsave_status, aof_last_write_status |
🟡 中 | 持久化失败意味着数据风险 |
| 复制 | master_link_status, master_last_io_seconds_ago |
🔴 高 | 主从断开导致读写不一致 |
监控不是越多越好,而是围绕业务 SLA 选择关键指标。对于缓存场景,延迟和命中率是核心;对于消息队列,持久化和复制延迟更重要。
*三、监控原理与关键指标解析
*3.1 INFO 命令:Redis 的监控数据总线
Redis 的 INFO 命令是所有监控体系的基石。它输出数十个模块的指标,按段分类:
INFO [section]
常用段落:
| 段落 | 监控内容 | 典型用途 |
|---|---|---|
server |
版本、运行时间、监听端口 | 版本管理和巡检 |
clients |
连接数、最大输出缓冲、阻塞客户端 | 连接泄露检测 |
memory |
内存使用、RSS、碎片率、驱逐统计 | 内存预警和优化 |
stats |
总命令数、QPS、网络流量、驱逐数 | 容量规划和热点分析 |
replication |
主从状态、复制偏移、延迟 | 高可用监控 |
persistence |
RDB/AOF 状态、最后一次持久化结果 | 数据安全审计 |
commandstats |
各命令的调用次数和耗时 | 慢命令定位 |
cpu |
主线程和子线程 CPU 耗时 | 资源消耗分析 |
keyspace |
各 DB 的 key 数量、过期 key、平均 TTL | 数据分布和过期策略 |
*3.2 内存监控:从 used_memory 到 RSS 的真相
used_memory 是 Redis 逻辑使用的内存(含对象开销、缓冲区等),used_memory_rss 是操作系统实际分配的物理内存。两者的差异反映了内存碎片和外部开销(如客户端输出缓冲)。
关键指标:
used_memory/maxmemory:内存使用率,超过 80% 需预警used_memory_rss/used_memory:比值 > 1.5 说明碎片严重,需考虑 MEMORY PURGE 或重启mem_fragmentation_ratio:官方推荐 1.0 ~ 1.5,超过 2.0 需关注evicted_keys:被驱逐的 key 数量,持续非零说明内存不足
*3.3 慢查询监控:SLOWLOG 与 LATENCY DOCTOR
Redis 是单线程模型,任何慢命令都会阻塞后续请求。SLOWLOG 记录超过阈值的命令:
CONFIG SET slowlog-log-slower-than 10000 # 10ms
CONFIG SET slowlog-max-len 128
Redis 2.8.13+ 提供了 LATENCY DOCTOR,自动诊断延迟异常:
LATENCY DOCTOR
它会分析 LATENCY LATEST 中的事件(如 fork, aof-write, rdb-save),并给出人类可读的建议。
*3.4 复制延迟与主从一致性
主从架构下,复制延迟是读写分离场景的关键指标:
master_repl_offsetvsslave_repl_offset:偏移差反映数据同步进度master_last_io_seconds_ago:从库多久没收到主库数据,超过 10 秒需告警master_link_status:up或down,直接反映复制链路健康
*四、可观测性架构设计
*4.1 整体架构图
┌─────────────────────────────────────────────────────────────────────┐
│ Redis 监控可观测性架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Redis │ │ Redis │ │ Redis │ │ Redis │ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │ Sentinel │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ │ INFO / │ INFO / │ INFO / │ INFO / │
│ │ SLOWLOG │ SLOWLOG │ SLOWLOG │ SLOWLOG │
│ └───────┬───────┴───────┬───────┴───────┬───────┴───────┘ │
│ │ │ │ │
│ ┌───────▼───────────────▼───────────────▼───────┐ │
│ │ Redis Exporter (oliver006/redis_exporter) │ │
│ │ - 端口: 9121 │ │
│ │ - 采集周期: 15s │ │
│ └───────┬───────────────────────────────────────────┘ │
│ │ │
│ │ Prometheus 拉取 (scrape) │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Prometheus Server │ │
│ │ - 存储: 15天本地 TSDB │ │
│ │ - 告警规则: 10+ 条 │ │
│ │ - 预聚合: recording rules │ │
│ └──────────┬──────────────────┘ │
│ │ │
│ ┌──────────┴──────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ Grafana │ │ Alertmanager │ │
│ │ 可视化 │ │ 告警路由 │ │
│ │ 仪表盘 │ │ 静默/抑制 │ │
│ └──────────┘ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ 钉钉/飞书/邮件/ │ │
│ │ PagerDuty/Slack │ │
│ └────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
*4.2 组件选型说明
| 组件 | 选型 | 理由 |
|---|---|---|
| 指标采集 | Redis Exporter | 社区最成熟,覆盖 200+ 指标,支持 Sentinel |
| 时序存储 | Prometheus | 云原生标准,Pull 模型适合 Redis 短连接 |
| 可视化 | Grafana | 模板丰富,官方提供 Redis 专用 Dashboard |
| 告警引擎 | Prometheus + Alertmanager | 规则灵活,支持分级告警和静默 |
| 日志采集 | Promtail + Loki | 可选,用于慢查询日志聚合 |
*4.3 部署拓扑建议
- 单实例:Exporter 与 Redis 同机或同容器,Prometheus 远程拉取
- 主从:Exporter 部署在主从节点各一个,分别监控
- Cluster:每个分片主节点部署 Exporter,Prometheus 配置全部目标
- Sentinel:单独监控 Sentinel 进程,同时监控其管理的 Redis 实例
*五、核心命令与指标采集
*5.1 Redis CLI:INFO 全量采集
# 采集全部监控信息
redis-cli INFO
# 仅采集内存段(适合脚本过滤)
redis-cli INFO memory
# 采集复制状态(主从监控)
redis-cli INFO replication
# 持续监控 QPS(每秒采样)
redis-cli INFO stats | grep instantaneous_ops_per_sec
# 查看命令统计(找出热点命令)
redis-cli INFO commandstats
*5.2 Shell:自动化监控脚本
#!/bin/bash
# redis-health-check.sh - Redis 健康巡检脚本
# 用法: ./redis-health-check.sh 127.0.0.1 6379
HOST=${1:-127.0.0.1}
PORT=${2:-6379}
ALERT_THRESHOLD=80 # 内存告警阈值(%)
# 获取内存使用率
USED=$(redis-cli -h $HOST -p $PORT INFO memory | grep "used_memory:" | cut -d: -f2 | tr -d '\r')
MAX=$(redis-cli -h $HOST -p $PORT CONFIG GET maxmemory | tail -1)
if [ "$MAX" -eq 0 ]; then
MAX=$USED # 未设置 maxmemory,使用当前内存作为基线
fi
USAGE=$(echo "scale=2; $USED / $MAX * 100" | bc)
# 获取连接数
CLIENTS=$(redis-cli -h $HOST -p $PORT INFO clients | grep "connected_clients:" | cut -d: -f2 | tr -d '\r')
MAX_CLIENTS=$(redis-cli -h $HOST -p $PORT CONFIG GET maxclients | tail -1)
# 获取复制延迟
REPL_LAG=$(redis-cli -h $HOST -p $PORT INFO replication | grep "master_last_io_seconds_ago:" | cut -d: -f2 | tr -d '\r')
# 输出 JSON 格式监控数据
cat <<EOF
{
"timestamp": "$(date -Iseconds)",
"host": "$HOST:$PORT",
"memory_usage_percent": $USAGE,
"connected_clients": $CLIENTS,
"maxclients": $MAX_CLIENTS,
"replication_lag_seconds": ${REPL_LAG:-0},
"alert": $(echo "$USAGE > $ALERT_THRESHOLD" | bc)
}
EOF
*5.3 Python:应用层监控客户端
#!/usr/bin/env python3
"""
redis_monitor.py - Python 应用层 Redis 监控采集
依赖: pip install redis
"""
import redis
import json
import time
from datetime import datetime
def collect_metrics(host='127.0.0.1', port=6379, password=None):
"""采集 Redis 核心监控指标"""
r = redis.Redis(host=host, port=port, password=password, decode_responses=True)
# 采集 INFO 数据
info = r.info()
metrics = {
'timestamp': datetime.now().isoformat(),
'host': f'{host}:{port}',
'version': info.get('redis_version'),
'uptime_seconds': info.get('uptime_in_seconds'),
# 内存指标
'memory': {
'used_memory': info.get('used_memory'),
'used_memory_rss': info.get('used_memory_rss'),
'used_memory_peak': info.get('used_memory_peak'),
'maxmemory': info.get('maxmemory', 0),
'fragmentation_ratio': info.get('mem_fragmentation_ratio'),
'evicted_keys': info.get('evicted_keys', 0),
},
# 连接指标
'clients': {
'connected': info.get('connected_clients'),
'blocked': info.get('blocked_clients'),
'maxclients': info.get('maxclients'),
},
# 性能指标
'performance': {
'instantaneous_ops_per_sec': info.get('instantaneous_ops_per_sec'),
'total_commands_processed': info.get('total_commands_processed'),
'keyspace_hits': info.get('keyspace_hits'),
'keyspace_misses': info.get('keyspace_misses'),
'hit_rate': (
info.get('keyspace_hits', 0) /
(info.get('keyspace_hits', 0) + info.get('keyspace_misses', 1)) * 100
),
},
# 复制指标
'replication': {
'role': info.get('role'),
'master_link_status': info.get('master_link_status'),
'master_last_io_seconds_ago': info.get('master_last_io_seconds_ago'),
'master_repl_offset': info.get('master_repl_offset'),
'slave_repl_offset': info.get('slave_repl_offset'),
},
# 持久化指标
'persistence': {
'rdb_last_bgsave_status': info.get('rdb_last_bgsave_status'),
'aof_last_write_status': info.get('aof_last_write_status'),
'aof_current_size': info.get('aof_current_size'),
'aof_base_size': info.get('aof_base_size'),
}
}
return metrics
def check_slowlog(r, threshold_ms=10, limit=10):
"""检查慢查询日志"""
slowlog = r.slowlog_get(limit)
slow_commands = []
for entry in slowlog:
duration_ms = entry['duration'] / 1000 # 微秒转毫秒
if duration_ms > threshold_ms:
slow_commands.append({
'id': entry['id'],
'command': ' '.join(entry['command'].decode() if isinstance(entry['command'], bytes) else str(c) for c in entry['command']),
'duration_ms': duration_ms,
'timestamp': datetime.fromtimestamp(entry['time']).isoformat()
})
return slow_commands
if __name__ == '__main__':
metrics = collect_metrics()
print(json.dumps(metrics, indent=2, ensure_ascii=False))
# 慢查询检查示例
r = redis.Redis(decode_responses=True)
slow = check_slowlog(r, threshold_ms=10)
if slow:
print(f"\n⚠️ 发现 {len(slow)} 条慢查询:")
for cmd in slow[:5]:
print(f" - {cmd['command']}: {cmd['duration_ms']:.2f}ms")
*5.4 Java:Spring Boot 生产级监控集成
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* RedisMetricsCollector - Spring Boot 生产级 Redis 监控采集
* 依赖: io.micrometer:micrometer-core, redis.clients:jedis
*/
##
public class RedisMetricsCollector {
private final JedisPool jedisPool;
private final MeterRegistry meterRegistry;
public RedisMetricsCollector(JedisPool jedisPool, MeterRegistry meterRegistry) {
this.jedisPool = jedisPool;
this.meterRegistry = meterRegistry;
}
/**
* 每 15 秒采集一次 Redis 指标,写入 Micrometer
*/
@Scheduled(fixedRate = 15000)
public void collectMetrics() {
try (Jedis jedis = jedisPool.getResource()) {
// 内存指标
long usedMemory = Long.parseLong(
jedis.info("memory").split("used_memory:")[1].split("\r\n")[0]
);
meterRegistry.gauge("redis.memory.used", Tags.of("instance", "main"), usedMemory);
// QPS 指标
long ops = Long.parseLong(
jedis.info("stats").split("instantaneous_ops_per_sec:")[1].split("\r\n")[0]
);
meterRegistry.gauge("redis.ops.per_second", Tags.of("instance", "main"), ops);
// 连接数
long clients = Long.parseLong(
jedis.info("clients").split("connected_clients:")[1].split("\r\n")[0]
);
meterRegistry.gauge("redis.clients.connected", Tags.of("instance", "main"), clients);
// 复制延迟(仅 Slave 节点)
String replicationInfo = jedis.info("replication");
if (replicationInfo.contains("role:slave")) {
long lag = Long.parseLong(
replicationInfo.split("master_last_io_seconds_ago:")[1].split("\r\n")[0]
);
meterRegistry.gauge("redis.replication.lag_seconds",
Tags.of("instance", "main"), lag);
}
// 命中率
long hits = Long.parseLong(
jedis.info("stats").split("keyspace_hits:")[1].split("\r\n")[0]
);
long misses = Long.parseLong(
jedis.info("stats").split("keyspace_misses:")[1].split("\r\n")[0]
);
double hitRate = (hits + misses) > 0 ? (double) hits / (hits + misses) * 100 : 0;
meterRegistry.gauge("redis.cache.hit_rate", Tags.of("instance", "main"), hitRate);
} catch (Exception e) {
// 采集失败时记录错误,避免影响业务
meterRegistry.counter("redis.metrics.collection.errors",
Tags.of("error", e.getClass().getSimpleName())).increment();
}
}
/**
* 慢查询检查(每分钟执行)
*/
@Scheduled(fixedRate = 60000)
public void checkSlowLog() {
try (Jedis jedis = jedisPool.getResource()) {
// 获取最近 10 条慢查询(slowlog-log-slower-than 需预先配置)
var slowlog = jedis.slowlogGet(10);
for (var entry : slowlog) {
long durationMs = entry.getExecutionTime() / 1000; // 微秒转毫秒
if (durationMs > 100) { // 超过 100ms 视为严重慢查询
meterRegistry.counter("redis.slowlog.severe",
Tags.of("command", entry.getArgs().get(0)))
.increment();
}
}
} catch (Exception e) {
// 静默处理,避免告警风暴
}
}
}
*六、实战:Prometheus + Grafana 监控体系搭建
*6.1 部署 Redis Exporter
# 下载并启动 Redis Exporter(二进制部署)
wget https://github.com/oliver006/redis_exporter/releases/download/v1.55.0/redis_exporter-v1.55.0.linux-amd64.tar.gz
tar xzf redis_exporter-v1.55.0.linux-amd64.tar.gz
cd redis_exporter-v1.55.0.linux-amd64
# 启动 Exporter(支持密码、TLS、多实例)
./redis_exporter \
-redis.addr redis://127.0.0.1:6379 \
-redis.password 'your_password' \
-web.listen-address :9121
Docker 部署方式:
docker run -d \
--name redis_exporter \
-p 9121:9121 \
oliver006/redis_exporter:latest \
--redis.addr redis://redis:6379
*6.2 Prometheus 配置
# /etc/prometheus/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'redis'
static_configs:
- targets:
- 'redis-01:9121'
- 'redis-02:9121'
- 'redis-03:9121'
relabel_configs:
- source_labels: [__address__]
target_label: instance
- job_name: 'redis-sentinel'
static_configs:
- targets:
- 'sentinel-01:9121'
- 'sentinel-02:9121'
- 'sentinel-03:9121'
metrics_path: /scrape
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- target_label: __address__
replacement: sentinel-exporter:9121
*6.3 Grafana Dashboard 导入
官方推荐的 Dashboard ID:763,或搜索 "Redis Dashboard for Prometheus Redis Exporter"(作者:oliver006)。
核心面板应包含:
- Overview:运行时间、版本、QPS、客户端数
- Memory:内存使用趋势、RSS、碎片率、峰值
- Connections:连接数、拒绝连接数、阻塞客户端
- Persistence:RDB/AOF 状态、最后一次保存时间
- Replication:主从偏移量、复制延迟、连接状态
- Performance:命令处理耗时、命中率、慢查询数量
- Alerts:实时告警事件列表
*6.4 告警规则(Prometheus Rule)
# /etc/prometheus/rules/redis.yml
groups:
- name: redis_alerts
rules:
# 内存使用率超过 85%
- alert: RedisMemoryUsageHigh
expr: |
(
redis_memory_used_bytes /
(redis_memory_max_bytes > 0 or redis_memory_used_bytes * 1.2)
) * 100 > 85
for: 5m
labels:
severity: warning
annotations:
summary: "Redis 内存使用率过高"
description: "实例 {{ $labels.instance }} 内存使用率 {{ $value | humanizePercentage }},接近 maxmemory 限制。"
# 连接数超过 80% maxclients
- alert: RedisConnectionsNearLimit
expr: |
redis_connected_clients / redis_config_maxclients * 100 > 80
for: 3m
labels:
severity: warning
annotations:
summary: "Redis 连接数接近上限"
description: "实例 {{ $labels.instance }} 连接数 {{ $value | humanizePercentage }},可能存在连接泄露。"
# 复制延迟超过 10 秒
- alert: RedisReplicationLagHigh
expr: redis_master_last_io_seconds_ago > 10
for: 2m
labels:
severity: critical
annotations:
summary: "Redis 主从复制延迟过高"
description: "从库 {{ $labels.instance }} 已 {{ $value }} 秒未收到主库数据。"
# 主从连接断开
- alert: RedisMasterLinkDown
expr: redis_master_link_up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Redis 主从连接断开"
description: "从库 {{ $labels.instance }} 与主库连接已断开。"
# 持久化失败
- alert: RedisPersistenceFailed
expr: |
redis_rdb_last_bgsave_status != 1 or
redis_aof_last_write_status != 1
for: 1m
labels:
severity: critical
annotations:
summary: "Redis 持久化失败"
description: "实例 {{ $labels.instance }} 的 RDB 或 AOF 持久化状态异常。"
# QPS 骤降(对比 1 小时前下降 50%)
- alert: RedisQPSDrop
expr: |
(
redis_instantaneous_ops_per_sec
/
avg_over_time(redis_instantaneous_ops_per_sec[1h])
) < 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "Redis QPS 异常下降"
description: "实例 {{ $labels.instance }} QPS 较 1 小时前下降超过 50%,可能存在业务异常。"
# 慢查询数量激增
- alert: RedisSlowLogIncrease
expr: |
increase(redis_slowlog_length[5m]) > 10
for: 2m
labels:
severity: warning
annotations:
summary: "Redis 慢查询数量激增"
description: "实例 {{ $labels.instance }} 5 分钟内新增 {{ $value }} 条慢查询,需检查热点命令。"
*6.5 Alertmanager 告警路由
# /etc/alertmanager/alertmanager.yml
global:
smtp_smarthost: 'smtp.example.com:587'
smtp_from: 'alert@example.com'
route:
group_by: ['alertname', 'instance']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'default'
routes:
- match:
severity: critical
receiver: 'pagerduty'
continue: true
- match:
severity: warning
receiver: 'slack'
receivers:
- name: 'default'
email_configs:
- to: 'ops@example.com'
- name: 'pagerduty'
pagerduty_configs:
- service_key: '<your-pagerduty-key>'
- name: 'slack'
slack_configs:
- api_url: '<your-slack-webhook>'
channel: '#redis-alerts'
title: '{{ .GroupLabels.alertname }}'
text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
*七、多语言客户端监控采集
除了服务端监控,应用层客户端监控同样重要。以下是各语言客户端的监控集成方案。
*7.1 Python:基于 redis-py 的连接池监控
import redis
import psutil
class MonitoredRedisClient:
"""带监控能力的 Redis 客户端包装器"""
def __init__(self, **kwargs):
self.pool = redis.ConnectionPool(**kwargs)
self.client = redis.Redis(connection_pool=self.pool)
def get_pool_stats(self):
"""获取连接池状态"""
return {
'pool_size': self.pool.max_connections,
'in_use_connections': len(self.pool._in_use_connections),
'available_connections': len(self.pool._available_connections),
'connection_utilization': (
len(self.pool._in_use_connections) / self.pool.max_connections * 100
)
}
def execute_with_timing(self, command, *args, **kwargs):
"""执行命令并记录耗时"""
import time
start = time.time()
try:
result = getattr(self.client, command)(*args, **kwargs)
latency_ms = (time.time() - start) * 1000
# 上报指标(可接入 Prometheus Pushgateway 或 StatsD)
print(f"[METRIC] redis_command_duration{{cmd={command}}} {latency_ms:.2f}ms")
if latency_ms > 100:
print(f"[WARN] 慢查询: {command} 耗时 {latency_ms:.2f}ms")
return result
except redis.RedisError as e:
print(f"[ERROR] Redis 命令失败: {command}, 错误: {e}")
raise
*7.2 Java:基于 Lettuce 的指标采集
import io.lettuce.core.metrics.DefaultCommandLatencyCollectorOptions;
import io.lettuce.core.metrics.MicrometerCommandLatencyRecorder;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
/**
* Lettuce 客户端监控配置
* Lettuce 原生支持 Micrometer 指标集成
*/
public class MonitoredLettuceClient {
public static ClientResources createMonitoredClientResources() {
MeterRegistry meterRegistry = new SimpleMeterRegistry();
// 配置命令延迟采集选项
DefaultCommandLatencyCollectorOptions options =
DefaultCommandLatencyCollectorOptions.builder()
.targetUnit(io.lettuce.core.metrics.Microseconds) // 微秒精度
.resetLatenciesAfterInterval(java.time.Duration.ofMinutes(1)) // 每分钟重置
.build();
return DefaultClientResources.builder()
.commandLatencyCollectorOptions(options)
.commandLatencyRecorder(new MicrometerCommandLatencyRecorder(meterRegistry, options))
.build();
}
}
*7.3 Go:基于 go-redis 的 Hook 监控
package main
import (
"context"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
// MetricsHook 实现 go-redis 的 Hook 接口,用于采集命令延迟
type MetricsHook struct{}
func (h *MetricsHook) DialHook(next redis.DialHook) redis.DialHook {
return func(ctx context.Context, net, addr string) (redis.Conn, error) {
return next(ctx, net, addr)
}
}
func (h *MetricsHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
return func(ctx context.Context, cmd redis.Cmder) error {
start := time.Now()
err := next(ctx, cmd)
latency := time.Since(start)
// 上报到 Prometheus 或日志
fmt.Printf("[METRIC] redis_cmd_latency{cmd=%s} %.3fms\n",
cmd.FullName(), float64(latency.Microseconds())/1000)
if latency > 100*time.Millisecond {
fmt.Printf("[WARN] 慢查询: %s 耗时 %.3fms\n",
cmd.FullName(), float64(latency.Microseconds())/1000)
}
return err
}
}
func (h *MetricsHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
return func(ctx context.Context, cmds []redis.Cmder) error {
start := time.Now()
err := next(ctx, cmds)
latency := time.Since(start)
fmt.Printf("[METRIC] redis_pipeline_latency cmds=%d %.3fms\n",
len(cmds), float64(latency.Microseconds())/1000)
return err
}
}
func main() {
// 创建带 Hook 的 Redis 客户端
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
// 添加监控 Hook
rdb.AddHook(&MetricsHook{})
// 此后所有命令自动采集延迟
val, err := rdb.Get(ctx, "key").Result()
if err != nil {
panic(err)
}
fmt.Println(val)
}
*八、故障排查与告警规则
*8.1 常见故障场景与诊断路径
| 故障现象 | 可能原因 | 诊断命令 | 解决方案 |
|---|---|---|---|
| 内存突增 | 大 Key 写入 / 批量操作 | redis-cli --bigkeys |
拆分大 Key,启用内存上限 |
| 延迟飙升 | 慢查询 / fork 阻塞 | LATENCY DOCTOR | 优化慢命令,升级硬件 |
| 连接被拒绝 | 连接泄露 / 并发过高 | INFO clients |
检查连接池配置,扩容 |
| 主从断开 | 网络故障 / 缓冲区溢出 | INFO replication |
检查 repl-backlog-size |
| 命中率骤降 | 缓存穿透 / 大量新 Key | INFO stats |
启用布隆过滤器,优化缓存策略 |
| 持久化失败 | 磁盘满 / 权限问题 | INFO persistence |
清理磁盘,检查目录权限 |
*8.2 告警阈值建议(生产环境)
| 指标 | 警告阈值 | 严重阈值 | 说明 |
|---|---|---|---|
| 内存使用率 | > 80% | > 90% | 预留 10% 缓冲,避免突发写入导致 OOM |
| 连接数占比 | > 70% | > 90% | 连接泄露往往在 70% 时开始恶化 |
| 复制延迟 | > 5s | > 10s | 读写分离场景,超过 10s 可能读到旧数据 |
| 慢查询数量 | > 5/min | > 20/min | 持续慢查询意味着单线程瓶颈 |
| 命中率(缓存) | < 80% | < 50% | 低于 50% 缓存几乎失去意义 |
| 拒绝连接数 | > 0 | - | 任何拒绝连接都应立即排查 |
| 持久化状态 | != ok | - | 持久化失败 = 数据安全风险 |
*8.3 故障自愈脚本示例
#!/bin/bash
# redis-auto-heal.sh - 自动故障自愈脚本
# 与告警系统集成,接收 Alertmanager webhook 触发
INSTANCE=$1
ALERT_TYPE=$2
case $ALERT_TYPE in
"memory_high")
# 自动触发内存整理
redis-cli -h $INSTANCE MEMORY PURGE
# 记录日志
echo "$(date) [$INSTANCE] 自动执行 MEMORY PURGE" >> /var/log/redis-heal.log
;;
"connection_leak")
# 检查并关闭空闲连接
redis-cli -h $INSTANCE CLIENT LIST | grep "idle=" | \
awk -F' ' '{for(i=1;i<=NF;i++) if($i ~ /id=/) print $i}' | \
sed 's/id=//' | xargs -I {} redis-cli -h $INSTANCE CLIENT KILL {} skipme
echo "$(date) [$INSTANCE] 自动清理空闲连接" >> /var/log/redis-heal.log
;;
"slowlog_spike")
# 临时降低慢查询阈值以捕获更多日志
redis-cli -h $INSTANCE CONFIG SET slowlog-log-slower-than 5000
echo "$(date) [$INSTANCE] 慢查询激增,临时降低阈值至 5ms" >> /var/log/redis-heal.log
;;
*)
echo "$(date) [$INSTANCE] 未知告警类型: $ALERT_TYPE" >> /var/log/redis-heal.log
;;
esac
⚠️ 生产环境注意:自动故障自愈需谨慎评估副作用。建议先在测试环境验证,并配合人工审批流程。
*九、FAQ 常见问题
*Q1:Prometheus 采集 Redis 指标会影响性能吗?
A:影响极小。Redis Exporter 通过 INFO 命令采集,单次执行耗时通常在 1ms 以内。默认 15 秒采集间隔,对 Redis 主线程的压力可以忽略。对于极高并发场景(> 100 万 QPS),可将间隔调整为 30 秒。
*Q2:内存使用率 80% 时,为什么还没有触发驱逐?
A:Redis 的驱逐只在 used_memory > maxmemory 时触发。80% 是预警阈值,给运维留出响应时间。如果未设置 maxmemory,Redis 将持续使用内存直到系统 OOM,因此生产环境必须设置 maxmemory 和 maxmemory-policy。
*Q3:监控显示复制延迟 0 秒,但业务仍读到旧数据?
A:master_last_io_seconds_ago 只表示从库收到数据的网络延迟,不代表数据已完全写入从库内存。高并发场景下,从库处理命令队列可能有延迟。更精确的指标是 master_repl_offset - slave_repl_offset,偏移差 > 1000 说明存在处理延迟。
*Q4:为什么 Grafana 显示内存使用率 100%,但 used_memory 远小于 maxmemory?
A:used_memory 是 Redis 逻辑内存,而 used_memory_rss 是系统物理内存。如果 RSS 远大于 used_memory,说明存在严重内存碎片或客户端输出缓冲堆积。此时应关注 RSS 而非 used_memory,并检查 mem_fragmentation_ratio。
*Q5:客户端监控和服务端监控,哪个更重要?
A:两者缺一不可。服务端监控回答"Redis 是否健康",客户端监控回答"应用使用 Redis 是否正确"。很多性能问题(如连接池过小、Pipeline 未使用、缓存穿透)在服务端指标上表现不明显,但客户端监控能直接发现。建议服务端做基线告警,客户端做精细诊断。
*Q6:Redis Cluster 模式下如何监控?
A:每个节点部署独立的 Redis Exporter,Prometheus 配置所有节点为目标。Grafana Dashboard 使用变量($instance)切换节点视图。额外关注 cluster_slots_assigned 和 cluster_slots_ok,确保所有 16384 个槽位正常分配。任何节点槽位异常(cluster_slots_pfail > 0)都应立即告警。
*十、总结与下一步
本文从 Redis 监控的六大核心维度出发,深入解析了 INFO 命令的数据结构、内存与复制延迟的监控原理,给出了基于 Prometheus + Grafana + Redis Exporter 的完整可观测性架构方案,并提供了 Shell、Python、Java、Go 多语言的监控采集代码。
核心要点回顾:
- 监控不是堆砌指标:围绕内存、连接、命令、延迟、持久化、复制六个维度,选择对业务 SLA 有直接影响的指标
- 服务端 + 客户端双管齐下:服务端监控发现系统异常,客户端监控发现使用问题
- 告警分级:Critical(立即响应)vs Warning(趋势关注),避免告警疲劳
- 从监控到自愈:基于告警触发自动化脚本,缩短 MTTR(平均恢复时间)
下一步建议:
- 短期:部署 Redis Exporter + Grafana Dashboard,建立基线监控
- 中期:根据业务场景定制告警规则,接入企业告警通道(钉钉/飞书/邮件)
- 长期:建立 Redis 监控 SLO(如 P99 延迟 < 10ms、命中率 > 90%),将监控纳入 CI/CD 质量门禁
监控的本质不是"出了问题再查",而是"让问题在发生前就被看见"。一套成熟的 Redis 监控体系,是保障缓存服务稳定运行的最后一道,也是最重要的一道防线。