性能监控脚本:实时洞察系统健康状态
在复杂的分布式系统中,性能问题往往难以预测和定位。一套完善的性能监控脚本可以帮助你实时了解系统状态,在问题影响用户之前及时发现和处理。
一、引言:为什么需要性能监控
痛点场景
问题1:性能退化难发现
某电商网站响应时间从200ms逐渐恶化到3秒,但由于过程缓慢,用户只是觉得”网速慢”,没有投诉。直到某次活动,流量突增导致服务完全崩溃,才发现数据库查询已经严重超时。
问题2:资源瓶颈不明确
服务器CPU使用率偶尔达到100%,但不知道是哪个进程导致的。排查时CPU使用率又降下来了,问题无法复现,直到数据库连接池耗尽才发现是慢查询累积。
问题3:内存泄漏难定位
某Java应用运行一段时间后内存占用持续增长,最终因OOM崩溃。重启后又恢复正常,但根本原因不明,只能定时重启作为临时解决方案。
问题4:网络抖动不可见
用户反馈访问有时很快有时很慢,但服务器监控显示一切正常。实际上是因为DNS解析不稳定导致的延迟,但没有端到端监控,难以发现。
问题5:性能优化无依据
团队争论应该优化哪个模块,各方都有理由但缺乏数据支持。最终凭直觉决定优化方向,投入大量时间后效果却不明显。
解决方案价值
一套完善的性能监控脚本可以:
二、核心架构设计
2.1 监控指标体系
性能监控指标
├── 系统层指标
│ ├── CPU使用率(整体、单进程)
│ ├── 内存使用率(物理、Swap)
│ ├── 磁盘IO(读写速率、IOPS)
│ ├── 网络IO(带宽、连接数)
│ └── 系统负载(1min、5min、15min)
├── 应用层指标
│ ├── 响应时间(P50、P95、P99)
│ ├── 请求吞吐量(QPS、TPS)
│ ├── 错误率(4xx、5xx)
│ ├── 连接池使用率(数据库、Redis)
│ └── 队列长度(请求队列、任务队列)
├── 数据库层指标
│ ├── 慢查询数量和耗时
│ ├── 连接数(活跃、空闲)
│ ├── 复制延迟(主从同步)
│ ├── 缓冲池命中率(InnoDB)
│ └── 锁等待(行锁、表锁)
└── 业务层指标
├── 订单量、支付成功率
├── 用户活跃度(DAU、MAU)
├── 转化率(注册、购买)
└── 异常交易(退款、投诉)
2.2 数据采集架构
采集架构
├── 采集方式
│ ├── 主动采集(pull):定时拉取指标
│ ├── 被动采集(push):Agent主动上报
│ └── 日志采集(tail):解析日志提取指标
├── 采集频率
│ ├── 实时指标(1-5秒):CPU、内存
│ ├── 准实时指标(30-60秒):响应时间、QPS
│ └── 统计指标(5-15分钟):慢查询、错误率
└── 存储方式
├── 时序数据库(Prometheus):长期存储
├── 内存缓存(Redis):实时计算
└── 日志文件(CSV):历史归档
三、脚本实现
3.1 系统性能采集脚本
#!/bin/bash
system-metrics.sh - 系统性能采集脚本
功能:采集CPU、内存、磁盘、网络等指标
版本:v2.0
set -euo pipefail
配置
COLLECTION_INTERVAL=5 # 采集间隔(秒)
OUTPUT_DIR="/var/log/metrics"
OUTPUT_FILE="${OUTPUT_DIR}/system-metrics.log"
创建输出目录
mkdir -p "$OUTPUT_DIR"
采集CPU使用率
collect_cpu() {
# 总体CPU使用率
local cpu_usage=$(top -bn1 | grep "Cpu(s)" |
sed "s/., ([0-9.])% id.*/1/" |
awk '{print 100 - $1}')
# 单个进程CPU使用率
local top_processes=$(ps aux | sort -rk 3 | head -10 |
awk '{printf "%s(%s%%) ",$11,$3}')
echo "cpu_usage=${cpu_usage}"
echo "top_processes=${top_processes}"
}
采集内存使用率
collect_memory() {
local mem_total=$(free -m | awk '/Mem:/ {print $2}')
local mem_used=$(free -m | awk '/Mem:/ {print $3}')
local mem_usage=$((mem_used * 100 / mem_total))
local swap_total=$(free -m | awk '/Swap:/ {print $2}')
local swap_used=$(free -m | awk '/Swap:/ {print $3}')
local swap_usage=0
if [[ $swap_total -gt 0 ]]; then
swap_usage=$((swap_used * 100 / swap_total))
fi
echo "mem_usage=${mem_usage}"
echo "swap_usage=${swap_usage}"
}
采集磁盘IO
collect_disk() {
# 磁盘使用率
local disk_usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
# IO统计(需要安装sysstat包)
local io_stats=""
if command -v iostat &>/dev/null; then
io_stats=$(iostat -x 1 2 | tail -n +3 | awk 'NR>1 {sum+=$10; count++} END {print sum/count}')
fi
echo "disk_usage=${disk_usage}"
echo "io_wait=${io_stats}"
}
采集网络IO
collect_network() {
# 网络连接数
local connections=$(netstat -an | grep ESTABLISHED | wc -l)
# 网络流量(需要/proc/net/dev)
local rx_bytes=$(cat /proc/net/dev | grep eth0 | awk '{print $2}')
local tx_bytes=$(cat /proc/net/dev | grep eth0 | awk '{print $10}')
echo "connections=${connections}"
echo "rx_bytes=${rx_bytes}"
echo "tx_bytes=${tx_bytes}"
}
采集系统负载
collect_load() {
local load_1min=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//')
local load_5min=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $2}' | sed 's/,//')
local load_15min=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $3}')
echo "load_1min=${load_1min}"
echo "load_5min=${load_5min}"
echo "load_15min=${load_15min}"
}
主采集函数
collect_all() {
local timestamp=$(date +%s)
{
echo "timestamp=${timestamp}"
collect_cpu
collect_memory
collect_disk
collect_network
collect_load
} >> "$OUTPUT_FILE"
# 保留最近24小时的数据
local cutoff_time=$(($(date +%s) - 86400))
temp_file="${OUTPUT_FILE}.tmp"
awk -v cutoff="$cutoff_time" '$1 > cutoff' "$OUTPUT_FILE" > "$temp_file"
mv "$temp_file" "$OUTPUT_FILE"
}
主循环
main() {
echo "开始采集系统性能指标,间隔${COLLECTION_INTERVAL}秒..."
while true; do
collect_all
sleep $COLLECTION_INTERVAL
done
}
执行
if [[ "${1:-}" == "--daemon" ]]; then
main &
echo $! > /var/run/system-metrics.pid
else
collect_all
fi
3.2 应用性能监控脚本
#!/bin/bash
application-metrics.sh - 应用性能监控脚本
功能:监控Web应用响应时间、QPS、错误率
版本:v1.0
set -euo pipefail
配置
WEB_URL="https://1000y.chencunli.com"
CHECK_INTERVAL=30 # 检查间隔(秒)
LOG_FILE="/var/log/app-metrics.log"
创建日志目录
mkdir -p "$(dirname "$LOG_FILE")"
检查Web应用性能
check_web_performance() {
local timestamp=$(date +%s)
# 测量响应时间
local start_time=$(date +%s.%N)
local http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 "$WEB_URL")
local end_time=$(date +%s.%N)
local response_time=$(echo "$end_time - $start_time" | bc)
# 记录指标
echo "${timestamp},response_time,${response_time}" >> "$LOG_FILE"
echo "${timestamp},http_code,${http_code}" >> "$LOG_FILE"
# 判断性能
if [[ $(echo "$response_time > 3.0" | bc) -eq 1 ]]; then
echo "警告: 响应时间过长 (${response_time}秒)"
# 发送告警
send_alert "响应时间异常: ${response_time}秒"
fi
if [[ $http_code -ge 400 ]]; then
echo "警告: HTTP错误码 (${http_code})"
send_alert "HTTP错误: ${http_code}"
fi
}
计算QPS(每秒查询率)
calculate_qps() {
local log_file="$1"
local duration_sec="$2"
# 统计最近N秒内的请求总数
local cutoff_time=$(($(date +%s) - duration_sec))
local request_count=$(awk -v cutoff="$cutoff_time" '$1 > cutoff && $2 == "http_code"' "$LOG_FILE" | wc -l)
local qps=$(echo "scale=2; $request_count / $duration_sec" | bc)
echo "$qps"
}
计算错误率
calculate_error_rate() {
local log_file="$1"
local duration_sec="$2"
local cutoff_time=$(($(date +%s) - duration_sec))
local total_requests=$(awk -v cutoff="$cutoff_time" '$1 > cutoff && $2 == "http_code"' "$LOG_FILE" | wc -l)
local error_requests=$(awk -v cutoff="$cutoff_time" '$1 > cutoff && $2 == "http_code" && $3 >= 400' "$LOG_FILE" | wc -l)
if [[ $total_requests -eq 0 ]]; then
echo "0"
return
fi
local error_rate=$(echo "scale=2; $error_requests * 100 / $total_requests" | bc)
echo "$error_rate"
}
生成性能报告
generate_report() {
local duration_sec=300 # 统计最近5分钟
local avg_response_time=$(awk -v cutoff="$(($(date +%s) - duration_sec))" '$1 > cutoff && $2 == "response_time" {sum+=$3; count++} END {if(count>0) print sum/count; else print 0}' "$LOG_FILE")
local qps=$(calculate_qps "$LOG_FILE" "$duration_sec")
local error_rate=$(calculate_error_rate "$LOG_FILE" "$duration_sec")
echo "========================================"
echo "应用性能报告(最近${duration_sec}秒)"
echo "========================================"
echo "平均响应时间: ${avg_response_time}秒"
echo "QPS: ${qps}"
echo "错误率: ${error_rate}%"
echo "========================================"
# 判断是否告警
if [[ $(echo "$avg_response_time > 2.0" | bc) -eq 1 ]]; then
send_alert "平均响应时间过高: ${avg_response_time}秒"
fi
if [[ $(echo "$error_rate > 5.0" | bc) -eq 1 ]]; then
send_alert "错误率过高: ${error_rate}%"
fi
}
发送告警
send_alert() {
local message="$1"
echo "[告警] $(date '+%Y-%m-%d %H:%M:%S') - $message" >&2
# 发送飞书通知
if command -v curl &>/dev/null; then
curl -X POST "http://localhost:18789/webhook/alert"
-H "Content-Type: application/json"
-d "{"text": "$message", "severity": "warning"}"
2>/dev/null || true
fi
}
主函数
main() {
echo "开始监控应用性能,间隔${CHECK_INTERVAL}秒..."
while true; do
check_web_performance
sleep "$CHECK_INTERVAL"
done
}
执行
if [[ "${1:-}" == "--daemon" ]]; then
main &
echo $! > /var/run/app-metrics.pid
echo "应用性能监控已启动,PID: $(cat /var/run/app-metrics.pid)"
elif [[ "${1:-}" == "--report" ]]; then
generate_report
else
main
fi
3.3 数据库性能监控脚本
#!/bin/bash
database-metrics.sh - 数据库性能监控脚本
功能:监控MySQL性能指标
版本:v1.0
set -euo pipefail
配置
MYSQL_USER="root"
MYSQL_PASSWORD="RootPass2026StrongSecure789XYZ"
MYSQL_HOST="localhost"
LOG_FILE="/var/log/db-metrics.log"
采集MySQL性能指标
collect_mysql_metrics() {
local timestamp=$(date +%s)
# 连接数
local connections=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST"
-e "SHOW STATUS LIKE 'Threads_connected'" | tail -1 | awk '{print $2}')
local max_connections=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST"
-e "SHOW VARIABLES LIKE 'max_connections'" | tail -1 | awk '{print $2}')
# QPS(每秒查询数)
local questions=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST"
-e "SHOW STATUS LIKE 'Questions'" | tail -1 | awk '{print $2}')
local uptime=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST"
-e "SHOW STATUS LIKE 'Uptime'" | tail -1 | awk '{print $2}')
local qps=$(echo "scale=2; $questions / $uptime" | bc)
# 慢查询数
local slow_queries=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST"
-e "SHOW STATUS LIKE 'Slow_queries'" | tail -1 | awk '{print $2}')
# InnoDB缓冲池命中率
local read_requests=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST"
-e "SHOW STATUS LIKE 'Innodb_buffer_pool_read_requests'" | tail -1 | awk '{print $2}')
local reads=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST"
-e "SHOW STATUS LIKE 'Innodb_buffer_pool_reads'" | tail -1 | awk '{print $2}')
local hit_rate=0
if [[ $read_requests -gt 0 ]]; then
hit_rate=$(echo "scale=2; ($read_requests - $reads) * 100 / $read_requests" | bc)
fi
# 主从复制延迟
local lag=0
local slave_status=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST"
-e "SHOW SLAVE STATUSG" 2>/dev/null || true)
if [[ -n "$slave_status" ]]; then
lag=$(echo "$slave_status" | grep "Seconds_Behind_Master" | awk '{print $2}')
fi
# 输出指标
echo "${timestamp},connections,${connections}"
echo "${timestamp},max_connections,${max_connections}"
echo "${timestamp},qps,${qps}"
echo "${timestamp},slow_queries,${slow_queries}"
echo "${timestamp},buffer_pool_hit_rate,${hit_rate}"
echo "${timestamp},replication_lag,${lag}"
# 判断告警
local connection_usage=$((connections * 100 / max_connections))
if [[ $connection_usage -gt 80 ]]; then
echo "警告: 连接数使用率 ${connection_usage}%"
send_alert "MySQL连接数过高: ${connections}/${max_connections}"
fi
if [[ $lag -gt 30 ]]; then
echo "警告: 主从复制延迟 ${lag}秒"
send_alert "MySQL主从延迟: ${lag}秒"
fi
if [[ $(echo "$hit_rate < 95.0" | bc) -eq 1 ]]; then
echo "警告: InnoDB缓冲池命中率 ${hit_rate}%"
send_alert "InnoDB缓存命中率低: ${hit_rate}%"
fi
}
生成慢查询报告
generate_slow_query_report() {
local slow_log="/var/log/mysql/slow.log"
if [[ ! -f "$slow_log" ]]; then
echo "慢查询日志不存在"
return
fi
echo "========================================"
echo "慢查询报告(最近1小时)"
echo "========================================"
# 统计慢查询数量
local count=$(grep -c "# Time:" "$slow_log" || echo "0")
echo "慢查询总数: ${count}"
# 统计平均查询时间
local avg_time=$(grep "# Query_time:" "$slow_log" |
sed 's/.Query_time: ([0-9.]).*/1/' |
awk '{sum+=$1; count++} END {if(count>0) print sum/count; else print 0}')
echo "平均查询时间: ${avg_time}秒"
# 显示Top 10最慢查询
echo ""
echo "Top 10最慢查询:"
grep "# Query_time:" "$slow_log" |
sed 's/.Query_time: ([0-9.]).*/1/' |
sort -rn | head -10
echo "========================================"
}
发送告警
send_alert() {
local message="$1"
echo "[告警] $(date '+%Y-%m-%d %H:%M:%S') - $message" >&2
# 发送通知
if command -v curl &>/dev/null; then
curl -X POST "http://localhost:18789/webhook/alert"
-H "Content-Type: application/json"
-d "{"text": "$message", "severity": "warning"}"
2>/dev/null || true
fi
}
主函数
main() {
mkdir -p "$(dirname "$LOG_FILE")"
# 采集指标
collect_mysql_metrics | while read line; do
echo "$(date +%s),${line#*,}" >> "$LOG_FILE"
done
# 生成慢查询报告
generate_slow_query_report
}
执行
if [[ "${1:-}" == "--report" ]]; then
generate_slow_query_report
else
main
fi
3.4 性能分析脚本
#!/bin/bash
performance-analyzer.sh - 性能分析脚本
功能:分析历史性能数据,生成报告
版本:v1.0
set -euo pipefail
配置
DATA_DIR="/var/log/metrics"
REPORT_DIR="/var/log/performance-reports"
创建报告目录
mkdir -p "$REPORT_DIR"
分析CPU趋势
analyze_cpu_trend() {
local data_file="${DATA_DIR}/system-metrics.log"
local period_hours="${1:-24}"
echo "========================================"
echo "CPU使用率趋势(最近${period_hours}小时)"
echo "========================================"
local cutoff_time=$(($(date +%s) - period_hours * 3600))
# 统计CPU使用率
awk -v cutoff="$cutoff_time" '$1 > cutoff && $2 == "cpu_usage=" {print $3}' "$data_file" |
awk '{
sum+=$1; count++
if($1>max) max=$1
if($1<min || min==0) min=$1
} END {
print "平均使用率:", sum/count, "%"
print "最高使用率:", max, "%"
print "最低使用率:", min, "%"
print "采样点数:", count
}'
echo "========================================"
}
分析内存趋势
analyze_memory_trend() {
local data_file="${DATA_DIR}/system-metrics.log"
local period_hours="${1:-24}"
echo "========================================"
echo "内存使用率趋势(最近${period_hours}小时)"
echo "========================================"
local cutoff_time=$(($(date +%s) - period_hours * 3600))
awk -v cutoff="$cutoff_time" '$1 > cutoff && $2 == "mem_usage=" {print $3}' "$data_file" |
awk '{
sum+=$1; count++
if($1>max) max=$1
if($1<min || min==0) min=$1
} END {
print "平均使用率:", sum/count, "%"
print "最高使用率:", max, "%"
print "最低使用率:", min, "%"
}'
# 检测内存泄漏(持续增长)
local first_usage=$(awk -v cutoff="$cutoff_time" '$1 > cutoff && $2 == "mem_usage=" {print $3; exit}' "$data_file")
local last_usage=$(awk -v cutoff="$cutoff_time" '$1 > cutoff && $2 == "mem_usage=" {print $3}' "$data_file" | tail -1)
local growth=$(echo "$last_usage - $first_usage" | bc)
echo ""
echo "内存增长:", growth, "%"
if [[ $(echo "$growth > 20" | bc) -eq 1 ]]; then
echo "警告: 内存使用率增长超过20%,可能存在内存泄漏"
fi
echo "========================================"
}
分析应用响应时间
analyze_response_time() {
local data_file="/var/log/app-metrics.log"
local period_minutes="${1:-60}"
echo "========================================"
echo "应用响应时间分析(最近${period_minutes}分钟)"
echo "========================================"
local cutoff_time=$(($(date +%s) - period_minutes * 60))
awk -v cutoff="$cutoff_time" '$1 > cutoff && $2 == "response_time" {print $3}' "$data_file" |
awk '{
sum+=$1; count++
if($1>max) max=$1
if($1<min || min==0) min=$1
if($1>3.0) slow_count++
} END {
print "平均响应时间:", sum/count, "秒"
print "最快响应时间:", min, "秒"
print "最慢响应时间:", max, "秒"
print "超时请求(>3秒):", slow_count, "次"
print "超时比例:", slow_count*100/count, "%"
}'
echo "========================================"
}
生成综合性能报告
generate_comprehensive_report() {
local report_file="${REPORT_DIR}/performance-report-$(date +%Y%m%d-%H%M%S).txt"
{
echo "========================================"
echo "系统性能综合报告"
echo "========================================"
echo "生成时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "服务器: $(hostname)"
echo ""
echo "========================================"
echo ""
# CPU分析
analyze_cpu_trend 24
echo ""
# 内存分析
analyze_memory_trend 24
echo ""
# 响应时间分析
analyze_response_time 60
echo ""
# 容量建议
echo "========================================"
echo "容量规划建议"
echo "========================================"
# 基于历史数据预测
local avg_cpu=$(awk '$2 == "cpu_usage=" {sum+=$3; count++} END {if(count>0) print sum/count; else print 0}' "${DATA_DIR}/system-metrics.log")
local max_cpu=$(awk '$2 == "cpu_usage=" {print $3}' "${DATA_DIR}/system-metrics.log" | sort -rn | head -1)
echo "当前平均CPU使用率: ${avg_cpu}%"
echo "历史峰值CPU使用率: ${max_cpu}%"
if [[ $(echo "$avg_cpu > 70" | bc) -eq 1 ]]; then
echo "建议: CPU使用率偏高,建议扩容或优化"
fi
if [[ $(echo "$max_cpu > 90" | bc) -eq 1 ]]; then
echo "建议: 历史CPU使用率超过90%,需要扩容"
fi
echo "========================================"
} | tee "$report_file"
echo "报告已生成: $report_file"
}
主函数
main() {
case "${1:-}" in
--cpu)
analyze_cpu_trend "${2:-24}"
;;
--memory)
analyze_memory_trend "${2:-24}"
;;
--response)
analyze_response_time "${2:-60}"
;;
--report)
generate_comprehensive_report
;;
*)
generate_comprehensive_report
;;
esac
}
执行
main "$@"
四、实战案例:性能问题定位
4.1 案例背景
某网站在高峰期响应时间从200ms恶化到5秒,但CPU、内存使用率都不高,问题难以定位。
4.2 排查过程
第1步:系统层面
第2步:应用层面
第3步:数据库层面
第4步:优化和验证
五、常见陷阱
陷阱1:监控数据量过大
解决方案:采样存储,仅保留聚合数据
陷阱2:告警风暴
解决方案:设置合理的阈值和持续时间
陷阱3:监控影响性能
解决方案:降低采集频率,异步处理
六、总结
30天行动计划
第1周:部署基础监控
第2周:应用监控
第3周:数据库监控
第4周:分析和优化