快速测试多个ping框的最佳方法是什么
本文关键字:最佳 方法 是什么 ping 测试 | 更新日期: 2023-09-27 18:20:26
我有一个应用程序,可以监视和控制一堆计算机(可能是3到35台左右,可能是本地计算机)。
我监视的一件事是正常运行时间/ping状态。应用程序的目的之一是重新启动盒子,有时它们会因为其他原因重新启动。
我希望能够快速获取可Ping/不可Ping的更改。
我在一根线上绕了一圈。
在我看来,阻止ping会阻止它更新一段时间,即使你并行运行它(阻止一个盒子的ping阻止另一个盒子)
(并行实现示例,请注意以下只是我的想法,尚未实现,可能包含错误)
var startTime = DateTime.Now;
var period = TimeSpan.FromSeconds();
Parallel.ForEach(boxes, (box) =>
{
var now = DateTime.Now;
var remainingTime = (now - startTime) - period;
while(remainingTime > TimeSpan.Zero)
{
box.CanPing.TryUpdate();
}
});
其中TryUpdate类似于
using(ping = new Ping())
{
var reply = ping.Send (IP);
bool upStatus = (reply.Status == IPStatus.Success);
this.Value = upStatus;
}
或者,我尝试使用多个SendAsync(一次多个异步ping)来尽快发现正常运行时间,并在SendAsync 的回调中双重检查锁定
if(upStatus != this.Value)
{
lock(_lock)//is it safe to have a non static readonly lock object, all the examples seem to use a static object but that wouldn't scale to locking in multiple instances of the containing class object
{
if(upStatus != this.Value)
{
...
}
}
}
这是一个可怕的内存泄漏,但这可能是因为我太快地进行了太多异步ping调用(每个调用都带有一个线程),并且没有处理ping。如果我把自己限制在每台计算机一次3个,或者在中间暂停更长的时间,并处理()ping,你认为这是个好主意吗?
什么是更好的策略?还有其他想法吗?
这是多线程的一个特殊情况,在这种情况下,您不需要线程来使程序更快,您需要使它更具响应性。您的操作几乎不需要计算能力。因此,我不会害怕为每台被监控的计算机创建一个线程。无论如何,他们大部分时间都在做sleep()
。它们应该创建一次,因为线程创建实际上是这里最昂贵的事情。
我会创建这样的对象层次结构:
GUIProxy
-将处理所有的gui操作,比如更改coputer名称旁边的通知颜色HostManager
-将注册新机器,删除旧机器,对Monitors
执行定时检查HostMonitor
-将周期性地、顺序地向检查计算机发送ping。稍后会详细了解它的行为
检查算法
在局域网中,大多数ping在发送后1-2毫秒内返回。在互联网上,时间可能会有所不同。根据机器的位置,我会为每个Monitor
分别设置两个ping时间阈值。当局域网ping大于5ms或互联网ping>200ms时,一个是"警告"阈值(GUI中的黄灯或其他)。第二个是"错误"阈值,局域网>1s,互联网>2s或什么。每个Monitor
将发送ping,等待应答,并在接收到应答后发送另一个ping。它应该存储lastPingSendTime
、lastPingReceiveTime
和currentPingSendTime
。前者用于确定延迟,后者用于检查HostManager
中的延迟。当然,Monitor
应该正确地处理超时和其他系统/网络事件。
在同样在单个线程上运行的HostManager
中,我会检查每个监视器上的currentPingSendTime
,并将其与该监视器的阈值进行比较。如果超过阈值,则会通知GUIProxy
在GUI中显示情况。
优点
- 你自己控制线程
- 可以使用同步(更简单)的ping方法
Manager
不会挂起,因为它异步访问监视器- 你可以实现一个抽象的监视器接口,可以用来监视其他东西,而不仅仅是计算机
的缺点
- 正确的
Monitor
线程实现可能并不简单
根据您是否需要扩展解决方案,您可以实现Dariusz所说的状态检查(这是一种绝对合法的方法)。
这种方法只有一个缺点,在您的场景中可能相关,也可能不相关:扩展到成百上千个受监控的盒子或服务将导致大量的线程。关于64位模式下的.net应用程序支持数千个并发线程的事实,我不建议产生那么多工作线程。如果你给资源调度程序安排如此大量的工人,他将不再是你最好的朋友。
为了得到一个可扩展的解决方案,这有点困难。让我们简单地回到最初的问题:您想要快速监控一堆盒子,而流水线处理执行不好。考虑到您将来可能会监视其他服务(tcp)也在等待超时,这将完全扼杀这种方法。
解决方案:自定义线程池或线程重用
当你处理一种特殊类型的线程时,它会受到从默认线程池派生线程的时间的影响,需要一个解决方案来解决派生问题。考虑到能够扩大规模,我建议这样做:
使用自定义或默认线程池生成几个处于挂起状态的线程。现在你是系统想要测量几个盒子。因此:找到预先预热的线程,取第一个挂起/空闲的线程,并将其保留给您的监控作业。在您获得用于您的用途的线程后,您将为他提供某种实际工作者方法的句柄(该方法将由线程异步调用)。监视迭代完成后(这可能需要一些时间),线程返回结果(好的方法是回调)并将自己设置为挂起模式。
因此,这只是一个带有预热线程的自定义调度器。如果您使用ManualResetEvents构建挂起/恢复,线程几乎立即可用。
还想要更多性能吗
如果您仍在获得更高的性能,并且希望能够以更细粒度的方式调整您的系统,我建议您使用专门的线程池(就像zabbix为监控所做的那样)。因此,你不只是分配一堆线程,这些线程可能会调用一个自定义方法来检查一个框是否可以通过ping或tcp访问,你还为每个监控类型分配了一个单独的池。因此,在icmp(ping)和tcp监控的情况下,您将创建至少两个线程池,其中线程已经包含关于"如何检查"的基本知识。在ping监视器的情况下,线程将准备好并等待初始化的ping实例,该实例只是在等待目标进行检查。当您将线程从挂起状态移除时,它会立即检查主机并返回结果。之后,它为睡眠做准备(在这种情况下,它已经为下一次运行初始化了环境)。如果您以良好的方式实现这一点,您甚至可以重用套接字等资源。
总之,这种方法使您能够监控3、35甚至数百个盒子,而不会遇到麻烦。当然,监控仍然是有限的,你不应该分叉数千个预热线程。这并不是背后的想法:这个想法是,您已经定义了可供使用的线程的最大数量,这些线程只是等待检查目的地。在为多个主机启动监控时,您不必处理分叉问题-如果您的监控超出了您定义的并发允许范围,您只需要处理排队问题(这可能远高于Parallel.ForEach,默认情况下每个内核最多产生一个线程!检查方法的过载以增加这个数量。)
绝对优化
如果你仍然愿意进一步改进系统,请获得你的调度器和资源规划器,而不仅仅是预热线程的计数。给他限制,比如最少4个,最多42个线程。调度器会考虑在这些边界内启动和停止其他线程。如果您的系统在夜间降低了监控速率,并且您不希望挂起的线程挂起,这将非常有用。
这将是A+实施,因为您不仅能够立即从冷状态开始监视至少某些主机,而且能够快速地从冷状态启动监视许多主机,您还可以返还长期不需要的资源。
由于这似乎是应用程序的一项专门任务,我同意自己管理用于特定任务的线程数量可能是有意义的。
此外,在您的流程中似乎有许多阶段:
- 提供下一个要检查的地址
- 正在确定检查时要使用的超时。使用超时可能取决于几个因素,包括地址在上次检查中是否被确定为无响应,它的响应时间通常是多少,以及,正如Dariusz所提到的,如果它在LAN、extranet、internet上
- 执行ping
- 处理和解释ping回复与之前的回复状态和累积状态(例如,更新地址的统计信息,甚至可能存储这些信息)
- 对(重复)无响应发出"警报"
- 正在发出重新启动命令
因此,如果您使用前一阶段产生的输出清除了可以独立执行的阶段,您可以选择SEDA(阶段事件驱动架构)类型的解决方案,在该解决方案中,您可以为每个阶段分配多个专用线程。并且对于流经各阶段的特定信息项目,可以使用提供者/生产者/消费者角色来将各阶段彼此连接,其中存在ProducerConsumerQueues来吸收临时不匹配(peek负载)和自动节流(例如,对ping的太多未决请求将阻塞ping请求的生产者,直到执行ping的消费者已经充分赶上为止)。
对于"Ping流"的基本结构,您可能有以下阶段:
- "PingRequest"生产者阶段,由IPAddresses的提供者提供,并使用工厂创建请求(因此工厂可以根据IPAddress的历史记录和上次已知状态来确定请求的超时)。它将请求传递给"PingRequests"的连接使用者
- "Pinger"阶段从其使用者队列中检索PingRequest,执行Ping并将结果传递给"PingResults"的连接使用者
- "ResultProcessor"阶段从其使用者队列中检索PingResults,更新IPAddress的状态,并将结果传递给"PingStatus"的连接使用者
在阶段3之后,您可能希望以相同的方式添加阶段,以生成警报、重新启动请求等。
这些阶段中的每一个都可以被分配一个专用数量的线程,并且在对流进行更改时可以非常灵活。
几个代码示例来说明:
/// <summary>
/// Coordinates and wires up the processing pipeline.
/// </summary>
public class PingModule : IConsumer<PingStatus>
{
private readonly ConcurrentDictionary<IPAddress, PingStatus> _status = new ConcurrentDictionary<IPAddress,PingStatus>();
private readonly CancellationTokenSource _cancelTokenSource;
private readonly PingRequestProducerWorkStage _requestProducer;
private readonly PingWorkStage _pinger;
private readonly PingReplyProcessingWorkStage _replyProcessor;
public PingModule(IProvider<IPAddress> addressProvider)
{
_cancelTokenSource = new CancellationTokenSource();
_requestProducer = new PingRequestProducerWorkStage(1, addressProvider, NextRequestFor, _cancelTokenSource.Token);
_pinger = new PingWorkStage(4, 10 * 2, _cancelTokenSource.Token);
_replyProcessor = new PingReplyProcessingWorkStage(2, 10 * 2, _cancelTokenSource.Token);
// connect the pipeline.
_requestProducer.ConnectTo(_pinger);
_pinger.ConnectTo(_replyProcessor);
_replyProcessor.ConnectTo(this);
}
private PingRequest NextRequestFor(IPAddress address)
{
PingStatus curStatus;
if (!_status.TryGetValue(address, out curStatus))
return new PingRequest(address, IPStatus.Success, TimeSpan.FromMilliseconds(120));
if (curStatus.LastResult.TimedOut)
{
var newTimeOut = TimeSpan.FromTicks(curStatus.LastResult.TimedOutAfter.Ticks * 2);
return new PingRequest(address, IPStatus.TimedOut, newTimeOut);
}
else
{
var newTimeOut = TimeSpan.FromTicks(curStatus.AverageRoundtripTime + 4 * curStatus.RoundTripStandardDeviation);
return new PingRequest(address, IPStatus.Success, newTimeOut);
}
}
// ...
}
现在可以很容易地修改这个管道。例如,您可能决定要有2或3个并行的"Pinger"阶段流,其中一个为先前断开连接的地址提供服务,一个为"慢速响应者"提供服务,另一个为其余地址提供服务。这可以通过将阶段1连接到执行此路由的消费者,并将PingRequest传递到正确的"Piner"来实现。
public class RequestRouter : IConsumer<PingRequest>
{
private readonly Func<PingRequest, IConsumer<PingRequest>> _selector;
public RequestRouter(Func<PingRequest, IConsumer<PingRequest>> selector)
{
this._selector = selector;
}
public void Consume(PingRequest work)
{
_selector(work).Consume(work);
}
public void Consume(PingRequest work, CancellationToken cancelToken)
{
_selector(work).Consume(work, cancelToken);
}
}
public class PingModule : IConsumer<PingStatus>
{
// ...
public PingModule(IProvider<IPAddress> addressProvider)
{
_cancelTokenSource = new CancellationTokenSource();
_requestProducer = new PingRequestProducerWorkStage(1, addressProvider, NextRequestFor, _cancelTokenSource.Token);
_disconnectedPinger = new PingWorkStage(2, 10 * 2, _cancelTokenSource.Token);
_slowAddressesPinger = new PingWorkStage(2, 10 * 2, _cancelTokenSource.Token);
_normalPinger = new PingWorkStage(3, 10 * 2, _cancelTokenSource.Token);
_requestRouter = new RequestRouter(RoutePingRequest);
_replyProcessor = new PingReplyProcessingWorkStage(2, 10 * 2, _cancelTokenSource.Token);
// connect the pipeline
_requestProducer.ConnectTo(_requestRouter);
_disconnectedPinger.ConnectTo(_replyProcessor);
_slowAddressesPinger.ConnectTo(_replyProcessor);
_normalPinger.ConnectTo(_replyProcessor);
_replyProcessor.ConnectTo(this);
}
private IConsumer<PingRequest> RoutePingRequest(PingRequest request)
{
if (request.LastKnownStatus != IPStatus.Success)
return _disconnectedPinger;
if (request.PingTimeOut > TimeSpan.FromMilliseconds(500))
return _slowAddressesPinger;
return _normalPinger;
}
// ...
}
我知道这是编码问题的结束,但你是否考虑过使用NagiOS或smokping或其他开源监控解决方案?这些功能可以快速检测连接的下降,而且可能还有许多其他你可能不想自己使用的功能。