简介:这是一套面向运维人员与C# Winform开发者的Windows服务与IIS网站实时监测源码项目,针对业务网站或服务因不确定因素停止运行、影响业务功能的常见痛点,提供停止后自动重启的临时救场方案,为排查根因争取时间。项目基于C#语言与.NET Framework 4框架,使用Visual Studio 2022开发,包含可直接运行的监测程序与完整源码工程,支持二次开发。压缩包共70个文件,约373KB,以23个cs源码文件为核心,辅以exe可执行程序、dll类库、config配置、xml与txt说明文档、resx资源及sln解决方案等,结构完整。内容涵盖Winform窗体程序使用,以及IIS网站与应用程序池操作帮助类、Windows服务操作帮助类、文件操作类、日志操作类等模块,既可用于日常项目运维,也适合作为Winform入门学习案例。目前已有114人学习下载,适合需要快速搭建服务与网站守护机制的开发者参考借鉴。
1. Windows服务、IIS网站和应用程序池实时监测:为什么你的运维告警总是慢半拍
凌晨两点,业务方电话打过来:“网站打不开了。”你远程连上去一看,IIS应用程序池已经停止响应超过二十分钟,Windows服务里那个负责后台任务的OrderSyncService也早就挂了。可你的监控面板上一切正常——因为它只Ping了服务器IP,端口通着,但应用层早就凉了。
这就是典型的“监测盲区”:操作系统活着,IIS进程活着,但应用程序池的工作进程已经假死,或者某个关键Windows服务悄悄停止了。市面上很多所谓“免费python源码大全”里的监控脚本,大多只做端口探测或进程存活检查,根本触及不到IIS应用程序池的请求队列、工作进程状态、以及Windows服务的具体运行态。今天要聊的这套实时监测源码项目,核心就是解决三个层面的问题:Windows服务的运行状态与启动类型、IIS网站的响应可用性、以及应用程序池的实时健康度。适合谁?适合手里管着几台到几十台Windows Server、又不想为Zabbix或SCOM写复杂插件的运维和后端开发。读完你能拿到一套可落地的采集逻辑、参数配置和避坑清单。
2. 监测对象拆解:从服务状态到应用程序池的请求队列
2.1 Windows服务监测:别只看“正在运行”
Windows服务的状态远不止“正在运行”和“已停止”两种。真正要抓的是四个维度:Status(Running/Stopped/Paused)、StartType(Auto/Manual/Disabled)、ProcessId(是否真的绑定了进程)、以及ExitCode(上次退出码)。很多“windows无法启动windows installer服务”这类报错,根源就是服务依赖项没起来,或者启动账户密码过期。监测源码里通常用WMI或ServiceController类来采集,但WMI查询在服务数量多的时候会有性能损耗,我一般会走System.Management的ManagementObject异步查询,或者直接用PowerShell的Get-Service配合Get-CimInstance。
关键参数:采集间隔建议30秒到60秒,太短会加重WMI负担,太长会漏掉瞬时崩溃。对于关键服务(比如SQL Server Agent、Windows Update),可以单独设置15秒的高频采集。注意StartType为Auto但状态为Stopped的服务,这是最高优先级的告警项——它本该自启却没起来。
2.2 IIS网站监测:HTTP状态码之外的三个指标
IIS网站监测如果只发一个HTTP请求看200,那和普通拨测没区别。真正要抓的是:CurrentConnections(当前连接数)、BytesSent/sec(发送速率)、以及ServiceUptime(网站运行时长)。如果CurrentConnections突然归零但网站进程还在,说明应用程序池可能已经挂了。源码里一般通过IIS的Performance Counter或者Microsoft.Web.Administration命名空间来读取。
// 使用 Microsoft.Web.Administration 读取网站状态 using (ServerManager serverManager = new ServerManager()) { foreach (Site site in serverManager.Sites) { // 网站状态:Started / Stopped / Unknown Console.WriteLine($"站点: {site.Name}, 状态: {site.State}"); // 应用程序池名称 string appPoolName = site.Applications["/"].ApplicationPoolName; Console.WriteLine($"绑定应用程序池: {appPoolName}"); // 绑定信息(端口、主机头) foreach (Binding binding in site.Bindings) { Console.WriteLine($"绑定: {binding.Protocol}://{binding.EndPoint}"); } } }逻辑说明:ServerManager是IIS 7及以上版本的管理入口,需要引用Microsoft.Web.Administration程序集。site.State返回的是ObjectState枚举,Started不代表应用程序池健康,只代表网站配置已加载。参数上,serverManager.Sites是只读集合,遍历时不要做修改操作,否则会抛InvalidOperationException。
2.3 应用程序池监测:请求队列是核心指标
应用程序池是IIS最脆弱的环节。它的状态有Started、Stopped、Disabling、Disabled等,但真正反映健康度的是RequestQueueLength(请求队列长度)和CurrentWorkerProcesses(当前工作进程数)。如果队列长度持续大于0且工作进程数为0,说明应用程序池已经假死。源码里通常用System.Diagnostics.PerformanceCounter来读APP_POOL_WAS计数器组。
// 读取应用程序池的请求队列长度 PerformanceCounter queueCounter = new PerformanceCounter( "APP_POOL_WAS", // 计数器类别 "Current Requests Queued", // 计数器名称 "DefaultAppPool", // 实例名称(应用程序池名) true // 只读 ); // 读取工作进程数 PerformanceCounter workerCounter = new PerformanceCounter( "APP_POOL_WAS", "Current Worker Processes", "DefaultAppPool", true ); float queueLength = queueCounter.NextValue(); float workerCount = workerCounter.NextValue(); Console.WriteLine($"队列长度: {queueLength}, 工作进程数: {workerCount}");参数说明:APP_POOL_WAS计数器组在IIS 7+默认启用,如果读不到值,检查Windows Process Activation Service是否运行。NextValue()第一次调用返回0,需要连续调用两次取第二次的值。实例名称就是应用程序池名称,区分大小写。队列长度超过10就值得关注,超过50基本可以判定应用层阻塞。
3. 实时采集架构:用C#还是Python,以及数据怎么落盘
3.1 技术选型:为什么我最终选了C# + WMI + PerformanceCounter
网上搜“免费python源码大全”能找到一堆用psutil和wmi模块写的监控脚本,但Python在Windows上的WMI调用有几个硬伤:一是wmi模块依赖pywin32,在Server Core环境下安装麻烦;二是GIL导致多线程采集时性能上不去;三是长时间运行后内存泄漏比C#明显。我最后选的是C#控制台程序 + 定时器 + 异步采集,编译成单个exe丢到服务器上就能跑,不需要装运行时(用.NET Framework 4.8,Windows Server自带)。
核心采集循环用System.Threading.Timer,每个采集项独立一个Timer,避免一个慢查询拖垮整个采集周期。数据落盘用SQLite,单文件、零配置、支持并发读。表结构三张:ServiceStatus、SiteStatus、AppPoolStatus,每张表按时间戳建索引。
CREATE TABLE ServiceStatus ( Id INTEGER PRIMARY KEY AUTOINCREMENT, CollectTime DATETIME NOT NULL, ServiceName TEXT NOT NULL, DisplayName TEXT, Status TEXT, StartType TEXT, ProcessId INTEGER, ExitCode INTEGER ); CREATE INDEX idx_service_time ON ServiceStatus(CollectTime);3.2 采集频率与数据保留策略
采集频率不是越密越好。Windows服务的状态变化通常以分钟级为单位,30秒一次足够;IIS网站和应用程序池的队列长度变化快,建议15秒一次。但SQLite的写入频率要控制,我一般会在内存里攒够10条再批量插入,减少磁盘I/O。
数据保留策略:原始数据保留7天,之后按小时聚合保留30天,再之后按天聚合保留一年。聚合表用INSERT INTO ... SELECT配合GROUP BY做,不要用触发器,性能太差。如果服务器磁盘紧张,可以把SQLite文件放到D:\MonitorData\下,别放C盘。
提示:SQLite在并发写入时会锁库,采集程序用单线程写入,查询用只读连接加
PRAGMA journal_mode=WAL,能显著减少锁冲突。
3.3 告警触发逻辑:阈值怎么设才不误报
告警逻辑分三级:Info(记录但不通知)、Warning(邮件或钉钉)、Critical(短信或电话)。Windows服务:StartType=Auto且Status=Stopped直接Critical;StartType=Manual且Status=Stopped记Info。IIS网站:HTTP状态码非200连续3次Warning,连续5次Critical。应用程序池:RequestQueueLength > 10持续2个采集周期Warning,> 50持续2个周期Critical。
这里有个血泪经验:不要用单次采集值触发告警。网络抖动或WMI偶发超时会导致误报。我一般会加一个“连续N次满足条件”的滑动窗口,N默认3,关键服务可以调到5。告警恢复也要做,否则告警风暴能把人逼疯。
4. 避坑与排查:那些让你半夜爬起来的监测翻车现场
4.1 现象:WMI查询返回“拒绝访问”,但账户明明是管理员
原因:UAC远程限制。即使你是本地管理员,通过WMI远程查询时,UAC会过滤掉令牌。解决:在注册表HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System下把LocalAccountTokenFilterPolicy设为1(DWORD),然后重启WinRM服务。如果是域环境,用域管理员账户跑采集程序,并在防火墙里放行WMI的入站规则。
4.2 现象:PerformanceCounter读应用程序池时抛InvalidOperationException
原因:应用程序池名称拼写错误,或者该应用程序池从未启动过。APP_POOL_WAS计数器组只为“曾经启动过”的应用程序池创建实例。解决:先用Get-Counter -ListSet "APP_POOL_WAS"列出所有实例名,确认名称完全一致。如果应用程序池是新建的且从未启动,先手动启动一次再采集。
4.3 现象:采集程序运行几天后内存涨到几个G
原因:ManagementObjectSearcher和PerformanceCounter对象没有释放。这两个类都实现了IDisposable,必须用using包裹或者在finally里调Dispose()。另外,ManagementObjectCollection在遍历完后也要手动释放。我一般会在每次采集周期结束后强制GC.Collect(),虽然不优雅,但能压住内存。
4.4 现象:IIS网站监测返回200,但页面实际是错误页
原因:只检查了HTTP状态码,没检查响应内容。IIS的httpErrors配置可能把500错误重写成200的自定义错误页。解决:在监测请求里加一个Keyword校验,比如检查响应体里是否包含“登录”或“首页”等关键字。或者直接请求一个健康检查接口,返回JSON格式的{"status":"ok"}。
4.5 现象:告警邮件发不出去,SMTP超时
原因:服务器上的SMTP端口被防火墙拦了,或者用了错误的SMTP服务器。解决:别用System.Net.Mail同步发送,用SendMailAsync加超时。如果公司有内部邮件网关,优先走内部网关。实在不行,把告警写到本地日志文件,用另一个独立的脚本读日志发通知,解耦采集和通知。
5. 进阶技巧:用PowerShell做轻量级兜底监测与数据校验
5.1 为什么还需要PowerShell兜底
C#采集程序再稳,也有挂掉的时候。我习惯在每台服务器上再放一个PowerShell脚本,每5分钟跑一次,做两件事:一是检查采集程序进程是否存活,二是直接查一遍关键服务和应用池状态,和SQLite里的最新数据做比对。如果发现采集程序超过10分钟没写数据,或者PowerShell查到的状态和数据库里不一致,就发告警。这叫“监测的监测”。
# 轻量级兜底检查脚本 $ErrorActionPreference = "Stop" $dbPath = "D:\MonitorData\monitor.db" $lastWriteTime = (Get-Item $dbPath).LastWriteTime $minutesSinceWrite = (New-TimeSpan -Start $lastWriteTime -End (Get-Date)).TotalMinutes if ($minutesSinceWrite -gt 10) { Write-EventLog -LogName Application -Source "MonitorWatchdog" ` -EntryType Error -EventId 1001 ` -Message "采集程序超过10分钟未写入数据,最后写入时间:$lastWriteTime" } # 直接检查关键服务 $criticalServices = @("W3SVC", "WAS", "SQLSERVERAGENT") foreach ($svc in $criticalServices) { $service = Get-Service -Name $svc -ErrorAction SilentlyContinue if ($service.Status -ne "Running") { Write-EventLog -LogName Application -Source "MonitorWatchdog" ` -EntryType Error -EventId 1002 ` -Message "关键服务 $svc 状态异常:$($service.Status)" } }逻辑说明:Get-Item取SQLite文件的最后写入时间,如果超过10分钟没更新,说明采集程序可能卡死或崩溃。Get-Service直接查服务状态,不依赖WMI,速度快且稳定。Write-EventLog写入Windows事件日志,方便和现有运维体系集成。参数上,$criticalServices数组按实际环境改,W3SVC是IIS的核心服务,WAS是应用程序池的支撑服务。
5.2 数据校验:用SQL做交叉验证
采集到的数据不一定准。我一般会写几条SQL做交叉验证。比如,检查同一时间点,ServiceStatus表里Status='Running'的服务数量,和PowerShell直接查出来的数量是否一致。如果不一致,说明采集程序可能漏采或误采。
-- 检查最近一次采集的服务数量是否异常 SELECT CollectTime, COUNT(*) AS TotalServices, SUM(CASE WHEN Status = 'Running' THEN 1 ELSE 0 END) AS RunningCount FROM ServiceStatus WHERE CollectTime = (SELECT MAX(CollectTime) FROM ServiceStatus) GROUP BY CollectTime;如果RunningCount突然比平时少很多,比如从80掉到60,那要么是服务器真出问题了,要么是采集程序翻车了。这时候结合PowerShell的兜底结果一起看,就能快速定位。
5.3 一个具体技巧:用Get-Counter做实时采样对比
Get-Counter是PowerShell里最被低估的命令。它可以直接读性能计数器,和C#采集程序读的是同一套数据源。我习惯在排查问题时,用Get-Counter手动采一次,和数据库里的值对比。
# 实时采样应用程序池的请求队列 Get-Counter -Counter "\APP_POOL_WAS(DefaultAppPool)\Current Requests Queued" ` -SampleInterval 1 -MaxSamples 5如果Get-Counter读出来是0,但数据库里是50,那说明采集程序读错了实例名或者计数器路径。如果两边都是50,但网站确实打不开,那说明应用程序池真的堵了,得去查代码或数据库连接。
这套监测方案我用了三年多,从Windows Server 2012 R2到2022都跑过。最大的教训是:别追求大而全,先把Windows服务、IIS网站、应用程序池这三个核心指标采准,告警阈值调稳,比堆一堆花哨的图表有用得多。采集程序尽量用原生编译的语言写,别在服务器上装一堆运行时。兜底脚本一定要有,而且要和主程序走不同的技术栈,避免同时翻车。希望帮到你。
本文还有配套的精品资源,点击获取