本来今天元旦休息,谁知道突发信息来了,当服务器资源CPU过高时,突然"假死"了,访问特别慢,一直属于请求加载中,但是重启IIS后又恢复正常?
由于又遇上了假期,我决定先动手写个监控工具暂时"救火",专门盯着IIS接口站点,一旦发现请求超时并且重试几次后都是超时就自动重启IIS。现在就跟大家分享一下这个工具是怎么实现的。
简单说说我想要什么功能
- 定时去访问一下网站,看看能不能正常访问
- 如果连续访问失败几次,就自动重启IIS
本程序采用用.NET 9.0编写
- Windows Forms:虽然WPF更现代,但是WinForm简单啊,快速开发
- Microsoft.Web.Administration:微软官方库,操作IIS
xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Web.Administration" Version="11.1.0" />
</ItemGroup>
</Project>
第一个坑:权限问题
csharp
private static bool IsRunAsAdministrator()
{
try
{
var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
var principal = new System.Security.Principal.WindowsPrincipal(identity);
return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
}
catch
{
return false;
}
}
刚开始我写完代码测试,发现IIS重启不了!后来才想起来,重启IIS需要管理员权限。
这里增加了一个提示:程序启动时先检查权限,如果不是管理员就弹个提示框告诉用户"请用管理员身份运行",并且退出程序。
核心功能:怎么检查网站状态?
csharp
private async Task CheckWebsite()
{
var url = txtUrl.Text.Trim();
var stopwatch = Stopwatch.StartNew();
try
{
// 设置超时时间
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(_timeout));
var response = await _httpClient.GetAsync(url, cts.Token);
stopwatch.Stop();
var responseTime = stopwatch.ElapsedMilliseconds;
if (response.IsSuccessStatusCode)
{
// 网站正常
_consecutiveFailures = 0;
WriteLog($"✓ 网站OK - {url}, 响应时间: {responseTime}ms");
}
else
{
// 网站异常,计数失败次数
_consecutiveFailures++;
if (_consecutiveFailures >= _maxFailures)
{
await RestartIIS(); // 重启IIS
}
}
}
catch (TaskCanceledException)
{
// 超时了
_consecutiveFailures++;
}
catch (HttpRequestException)
{
// 连接失败了
_consecutiveFailures++;
}
}
这里有几个技术点:
1. 异步编程,避免界面卡死
用async/await的好处就是,程序在等网站响应的时候,界面还能正常操作,不会卡死在那里。
就像你煮饭的时候,你可以去看电视,不用一直在厨房等着。
2. 超时控制
用CancellationTokenSource设置超时时间,如果网站10秒内没响应,就当失败处理。
这个很重要,万一网站真的卡死了,总不能让程序一直等下去吧?
3. 失败计数
不是失败一次就重启,而是连续失败3次(可配置)才重启。这样可以避免因为网络抖动导致的重启。
就像判断一个人是不是生病,不能打一个喷嚏就说他感冒了,得连续打几个喷嚏才行。
最有意思的部分:如何重启IIS?
csharp
private async Task<bool> RestartAppPoolAppCmd(string appPoolName)
{
string appCmdPath = @"C:\Windows\System32\inetsrv\appcmd.exe";
// 方法1:尝试回收应用程序池
var processInfo = new ProcessStartInfo
{
FileName = appCmdPath,
Arguments = $"recycle apppool /apppool.name:\"{appPoolName}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using var process = Process.Start(processInfo);
var output = await process.StandardOutput.ReadToEndAsync();
await process.WaitForExitAsync();
if (process.ExitCode != 0)
{
// 回收失败,尝试停止+启动
return await StopAndStartAppPool(appPoolName);
}
return true;
}
我用了两种重启方法:
方法1:回收应用程序池
用appcmd.exe命令回收,这是最优雅的方式,就像给应用程序池洗个澡一样。
方法2:停止+启动
如果回收失败,就先停止再启动,虽然粗暴一点,但是很有效。
这就像修电脑一样,能重启解决的就不重装,能重装解决的就不换硬件。
日志系统:怎么做到不卡界面的?
csharp
private readonly Channel<Tuple<string, Color>> _channelLog;
public void WriteLog(string msg)
{
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
var logMessage = $"[{timestamp}] {msg}";
_channelLog.Writer.TryWrite(new Tuple<string, Color>(logMessage, Color.White));
}
这里我用了Channel<T>,这是个生产者和消费者的模式:
- 生产者:各个功能模块往Channel里写日志
- 消费者:一个单独的任务从Channel里读日志并显示到界面上
这样写的目的是避免多线程同时操作界面导致的卡顿。
就像餐厅里,服务员(生产者)负责接单,厨师(消费者)负责做菜,分工明确,效率高。
内存泄漏防护:让程序跑得更久
csharp
// 限制日志行数
if (txtBoxResultInfo.Lines.Length > MaxLines)
{
int removeCount = txtBoxResultInfo.Lines.Length - MaxLines;
int endIndex = txtBoxResultInfo.GetFirstCharIndexFromLine(removeCount);
txtBoxResultInfo.Select(0, endIndex);
txtBoxResultInfo.SelectedText = "";
}
// 定期垃圾回收
if (DateTime.Now.Minute % 30 == 0 && DateTime.Now.Second < 10)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
长时间运行的程序最怕内存泄漏,所以我加了这两个措施:
- 限制日志显示行数:只保留最新的1000行,删掉旧的
- 定期垃圾回收:每30分钟强制回收一次内存
就像家里的垃圾桶,不能只往里扔不倒,得定期清理。
用户界面:让监控变得可视化
界面分几个部分:
- 监控配置区:设置URL、检查间隔、超时时间等
- 实时状态区:显示当前状态、响应时间、失败次数
- 操作控制区:开始/停止监控、手动重启IIS
- 日志显示区:显示详细的操作日志
界面用的是Windows Forms,虽然看起来不是特别现代,但是稳定可靠,而且开发速度快。
踩过的坑和学到的经验
1. UI线程安全问题
多线程更新UI时一定要用Invoke,否则会报"线程间操作无效"的错误。
2. 异常处理要分类
不同的异常有不同的处理方式,超时、连接失败、服务器错误都要区别对待。
3. 资源释放要彻底
HttpClient、Process这些都要用using语句包装,自动释放资源。
4. 日志不能无限增长
长期运行的程序,日志会越来越多,必须限制大小。
最终效果

写在最后
一开始觉得很简单,后来发现要考虑的细节越来越多:权限、超时、异常、日志、内存...每一个都需要仔细设计。希望这篇文章能给大家一些启发!