本文目录导读:

可以的,市面上确实有很多实用的脚本能够自动监控Redis性能,这类脚本通常能帮你收集关键指标,并在出现异常时报警。
下面我为你详细介绍几种常用方案,从轻量级脚本到企业级工具都有,你可以根据实际环境选择。
轻量级Shell脚本:快速部署,适合小规模环境
这种方案依赖Redis自带的redis-cli工具和系统命令,无需额外安装软件。
示例脚本:redis_monitor.sh
#!/bin/bash
# Redis连接信息
REDIS_CLI="/usr/bin/redis-cli" # 请根据实际路径修改
HOST="127.0.0.1"
PORT="6379"
PASSWORD="" # 如有密码,填在这里,如 -a 'yourpassword'
# 报警阈值(可根据需要调整)
MAX_MEMORY_MB=1024 # 内存使用超过1024MB报警
MAX_LATENCY_MS=100 # 延迟超过100ms报警
MAX_CONNECTIONS=500 # 连接数超过500报警
REPL_LAG_THRESHOLD=10 # 主从复制延迟超过10秒报警
SLOW_LOG_THRESHOLD=100000 # 慢查询阈值,微秒
# 报警函数(可自定义,如发送邮件、钉钉等)
alert() {
local message="$1"
echo "[ALERT] $(date '+%Y-%m-%d %H:%M:%S') - $message" >> /var/log/redis_monitor.log
# 可在此添加发送邮件或Webhook的代码
# curl -X POST -H "Content-Type: application/json" -d '{"text":"'"$message"'"}' your_webhook_url
}
# 获取INFO命令的输出
if [ -n "$PASSWORD" ]; then
INFO=$($REDIS_CLI -h $HOST -p $PORT -a "$PASSWORD" INFO 2>/dev/null)
else
INFO=$($REDIS_CLI -h $HOST -p $PORT INFO 2>/dev/null)
fi
if [ $? -ne 0 ]; then
alert "无法连接到Redis服务器 $HOST:$PORT"
exit 1
fi
# 提取关键性能指标
used_memory=$(echo "$INFO" | grep "^used_memory:" | cut -d ':' -f2)
used_memory_rss=$(echo "$INFO" | grep "^used_memory_rss:" | cut -d ':' -f2)
used_memory_peak=$(echo "$INFO" | grep "^used_memory_peak:" | cut -d ':' -f2)
total_connections_received=$(echo "$INFO" | grep "^total_connections_received:" | cut -d ':' -f2)
connected_clients=$(echo "$INFO" | grep "^connected_clients:" | cut -d ':' -f2)
instantaneous_ops_per_sec=$(echo "$INFO" | grep "^instantaneous_ops_per_sec:" | cut -d ':' -f2)
instantaneous_input_kbps=$(echo "$INFO" | grep "^instantaneous_input_kbps:" | cut -d ':' -f2)
instantaneous_output_kbps=$(echo "$INFO" | grep "^instantaneous_output_kbps:" | cut -d ':' -f2)
rejected_connections=$(echo "$INFO" | grep "^rejected_connections:" | cut -d ':' -f2)
keyspace_hits=$(echo "$INFO" | grep "^keyspace_hits:" | cut -d ':' -f2)
keyspace_misses=$(echo "$INFO" | grep "^keyspace_misses:" | cut -d ':' -f2)
uptime_in_seconds=$(echo "$INFO" | grep "^uptime_in_seconds:" | cut -d ':' -f2)
role=$(echo "$INFO" | grep "^role:" | cut -d ':' -f2)
# 内存检查(转换为MB)
used_memory_mb=$((used_memory / 1024 / 1024))
if [ $used_memory_mb -gt $MAX_MEMORY_MB ]; then
alert "Redis内存使用超限: ${used_memory_mb}MB (阈值: ${MAX_MEMORY_MB}MB)"
fi
# 连接数检查
if [ "$connected_clients" -gt "$MAX_CONNECTIONS" ]; then
alert "Redis客户端连接数过高: $connected_clients (阈值: $MAX_CONNECTIONS)"
fi
# 拒绝连接检查(如果有拒绝连接,通常意味着达到最大连接限制)
if [ -n "$rejected_connections" ] && [ "$rejected_connections" -gt 0 ]; then
alert "Redis出现拒绝连接: $rejected_connections次"
fi
# 主从复制检查(仅对从节点有效)
if [ "$role" = "slave" ]; then
master_link_status=$(echo "$INFO" | grep "^master_link_status:" | cut -d ':' -f2)
master_last_io_seconds_ago=$(echo "$INFO" | grep "^master_last_io_seconds_ago:" | cut -d ':' -f2)
slave_repl_offset=$(echo "$INFO" | grep "^slave_repl_offset:" | cut -d ':' -f2)
master_repl_offset=$(echo "$INFO" | grep "^master_repl_offset:" | cut -d ':' -f2)
if [ "$master_link_status" != "up" ]; then
alert "Redis主从复制链接断开!"
fi
if [ -n "$master_last_io_seconds_ago" ] && [ "$master_last_io_seconds_ago" -gt "$REPL_LAG_THRESHOLD" ]; then
alert "Redis主从复制延迟: ${master_last_io_seconds_ago}秒 (阈值: ${REPL_LAG_THRESHOLD}秒)"
fi
fi
# 慢查询检查
SLOW_LOG=$($REDIS_CLI -h $HOST -p $PORT SLOWLOG GET 5 2>/dev/null)
if [ -n "$SLOW_LOG" ]; then
# 简单判断有没有慢查询
echo "$SLOW_LOG" | while read line; do
duration=$(echo "$line" | awk '{print $2}' | tr -d ',') # 微秒
if [ "$duration" -gt "$SLOW_LOG_THRESHOLD" ]; then
alert "Redis慢查询记录: 耗时 ${duration}微秒, 命令: $line"
fi
done
fi
# 记录正常状态
echo "$(date '+%Y-%m-%d %H:%M:%S') - OK - 内存:${used_memory_mb}MB, 连接:${connected_clients}, OPS:${instantaneous_ops_per_sec}" >> /var/log/redis_monitor.log
使用方法:
- 保存为
redis_monitor.sh并赋予执行权限:chmod +x redis_monitor.sh - 设置crontab定期执行(例如每1分钟):
* * * * * /path/to/redis_monitor.sh - 在脚本中完善
alert()函数,对接你的报警通道(企业微信、钉钉、邮件等)。
Python脚本:更强大、更灵活
Python方案可以利用redis-py库和丰富的数据处理能力。
示例脚本:redis_perf_monitor.py
#!/usr/bin/env python3
import redis
import time
import smtplib
import json
from datetime import datetime
import os
# Redis连接配置
REDIS_CONFIG = {
'host': '127.0.0.1',
'port': 6379,
'password': None,
'db': 0,
'socket_connect_timeout': 5
}
# 报警阈值
ALERTS = {
'memory_usage_mb': 1024,
'connected_clients': 500,
'hit_rate_low': 0.8, # 缓存命中率低于80%报警
'latency_ms': 100,
'rejected_connections': 1,
'replication_lag_seconds': 10,
}
# 报警配置
ALERT_WEBHOOK_URL = "https://your-alert-webhook-url" # 如钉钉、企业微信机器人
SMTP_CONFIG = {
'server': 'smtp.example.com',
'port': 587,
'username': 'your@email.com',
'password': 'your-password',
'from_addr': 'monitor@example.com',
'to_addr': 'admin@example.com'
}
class RedisMonitor:
def __init__(self, redis_config):
self.client = redis.StrictRedis(**redis_config)
self.info = {}
self.alerts = []
def collect_metrics(self):
"""收集所有关键指标"""
try:
self.info = self.client.info()
except redis.ConnectionError as e:
self._add_alert(f"无法连接到Redis: {str(e)}")
return False
return True
def check_memory(self):
"""检查内存使用"""
used_memory = self.info.get('used_memory', 0)
used_memory_mb = used_memory / 1024 / 1024
max_memory = self.info.get('maxmemory', 0)
max_memory_mb = max_memory / 1024 / 1024
if used_memory_mb > ALERTS['memory_usage_mb']:
self._add_alert(f"内存使用超限: {used_memory_mb:.1f}MB (阈值: {ALERTS['memory_usage_mb']}MB)")
if max_memory > 0:
usage_percent = (used_memory / max_memory) * 100
if usage_percent > 80:
self._add_alert(f"内存使用率超过80%: {usage_percent:.1f}%")
# 检查RSS内存 vs 已使用内存(判断内存碎片)
used_memory_rss = self.info.get('used_memory_rss', 0)
if used_memory_rss > 0:
fragmentation = used_memory_rss / used_memory if used_memory > 0 else 0
if fragmentation > 1.5:
self._add_alert(f"内存碎片率高: {fragmentation:.2f}")
def check_connections(self):
"""检查连接数和拒绝连接"""
connected_clients = self.info.get('connected_clients', 0)
rejected_connections = self.info.get('rejected_connections', 0)
if connected_clients > ALERTS['connected_clients']:
self._add_alert(f"客户端连接数过高: {connected_clients} (阈值: {ALERTS['connected_clients']})")
if rejected_connections > ALERTS['rejected_connections']:
self._add_alert(f"出现拒绝连接: {rejected_connections}次")
def check_hit_rate(self):
"""检查缓存命中率"""
hits = self.info.get('keyspace_hits', 0)
misses = self.info.get('keyspace_misses', 0)
total = hits + misses
if total > 0:
hit_rate = hits / total
if hit_rate < ALERTS['hit_rate_low']:
self._add_alert(f"缓存命中率过低: {hit_rate:.1%} (阈值: {ALERTS['hit_rate_low']:.0%})")
def check_latency(self):
"""检查延迟"""
try:
start = time.time()
self.client.ping()
latency = (time.time() - start) * 1000
if latency > ALERTS['latency_ms']:
self._add_alert(f"Redis延迟过高: {latency:.1f}ms (阈值: {ALERTS['latency_ms']}ms)")
except Exception as e:
self._add_alert(f"延迟检查失败: {str(e)}")
def check_replication(self):
"""检查主从复制状态"""
role = self.info.get('role', 'master')
if role == 'slave':
master_link_status = self.info.get('master_link_status', 'down')
master_last_io_seconds_ago = self.info.get('master_last_io_seconds_ago', 0)
if master_link_status != 'up':
self._add_alert(f"主从复制链接断开! 状态: {master_link_status}")
if master_last_io_seconds_ago > ALERTS['replication_lag_seconds']:
self._add_alert(f"主从复制延迟: {master_last_io_seconds_ago}秒 (阈值: {ALERTS['replication_lag_seconds']}秒)")
# 检查复制积压缓冲区
repl_backlog_active = self.info.get('repl_backlog_active', 0)
repl_backlog_size = self.info.get('repl_backlog_size', 0)
if repl_backlog_active == 0:
self._add_alert("复制积压缓冲区未启用!")
def check_slow_log(self):
"""检查慢查询日志"""
try:
slow_logs = self.client.slowlog_get(10)
for log in slow_logs:
# slow_logs返回的是列表,每个元素是一个元组 (id, timestamp, duration, command, ...)
if len(log) >= 4:
duration = log[2] # 微秒
command = ' '.join(log[3]) if isinstance(log[3], list) else str(log[3])
if duration > 100000: # 100ms以上
self._add_alert(f"慢查询: {duration}微秒, 命令: {command}")
except Exception as e:
pass
def _add_alert(self, message):
"""添加报警信息"""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
alert_msg = f"[{timestamp}] {message}"
self.alerts.append(alert_msg)
print(alert_msg)
def send_alerts(self):
"""发送报警信息(可对接多种通道)"""
if not self.alerts:
return
alert_text = "\n".join(self.alerts)
# 示例1:发送到Webhook
self._send_webhook(alert_text)
# 示例2:发送邮件
self._send_email("Redis监控报警", alert_text)
def _send_webhook(self, message):
"""发送Webhook报警"""
try:
import requests
payload = {
"msgtype": "text",
"text": {
"content": f"# Redis监控报警\n{message}"
}
}
requests.post(ALERT_WEBHOOK_URL, json=payload, timeout=5)
except Exception as e:
print(f"发送Webhook报警失败: {e}")
def _send_email(self, subject, body):
"""发送邮件报警"""
try:
with smtplib.SMTP(SMTP_CONFIG['server'], SMTP_CONFIG['port']) as server:
server.starttls()
server.login(SMTP_CONFIG['username'], SMTP_CONFIG['password'])
msg = f"Subject: {subject}\n\n{body}"
server.sendmail(SMTP_CONFIG['from_addr'], SMTP_CONFIG['to_addr'], msg)
except Exception as e:
print(f"发送邮件报警失败: {e}")
def generate_report(self):
"""生成性能报告"""
report = {
"timestamp": datetime.now().isoformat(),
"host": REDIS_CONFIG['host'],
"port": REDIS_CONFIG['port'],
"metrics": {
"used_memory_mb": self.info.get('used_memory', 0) / 1024 / 1024,
"connected_clients": self.info.get('connected_clients', 0),
"ops_per_sec": self.info.get('instantaneous_ops_per_sec', 0),
"hit_rate": self._calc_hit_rate(),
"uptime_days": self.info.get('uptime_in_seconds', 0) / 86400,
"keys_count": sum(self.info.get('db0', {}).get('keys', 0) for key in self.info if key.startswith('db')),
"avg_ttl": self.client.info('keyspace').get('db0', {}).get('avg_ttl', 0)
},
"alerts": self.alerts
}
return report
def _calc_hit_rate(self):
hits = self.info.get('keyspace_hits', 0)
misses = self.info.get('keyspace_misses', 0)
total = hits + misses
return hits / total if total > 0 else 0
def run(self):
"""执行监控主流程"""
if not self.collect_metrics():
self.send_alerts()
return
self.check_memory()
self.check_connections()
self.check_hit_rate()
self.check_latency()
self.check_replication()
self.check_slow_log()
if self.alerts:
self.send_alerts()
self._log_to_file(self.generate_report())
else:
print(f"[{datetime.now()}] 性能正常")
def _log_to_file(self, report):
"""将报告写入日志文件"""
log_dir = "/var/log/redis_monitor"
os.makedirs(log_dir, exist_ok=True)
log_file = f"{log_dir}/redis_monitor_{datetime.now().strftime('%Y%m%d')}.log"
with open(log_file, 'a') as f:
f.write(json.dumps(report) + "\n")
if __name__ == "__main__":
monitor = RedisMonitor(REDIS_CONFIG)
monitor.run()
进阶方案:Prometheus + Grafana
对于生产环境,推荐使用成熟的监控体系:
- 数据采集:使用
redis_exporter(一个专门导出Redis指标的Prometheus exporter) - 数据存储:Prometheus负责抓取和存储指标
- 可视化:Grafana提供丰富的仪表盘
快速部署命令:
# 启动Redis Exporter docker run -d --name redis_exporter \ -e REDIS_ADDR=redis://your-redis-host:6379 \ -p 9121:9121 \ oliver006/redis_exporter # 配置Prometheus(prometheus.yml) # 添加: # - job_name: 'redis' # static_configs: # - targets: ['localhost:9121'] # Grafana导入Redis仪表盘(ID: 11835 或 763)
关键性能指标说明
无论使用哪种方案,都应关注以下核心指标:
| 指标 | 说明 | 建议阈值 |
|---|---|---|
| used_memory | 已用内存 | 不超过maxmemory的80% |
| connected_clients | 当前连接数 | 小于maxclients的80% |
| instantaneous_ops_per_sec | 每秒操作数 | 根据机型,lt;100K |
| hit_rate | 缓存命中率 | >90%为佳 |
| latency | 延迟 | lt;10ms |
| rejected_connections | 拒绝连接数 | 应为0 |
| keyspace_hits/misses | 键空间命中/未命中 | 反映缓存效率 |
| evicted_keys | 被淘汰键数 | 应尽量为0 |
| 慢查询 | 执行时间超过阈值的命令 | 根据业务调整 |
建议
- 脚本化监控适合快速部署,但功能有限
- Prometheus+Grafana是生产环境首选
- 对于云Redis(如阿里云、腾讯云),建议使用云厂商自带的监控服务
- 报警规则要合理:避免阈值过低导致报警风暴,也要避免过高导致故障遗漏
- 结合日志分析:将监控数据与业务日志关联,定位根因
如果需要更详细的配置说明或针对特定场景(如集群模式、哨兵模式)的监控脚本,可以进一步沟通。