Какой лучший способ проверить несколько ящиков для пинга быстро
У меня есть приложение, в котором я наблюдаю и контролирую несколько компьютеров (вероятно, от 3 до 35 или около того, возможно, локально).
Одна из вещей, которые я отслеживаю, это состояние готовности / проверки связи. Одна из целей приложения - перезапустить блоки, иногда они перезапускаются по другим причинам.
Я хотел бы иметь возможность быстро воспринимать изменения, которые можно пинговать / не пинговать.
У меня есть петля вращения на потоке.
Мне кажется, что блокирующий пинг не позволяет обновить его на некоторое время, даже если вы запускаете его параллельно (не позволяйте одному из блоков пинга блокировать другой)
(пример параллельной реализации, обратите внимание, что нижеприведенное только из головы и не было реализовано, может содержать ошибки)
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 (несколько асинхронных пингов одновременно), чтобы как можно быстрее обнаружить время безотказной работы с двойной проверкой блокировки при обратном вызове 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)
{
...
}
}
}
это была ужасная утечка памяти, но это может быть потому, что я слишком быстро выполняю слишком много асинхронных пинг-вызовов (каждый из которых идет с потоком), а не избавляюсь от пинга. Если я ограничу себя тремя компьютерами за раз или поставлю более длинную паузу посередине, а Dispose() ping, как вы думаете, это будет хорошей идеей?
Какая стратегия лучше? Есть другие идеи?
4 ответа
Это особый случай многопоточности, когда вам не нужны шаги, чтобы сделать программу быстрее, вам нужно сделать ее более отзывчивой. Ваши операции практически не требуют вычислительной мощности. Поэтому я бы не боялся создавать отдельный поток для каждого контролируемого компьютера. Они собираются делать sleep()
во всяком случае, большую часть времени. Они должны быть созданы один раз, потому что создание потока на самом деле самая дорогая вещь здесь.
Я хотел бы создать иерархию объектов следующим образом:
GUIProxy
- будет обрабатывать все операции с графическим интерфейсом, такие как изменение цвета уведомлений рядом с именем компьютераHostManager
- зарегистрировать новые машины, удалить старые, выполнить проверку синхронизацииMonitors
HostMonitor
- периодически, последовательно отправлять пинги для проверки компьютеров. Подробнее о его поведении позже
Алгоритм проверки
В локальных сетях большая часть времени возвращается через 1-2 мс после отправки. Через Интернет время может отличаться. Я бы хотел установить два порога времени пинга отдельно для каждого Monitor
в зависимости от местоположения машины. Один из них будет порогом "предупреждение" (желтый индикатор или sth в графическом интерфейсе), когда пинг локальной сети больше 5 мс или Интернет-пинг> 200 мс. Вторым порогом будет "ошибка" с LAN>1 и Интернетом> 2 или STH. каждый Monitor
отправит пинг, дождется ответа и отправит другой пинг после получения ответа. Должен хранить lastPingSendTime
, lastPingReceiveTime
а также currentPingSendTime
, Первые предназначены для определения задержки, а последние - для проверки задержки в HostManager
, Конечно Monitor
должен правильно обрабатывать таймауты и другие системные / сетевые события.
В HostManager
также работает на одном потоке, я бы проверил currentPingSendTime
на каждом мониторе и сравните его с пороговыми значениями этого монитора. Если порог пересекается, GUIProxy
будет уведомлен, чтобы показать ситуацию в GUI.
преимущества
- вы сами контролируете темы
- Вы можете использовать синхронный (более простой) метод пинга
Manager
не будет зависать, так как он обращается к мониторам асинхронно- Вы можете реализовать абстрактный интерфейс монитора, который вы можете использовать для мониторинга других вещей, не только компьютеров
Недостатки
- правильный
Monitor
реализация потоков не может быть простой
В зависимости от того, требуется ли вам решение для горизонтального масштабирования, вы можете выполнить проверку состояния, как сказал Дариуш (это абсолютно законный подход).
У этого подхода есть только один недостаток, который может или не может иметь значение в вашем сценарии: расширение до сотен или даже до тысячи отслеживаемых ящиков или служб приведет к огромному количеству потоков. Что касается того факта, что приложения.net в 64-битном режиме поддерживают несколько тысяч параллельных потоков, я бы не рекомендовал создавать такое количество рабочих. Планировщик ресурсов больше не будет вашим лучшим другом, если вы дадите ему задание для планирования такого огромного количества работников.
Чтобы получить масштабируемое решение, это немного сложнее. Давайте вкратце вернемся к исходной проблеме: вы хотите быстро отслеживать несколько блоков, а конвейерная обработка неэффективна. Если в будущем вы сможете отслеживать другие службы (tcp), также ожидая тайм-аутов, это полностью убьет этот подход.
Решение: Пользовательский пул потоков или их повторное использование
Поскольку вы имеете дело со специальным видом потоков, на который влияет время, когда поток создается из пула потоков по умолчанию, необходимо решение, чтобы избавиться от проблемы появления. имея в виду возможность масштабирования, я бы порекомендовал этот способ:
Используйте пользовательский пул или пул потоков по умолчанию, чтобы создать несколько потоков, которые находятся в приостановленном состоянии. Теперь ваша система хочет измерить несколько ящиков. Поэтому: получите к вам предварительно нагретые темы и возьмите первую приостановленную / свободную и зарезервируйте ее для своей работы по мониторингу. После того, как вы получили поток для своего использования, вы даете ему своего рода дескриптор вашего фактического рабочего метода (который будет вызываться потоком асинхронно). После завершения итерации мониторинга (что может занять некоторое время) потоки возвращают результат (хорошим способом будет обратный вызов) и переводят себя в режим ожидания.
Так что это просто пользовательский планировщик с предварительно нагретыми потоками. Если вы создаете приостановку / возобновление с помощью ManualResetEvents, потоки становятся доступны практически мгновенно.
Все еще хотите больше производительности?
Если вы все еще стремитесь к немного большей производительности и хотите более детально настроить полученную Систему, я бы порекомендовал специализированные пулы потоков (как это делает zabbix для мониторинга). Таким образом, вы не просто назначаете группу потоков, которые могут вызывать пользовательский метод, чтобы проверить, достижим ли ящик с помощью ping или tcp, вы назначаете отдельный пул для каждого типа мониторинга. Таким образом, в случае мониторинга icmp (ping) и tcp вы должны создать как минимум два пула потоков, в которых потоки уже содержат базовые знания о том, "как проверять". В случае монитора ping поток будет готов и будет ждать с инициализированным экземпляром ping, который просто ожидает проверки цели. Когда вы выводите поток из приостановленного состояния, он немедленно проверяет хост и возвращает результат. после этого он готовится ко сну (и в этом случае инициализирует Среду для следующего запуска). Если вы реализуете это хорошим способом, вы можете даже повторно использовать ресурсы, такие как сокеты.
В целом, этот подход позволяет вам контролировать 3, 35 или даже сотни ящиков без проблем. Конечно, мониторинг по-прежнему ограничен, и вы не должны разветвлять тысячи предварительно нагретых потоков. Идея не в этом: идея в том, что вы определили Максимальное количество потоков, которые готовы к использованию и просто ожидают проверки мест назначения. Вам не нужно сталкиваться с проблемами разветвления при запуске мониторинга для многих хостов - вам просто нужно иметь дело с очередями, если вы отслеживаете больше, чем позволяет определенный параллелизм (и это может быть намного выше, чем Parallel.ForEach, который по умолчанию порождает максимум один поток на ядро! Проверьте перегрузки метода, чтобы увеличить это количество.)
Абсолютная оптимизация
Если вы все еще хотите улучшить Систему, приобретите планировщик и планировщик ресурсов, а не просто количество предварительно подготовленных потоков. дать ему ограничения, как минимум 4, максимум 42 темы. Планировщик учитывает запуск и остановку дополнительных потоков в этих границах. Это полезно, если ваша Система снижает скорость мониторинга в течение ночи и вы не хотите, чтобы приостановленные потоки зависали.
Это будет реализация A+, поскольку вы не сможете сразу запустить Мониторинг из холодного состояния, по крайней мере, для некоторых Хостов, и быстро для многих Хостов - вы также вернете ресурсы, которые вам действительно не нужны, на длительное время.
Я знаю, что это своего рода проблема, связанная с кодированием, но рассматривали ли вы возможность использования NagiOS, копчения или другого решения для мониторинга с открытым исходным кодом? Они могут быстро обнаруживать пропадание соединения и, вероятно, имеют множество других функций, которые вы, возможно, не захотите использовать самостоятельно.
Поскольку это, кажется, в значительной степени отдельная задача для приложения, я согласен, что может иметь смысл самостоятельно управлять количеством потоков, используемых для конкретных задач.
Кроме того, кажется, что в вашем процессе есть несколько этапов:
- Предоставление следующего адреса для проверки
- Определение тайм-аута для использования при проверке. Тайм-аут для использования может зависеть от нескольких факторов, в том числе от того, был ли определен адрес, не отвечающий на предыдущую проверку, каково время ответа в целом, и, как отметил Дариуш, если он находится в локальной сети, экстрасети, Интернете и т. Д.,
- Выполнение пинга
- Обработка и интерпретация ping-ответа в сравнении с предыдущим статусом ответа и накопленным статусом (например, обновление статистики для адреса и, возможно, даже ее сохранение).
- Выдача "оповещений" о (повторной) безответственности
- Выдача команд перезапуска.
Поэтому, если у вас есть этапы, которые можно выполнить независимо, используя выходные данные, полученные на предыдущем этапе, вы можете выбрать решение типа SEDA (Staged Event Driven Architecture), в котором вы можете назначить количество выделенных потоков для каждого этапа. И этапы могут быть связаны друг с другом, используя роли "поставщик", "производитель", "потребитель", для определенных элементов информации, которая проходит через этапы, где существуют ProducerConsumerQueues для компенсации временных несоответствий (загрузки при загрузке) и автоматического регулирования (например, слишком много ожидающих запросов для пингов будет блокировать отправителя запросов на пинг до тех пор, пока потребитель, выполняющий пинг, не догонит достаточно
Для базовой структуры вашего "потока пинга" у вас могут быть следующие этапы:
- Этап производителя "PingRequest", который подается провайдером IP-адресов и использует Factory для создания запроса (поэтому Factory может определить время ожидания для запроса из истории и последний известный статус для IP-адреса). Он передает запрос подключенному потребителю "PingRequests".
- Стадия "Pinger", которая извлекает PingRequests из своей очереди потребителя, выполняет Ping и передает результаты подключенному потребителю "PingResults"
- Этап "ResultProcessor", который извлекает PingResults из своей очереди потребителя, обновляет состояние для IP-адреса и передает результаты подключенному потребителю "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 правильному "Pinger".
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;
}
// ...
}