5

多数のコンピューター (おそらく 3 ~ 35 台、おそらくローカル) を監視および制御するアプリケーションがあります。

私が監視していることの 1 つは、稼働時間と ping の状態です。アプリケーションの目的の 1 つはボックスを再起動することですが、他の理由で再起動することもあります。

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 を Dispose() するのは良い考えだと思いますか?

より良い戦略は何ですか?他のアイデアはありますか?

4

4 に答える 4

8

これは、プログラムを高速化するために踏み板を必要とせず、応答性を高める必要があるマルチスレッドの特殊なケースです。オペレーションに必要なコンピューティング パワーはほとんどまたはまったくありません。したがって、監視対象のコンピューターごとに 1 つのスレッドを作成することを恐れません。sleep()とにかく、彼らはほとんどの時間をやっています。スレッドの作成は実際にはここで最もコストがかかるため、一度作成する必要があります。

次のようなオブジェクト階層を作成します。

  • GUIProxy- コンピューターの名前の横にある通知の色を変更するなど、すべての GUI 操作を処理します
  • HostManager- 新しいマシンを登録し、古いマシンを削除し、タイミング チェックを実行します。Monitors
  • HostMonitor- ping を定期的、連続的に送信してコンピュータをチェックします。挙動については後述

アルゴリズムのチェック

LAN では、ほとんどの場合、ping は送信から 1 ~ 2 ミリ秒以内に返されます。インターネット上では、時間が異なる場合があります。Monitorマシンの場所に応じて、それぞれに 2 つの ping 時間のしきい値を個別に設定します。1 つは、LAN ping が 5 ミリ秒を超えるか、インターネット ping が 200 ミリ秒を超える場合の「警告」しきい値 (黄色のライトまたは GUI の sth) です。2 番目は「エラー」のしきい値で、LAN が 1 秒を超え、インターネットが 2 秒または sth を超えます。それぞれMonitorが ping を送信し、応答を待ち、応答を受信した後に別の ping を送信します。lastPingSendTimelastPingReceiveTimeおよびを格納する必要がありcurrentPingSendTimeます。前者はレイテンシを決定するためのもので、後者は遅延を確認するためのものですHostManager。もちろん、Monitorタイムアウトやその他のシステム/ネットワーク イベントを適切に処理する必要があります。

では、これHostManagerも単一のスレッドで実行され、currentPingSendTime各モニターで をチェックし、そのモニターのしきい値と比較してチェックします。しきい値を超えるとGUIProxy、GUI に状況が表示されるよう通知されます。

利点

  • 自分でスレッドを制御します
  • 同期(より単純な)pingメソッドを使用できます
  • Managerモニターに非同期でアクセスするため、ハングしません。
  • コンピューターだけでなく、他のものを監視するために使用できる抽象的な Monitor インターフェイスを実装できます

短所

  • 正しいMonitorスレッドの実装は単純ではないかもしれません
于 2013-07-11T08:09:22.107 に答える
2

スケールアウト ソリューションが必要かどうかに応じて、Dariusz が言ったように状態チェックを実装できます (これは絶対に正当なアプローチです)。

このアプローチには、シナリオに関連する場合と関連しない場合がある唯一の欠点があります。監視対象のボックスまたはサービスを数百または数千にスケールアップすると、膨大な量のスレッドが発生します。64 ビット モードの .net アプリケーションが数千の同時スレッドをサポートするという事実に関しては、それほど多くのワーカーを生成することはお勧めしません。そのような膨大な量のワーカーをスケジュールするジョブを彼に与えた場合、リソーススケジューラはもはやあなたの親友ではありません.

スケールアウト可能なソリューションを得るには、少し難しいです。すぐに元の問題に戻りましょう。一連のボックスをすばやく監視したいのですが、パイプライン化された処理がうまく機能していません。将来的に他のサービス(tcp)を監視する可能性があることを考慮して、タイムアウトも待機すると、このアプローチが完全に停止します。

解決策: カスタム スレッド プーリングまたはスレッドの再利用

スレッドがデフォルト スレッド プールから生成される時間の影響を受ける特殊な種類のスレッドを扱っているため、生成の問題を解決するためのソリューションが必要です。スケールアウトできることを念頭に置いて、次の方法をお勧めします。

カスタムまたはデフォルトのスレッド プールを使用して、サスペンド状態の複数のスレッドを生成します。これで、システムは複数のボックスを測定したいと考えています。したがって、事前にウォームアップされたスレッドにアクセスし、最初の一時停止/解放されたスレッドを取得して、監視ジョブ用に予約します。使用するスレッドを取得した後、実際のワーカー メソッド (スレッドによって非同期に呼び出される) へのある種のハンドルを彼に与えます。Monitoring Iteration が終了すると (時間がかかる場合があります)、スレッドは結果を返し (良い方法はコールバックになります)、自身を中断モードに設定します。

したがって、これはスレッドが事前にウォームアップされた単なるカスタム スケジューラです。ManualResetEvents を使用してサスペンド/レジュームを構築している場合、スレッドはほぼ瞬時に利用可能になります。

さらなるパフォーマンスが必要ですか?

もう少しパフォーマンスを上げて、結果のシステムをより細かく調整できるようにしたい場合は、専用のスレッド プールをお勧めします (zabbix が監視用に行っているように)。そのため、ping または tcp を介してボックスに到達可能かどうかを確認するカスタム メソッドを呼び出す可能性がある一連のスレッドを割り当てるだけでなく、監視タイプごとに個別のプールを割り当てます。そのため、icmp (ping) および tcp 監視の場合、「チェック方法」に関する基本的な知識が既にスレッドに含まれている少なくとも 2 つのスレッド プールを作成します。ping モニターの場合、スレッドは準備ができており、ターゲットがチェックするのを待っている初期化された ping インスタンスで待機します。スレッドを一時停止状態から取得すると、すぐにホストをチェックして結果を返します。その後、スリープの準備をします (この場合、次の実行のために既に環境を初期化しています)。これをうまく実装すれば、ソケットなどのリソースを再利用することもできます。

全体として、このアプローチにより、トラブルに巻き込まれることなく、3、35、または数百のボックスを監視できます。もちろん、Monitoring はまだ制限されているため、何千もの prewarmed スレッドを fork するべきではありません。それは背後にあるアイデアではありません: アイデアは、使用の準備ができており、チェックする目的地を取得するのを待っているスレッドの最大数を定義したということです。多数のホストのモニタリングを開始するときにフォークの問題に対処する必要はありません。定義した同時実行数を超えてモニタリングしている場合は、キューイングに対処するだけで済みます (これは、デフォルトで Parallel.ForEach よりもはるかに高くなる可能性があります)。 spawns コアごとに最大 1 つのスレッド! メソッドのオーバーロードを確認して、この量を増やしてください。)

絶対最適化

システムをさらに改善したい場合は、プレウォームンド スレッドのカウントだけでなく、スケジューラとリソース プランナーも入手してください。最小 4、最大 42 スレッドなどの制限を彼に与えます。スケジューラは、これらの境界内で追加のスレッドを開始および停止することを考慮に入れます。これは、システムが夜間に監視率を下げ、中断されたスレッドがぶらぶらしたくない場合に役立ちます。

これは A+ の実装です。これは、少なくとも一部のホストではすぐに、多くのホストでは迅速にコールド状態から監視を開始することができないためです。また、本当に長時間必要としないリソースを返すことにもなります。

于 2013-07-17T21:04:37.703 に答える
0

これはほとんどアプリケーション専用のタスクのように見えるので、特定のタスクに使用されるスレッドの数を自分で管理することは理にかなっているかもしれないことに同意します。

また、あなたのプロセスにはいくつかの段階があるようです:

  1. チェックする次のアドレスを提供する
  2. チェック時に使用するタイムアウトを決定します。使用するタイムアウトは、以前のチェックでアドレスが応答していないと判断されたかどうか、一般的な応答時間、および Dariusz が述べたように、LAN、エクストラネット、インターネット上にある場合など、いくつかの要因に依存する可能性があります.. .
  3. ping の実行
  4. ping 応答と以前の応答ステータスおよび蓄積されたステータスの処理と解釈 (たとえば、アドレスの統計の更新、場合によってはこれを保存することさえあります)。
  5. (繰り返される)無反応に対する「アラート」の発行
  6. 再起動コマンドの発行。

そのため、前のステージで生成された出力を使用して独立して明確に実行できるステージがある場合は、各ステージに多数の専用スレッドを割り当てることができる SEDA (Staged Event Driven Architecture) タイプのソリューションを選択できます。また、ステージ間を流れる特定の情報アイテムに対してプロバイダー/プロデューサー/コンシューマーの役割を使用して、ステージを相互に接続できます。一時的な不一致 (ピーク負荷) を吸収する ProducerConsumerQueues と自動スロットリング (ping の保留中の要求が多すぎるなど) があります。 ping を実行するコンシューマが十分に追いつくまで、ping リクエストのプロデューサーをブロックします)。

「Ping フロー」の基本構造については、次の段階が考えられます。

  1. IPAddresses の Provider によって供給され、Factory を使用してリクエストを作成する「PingRequest」プロデューサー ステージ (これにより、Factory は、IPAddress の履歴と最後の既知のステータスからリクエストのタイムアウトを判断できます)。「PingRequests」の接続されたコンシューマにリクエストを渡します。
  2. コンシューマー キューから PingRequests を取得する "Pinger" ステージは、Ping を実行し、接続された "PingResults" のコンシューマーに結果を渡します。
  3. コンシューマ キューから PingResults を取得する「ResultProcessor」ステージは、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 つが以前に切断されたアドレスにサービスを提供し、1 つが「スロー レスポンダー」にサービスを提供し、もう 1 つが残りにサービスを提供します。これは、このルーティングを行うコンシューマにステージ 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;
    }
    // ...
} 
于 2013-07-15T16:45:45.743 に答える
0

これがコーディングの問題の終焉のようなものであることは知っていますが、NagiiOS やスモーク、または別のオープンソースの監視ソリューションの使用を検討したことはありますか? これらは接続の低下をすばやく検出でき、自分でタップしたくない他の多くの機能を備えている可能性があります.

于 2013-07-16T01:34:39.410 に答える