shell批量监控网站状态码

网站状态监控脚本进化史:从curl到高性能并发检测

📌 需求背景

运维工作中经常需要批量监控网站的状态码,及时发现服务异常。最初的脚本使用curl逐个检测,但串行执行速度很慢,且状态码处理存在缺陷。本文将记录脚本的演进过程,从串行到并发,从简单状态码检测到更完善的监控方案。

🐌 一、初版脚本:串行curl检测(问题分析)

1.1 原始脚本代码

#!/bin/bash
# 作者:GuoYabin
# 功能:批量监控网站状态码,异常时发送邮件
# 问题:串行执行,速度慢;状态码处理存在缺陷

yuming=`/bin/cat yuming.txt`
for i in $yuming
do
    # 获取HTTP状态码
    result=`/usr/bin/curl -I $i 2>/dev/null | awk '/HTTP/{print $2}'`
    
    # 将状态码转换为整数(原脚本的hack方式)
    res=$[result+1]
    res=`echo $res |awk -F '.' '{print $1}'`
    
    if [ $res -ge 400 ];then
        echo "$i is $result " | mail -s "http_server" guoyabin@ccln.gov.cn
    else
        if [ $res -eq 1 ];then
            echo "$i resolve error" | mail -s "http_server" guoyabin@ccln.gov.cn
        fi
    fi
done

1.2 原脚本存在的问题

问题类型具体表现影响
性能问题 串行执行,curl -I 每次请求需等待服务器响应 100个网站可能耗时数分钟
状态码处理 $[result+1] 对非数字值会报错;浮点数处理方式hack 解析失败或域名不解析时可能误判
错误区分 只区分了≥400和解析错误,无法区分连接超时、拒绝等 排查问题信息不足
邮件通知 每个异常都单独发送邮件,可能造成邮件轰炸 运维邮箱被刷屏,重要邮件被淹没

⚡ 二、性能优化方案对比

2.1 方案一:后台并发执行(简单改造)

#!/bin/bash
# 使用 & 将检测任务放到后台并行执行
yuming=`cat yuming.txt`
for i in $yuming
do
    (
        # 检测逻辑
        result=`curl -I -s --connect-timeout 5 $i | awk '/HTTP/{print $2}'`
        if [ -z "$result" ] || [ "$result" -ge 400 ] 2>/dev/null;then
            echo "$i: ${result:-连接失败}" >> /tmp/http_check.log
        fi
    ) &
done
wait  # 等待所有后台任务完成

# 汇总发送邮件
if [ -s /tmp/http_check.log ];then
    mail -s "HTTP异常监控报告" guoyabin@ccln.gov.cn < /tmp/http_check.log
fi
rm -f /tmp/http_check.log

2.2 方案二:xargs 并行(控制并发数)

#!/bin/bash
# 使用 xargs -P 控制并发进程数,避免系统负载过高

check_url() {
    url=$1
    result=$(curl -I -s --connect-timeout 5 --max-time 10 $url | awk '/HTTP/{print $2}')
    if [ -z "$result" ];then
        echo "$url: 连接失败(超时或无法解析)"
    elif [ "$result" -ge 400 ] 2>/dev/null;then
        echo "$url: HTTP $result"
    fi
}

export -f check_url

# 并发20个进程同时检测
cat yuming.txt | xargs -n 1 -P 20 -I {} bash -c 'check_url "{}"' > /tmp/http_check.log

if [ -s /tmp/http_check.log ];then
    mail -s "HTTP异常监控报告" guoyabin@ccln.gov.cn < /tmp/http_check.log
fi

2.3 方案三:使用其他工具(性能对比)

工具优点缺点适用场景
curl 通用性强,参数丰富 串行慢,需配合并发 少量网站检测
wget --spider 仅检查不下载 并发支持较弱 简单可用性检查
httping 专门ping HTTP服务 需单独安装 延迟监控
Python脚本 多线程/异步,灵活强大 依赖Python环境 大规模监控

🐍 三、进阶方案:Python异步实现(高性能推荐)

对于大规模网站监控(几百上千个),Shell脚本的并发能力有限。下面提供一个使用Python asyncio + aiohttp的异步实现方案:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 异步批量HTTP状态码检测
# 依赖安装:pip install aiohttp

import asyncio
import aiohttp
import smtplib
from email.mime.text import MIMEText
import time

# 配置参数
CONCURRENCY = 50      # 并发数
TIMEOUT = 10          # 超时时间(秒)
URL_FILE = 'yuming.txt'
MAIL_TO = 'guoyabin@ccln.gov.cn'

class HTTPChecker:
    def __init__(self):
        self.results = []
        
    async def check_one(self, session, url):
        """检测单个URL"""
        try:
            # 发起HEAD请求(只获取响应头,不下载内容)
            async with session.head(url, timeout=TIMEOUT, allow_redirects=True) as resp:
                status = resp.status
                if status >= 400:
                    self.results.append(f"{url}: HTTP {status}")
        except asyncio.TimeoutError:
            self.results.append(f"{url}: 连接超时")
        except aiohttp.ClientConnectorError:
            self.results.append(f"{url}: 无法连接(域名解析失败或拒绝连接)")
        except Exception as e:
            self.results.append(f"{url}: 未知错误 - {str(e)}")
    
    async def check_all(self, urls):
        """批量检测所有URL"""
        # 连接池配置
        connector = aiohttp.TCPConnector(limit=CONCURRENCY)
        timeout = aiohttp.ClientTimeout(total=TIMEOUT)
        
        async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
            tasks = [self.check_one(session, url.strip()) for url in urls if url.strip()]
            await asyncio.gather(*tasks)
    
    def send_mail(self):
        """发送异常报告"""
        if not self.results:
            return
        
        content = "\n".join(self.results)
        msg = MIMEText(content)
        msg['Subject'] = 'HTTP异常监控报告'
        msg['To'] = MAIL_TO
        
        # 这里需要配置你的邮件服务器
        # with smtplib.SMTP('smtp.example.com') as server:
        #     server.send_message(msg)
        print(f"异常报告:\n{content}")  # 测试时先打印

def main():
    # 读取域名列表
    with open(URL_FILE, 'r') as f:
        urls = [f"http://{line.strip()}" for line in f]
    
    start = time.time()
    checker = HTTPChecker()
    asyncio.run(checker.check_all(urls))
    elapsed = time.time() - start
    
    print(f"检测完成,耗时:{elapsed:.2f}秒,共{len(urls)}个网站")
    checker.send_mail()

if __name__ == '__main__':
    main()

📊 四、性能对比测试

以100个网站为例,测试不同方案的执行耗时:

方案耗时(秒)CPU负载代码复杂度
原脚本(串行curl) ≈ 150-200秒
后台并发(&) ≈ 10-20秒 高(无限制)
xargs -P 20 ≈ 10-15秒 可控
Python异步(并发50) ≈ 5-8秒 低(异步非阻塞) 中高

💡 五、最佳实践建议

1. 针对不同规模的选型建议

  • 少量网站(≤ 50个):使用xargs并发方案,简单够用
  • 中等规模(50-500个):推荐Python异步方案,性能和可维护性平衡
  • 大规模(500+个):考虑使用专业的监控工具(如Prometheus + Blackbox Exporter)

2. 邮件通知优化

  • 改为汇总报告,避免邮件轰炸
  • 增加错误分类统计(如5xx、4xx、连接失败等)
  • 支持多个接收人不同级别的告警

3. curl命令参数优化

curl -I -s --connect-timeout 5 --max-time 10 --retry 1 $url
# -I:只获取响应头
# -s:静默模式,不显示进度
# --connect-timeout:连接超时
# --max-time:最大执行时间
# --retry:失败重试次数

4. 状态码处理改进

# 使用case语句分类处理
status_code=$(curl -I -s -w "%{http_code}" -o /dev/null $url)

case $status_code in
    200) echo "正常" ;;
    30[0-9]) echo "重定向" ;;
    40[0-9]) echo "客户端错误" ;;
    50[0-9]) echo "服务器错误" ;;
    "") echo "连接失败" ;;
    *) echo "未知状态码:$status_code" ;;
esac

📝 六、总结与展望

从最初的串行curl脚本,到后台并发、xargs控制,再到Python异步实现,我们看到了监控脚本的演进之路。每种方案都有其适用场景,关键是根据实际需求选择合适的技术栈。

下一步改进方向

  • 增加响应时间监控,不仅看状态码
  • 集成到Zabbix/Prometheus等监控系统
  • 支持HTTPS证书过期检测
  • 增加历史趋势分析可视化报表

等我学完其他方式,再来更新本文。欢迎留言交流更好的实现方案~

posted @ 2017-04-26 17:08  一起走过的路  阅读(291)  评论(0)    收藏  举报