Nettyコアのソースコード解析(4) ハートビート検出のソースコード解析

シリーズ記事ディレクトリ

Nettyコアのソースコード解析(1)、Nettyのサーバーサイド起動処理のソースコード解析
Nettyのコアソースコード解析(2)、Nettyのサーバーサイドのリクエスト受信処理のソースコード解析
Nettyコアのソースコード解析(3) ビジネスリクエストの鍵実行 - ChannelPipeline、ChannelHandler、ChannelHandlerContext のソースコード解析
Netty コアのソースコード解析 (4) ハートビート検出のソースコード解析
Netty コアのソースコード解析 (5) コアコンポーネント EventLoop のソースコード解析

1. 心拍検出の場合

Nettyエントリーケース - Nettyがハートビート検出を実現

Nettyはネットワークフレームワークとして多くの機能を提供していますが、本稿では主にハートビート機構のソースコードを分析します。

2. ソースコード分析

1. Netty ハートビートの 3 人のハンドラー

ハンドラ 効果
IdleStateHandler 接続 (読み取りまたは書き込み) のアイドル時間が長すぎる場合、IdleStateEvent イベントがトリガーされ、ChannelInboundHandler の userEventTriggered メソッドを書き換えることでイベントを処理できます。
ReadTimeoutハンドラ 指定された時間内に読み取りイベントが発生しない場合、ReadTimeoutException の例外がスローされ、接続は自動的に閉じられます。この例外は、ExceptionCaught メソッドで処理できます。
WriteTimeoutHandler 書き込み操作が一定時間内に完了できない場合、WriteTimeoutException がスローされ、接続が閉じられます。この例外は、ExceptionCaught メソッドで処理できます。

このうち、ReadTimeoutHandler と WriteTimeoutHandler は例外をスローして接続を閉じるもので、例外処理に属し、IdleStateHandler の解析に重点が置かれています。

2. IdleStateHandlerのソースコード

(1) 4つの重要な属性

// 是否考虑出站时较慢的情况,默认是false
private final boolean observeOutput;
// 读事件空闲时间,0 则禁用事件,纳秒为单位
private final long readerIdleTimeNanos;
// 写事件空闲时间,0则禁用事件,纳秒为单位
private final long writerIdleTimeNanos;
// 读或写空闲时间,0则禁用事件,纳秒为单位
private final long allIdleTimeNanos;

(2)handlerAdded方法

ハンドラーがパイプラインに追加されるとトリガーされます。

// io.netty.handler.timeout.IdleStateHandler#handlerAdded
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    
    
    if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
    
    
        // channelActive() event has been fired already, which means this.channelActive() will
        // not be invoked. We have to initialize here instead.
        initialize(ctx);
    } else {
    
    
        // channelActive() event has not been fired yet.  this.channelActive() will be invoked
        // and initialization will occur there.
    }
}
// io.netty.handler.timeout.IdleStateHandler#initialize
private void initialize(ChannelHandlerContext ctx) {
    
    
    // Avoid the case where destroy() is called before scheduling timeouts.
    // See: https://github.com/netty/netty/issues/143
    switch (state) {
    
    
    case 1:
    case 2:
        return;
    }

    state = 1;
    // 初始化“监控出站数据属性”
    initOutputChanged(ctx);
	// System.nanoTime() 返回当前纳秒
    lastReadTime = lastWriteTime = ticksInNanos();
    if (readerIdleTimeNanos > 0) {
    
    
    	// 这里的schedule会调用eventLoop的schedule方法,将定时任务添加到队列里
        readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
                readerIdleTimeNanos, TimeUnit.NANOSECONDS);
    }
    if (writerIdleTimeNanos > 0) {
    
    
        writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
                writerIdleTimeNanos, TimeUnit.NANOSECONDS);
    }
    if (allIdleTimeNanos > 0) {
    
    
        allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
                allIdleTimeNanos, TimeUnit.NANOSECONDS);
    }
}

指定されたパラメーターが 0 より大きい限り、時間指定されたタスクが作成され、各イベントが個別に判断され、初期化の繰り返しを防ぐために同時に状態が 1 に設定されることがわかりました。

// io.netty.handler.timeout.IdleStateHandler#schedule
ScheduledFuture<?> schedule(ChannelHandlerContext ctx, Runnable task, long delay, TimeUnit unit) {
    
    
    return ctx.executor().schedule(task, delay, unit);
}

(3) 4 つの内部クラス

IdleStateHandler には 4 つの内部クラスが定義されており、そのうち AbstractIdleTask は他の 3 つのクラスの親クラスであり、AbstractIdleTask は Runnable インターフェイスを実装して run メソッドを書き換え、他の 3 つは AbstractIdleTask に基づいて run メソッドを書き換えます。
ここに画像の説明を挿入

// io.netty.handler.timeout.IdleStateHandler.AbstractIdleTask
private abstract static class AbstractIdleTask implements Runnable {
    
    

    private final ChannelHandlerContext ctx;

    AbstractIdleTask(ChannelHandlerContext ctx) {
    
    
        this.ctx = ctx;
    }

    @Override
    public void run() {
    
    
        if (!ctx.channel().isOpen()) {
    
    // 通道关闭,不执行
            return;
        }

        run(ctx);
    }
	// 子类重写
    protected abstract void run(ChannelHandlerContext ctx);
}

3. イベントを読み取る run メソッド - ReaderIdleTimeoutTask

// io.netty.handler.timeout.IdleStateHandler.ReaderIdleTimeoutTask
private final class ReaderIdleTimeoutTask extends AbstractIdleTask {
    
    

    ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
    
    
        super(ctx);
    }

    @Override
    protected void run(ChannelHandlerContext ctx) {
    
    
    	// 获取设置的读超时时间
        long nextDelay = readerIdleTimeNanos;
        if (!reading) {
    
    
        	// 当前时间减去给定时间和最后一次读(执行channelReadComplete方法设置)
            nextDelay -= ticksInNanos() - lastReadTime;
        }
		// 如果小于0,就触发事件
        if (nextDelay <= 0) {
    
    
            // Reader is idle - set a new timeout and notify the callback.
            // 用于取消任务promise
            readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);

			// 表示下一次读就不是第一次了
            boolean first = firstReaderIdleEvent;
            // first设置为false,在channelRead方法中会被改为true
            firstReaderIdleEvent = false;

            try {
    
    
            	// 创建一个IdleStateEvent 类型的读事件,传递给Handler的userEventTriggered方法
                IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
                // Handler传递,调用下一个Handler的userEventTriggered方法
                channelIdle(ctx, event);
            } catch (Throwable t) {
    
    
                ctx.fireExceptionCaught(t);
            }
        } else {
    
    
        	// 如果大于0,继续放入队列,间隔时间是新的计算时间
            // Read occurred before the timeout - set a new timeout with shorter delay.
            readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
        }
    }
}

一般に、各読み取り操作は時間を記録します。スケジュールされたタスクの時間が経過すると、現在の時間と最後の読み取り時間の間の間隔が計算されます。間隔が設定時間を超えると、UserEventTriggered メソッドがトリガーされます。

4. イベントを書き込む run メソッド - WriterIdleTimeoutTask

// io.netty.handler.timeout.IdleStateHandler.WriterIdleTimeoutTask
private final class WriterIdleTimeoutTask extends AbstractIdleTask {
    
    

    WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
    
    
        super(ctx);
    }

    @Override
    protected void run(ChannelHandlerContext ctx) {
    
    

        long lastWriteTime = IdleStateHandler.this.lastWriteTime;
        long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
        if (nextDelay <= 0) {
    
    
            // Writer is idle - set a new timeout and notify the callback.
            writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);

            boolean first = firstWriterIdleEvent;
            firstWriterIdleEvent = false;

            try {
    
    
                if (hasOutputChanged(ctx, first)) {
    
    
                    return;
                }

                IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
                channelIdle(ctx, event);
            } catch (Throwable t) {
    
    
                ctx.fireExceptionCaught(t);
            }
        } else {
    
    
            // Write occurred before the timeout - set a new timeout with shorter delay.
            writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
        }
    }
}

書き込みタスクの実行コードロジックは基本的に読み取りタスクの実行コードロジックと同じですが、唯一の違いは、アウトバウンドの低速データに対して hasOutputChanged の判定があることです。

5. すべてのイベントの実行メソッド - AllIdleTimeoutTask

読み取りイベントと書き込みイベントの両方を監視する必要があることを示します。

// io.netty.handler.timeout.IdleStateHandler.AllIdleTimeoutTask
private final class AllIdleTimeoutTask extends AbstractIdleTask {
    
    

    AllIdleTimeoutTask(ChannelHandlerContext ctx) {
    
    
        super(ctx);
    }

    @Override
    protected void run(ChannelHandlerContext ctx) {
    
    

        long nextDelay = allIdleTimeNanos;
        if (!reading) {
    
    
        	// 当前时间减去最后一次写或者读的世界,若大于0,说明超时了
        	// 这里的时间计算是取读写事件的最大值来的
            nextDelay -= ticksInNanos() - Math.max(lastReadTime, lastWriteTime);
        }
        if (nextDelay <= 0) {
    
    
            // Both reader and writer are idle - set a new timeout and
            // notify the callback.
            allIdleTimeout = schedule(ctx, this, allIdleTimeNanos, TimeUnit.NANOSECONDS);

            boolean first = firstAllIdleEvent;
            firstAllIdleEvent = false;

            try {
    
    
            	// 同写事件相同,出站较慢的判断
                if (hasOutputChanged(ctx, first)) {
    
    
                    return;
                }

                IdleStateEvent event = newIdleStateEvent(IdleState.ALL_IDLE, first);
                channelIdle(ctx, event);
            } catch (Throwable t) {
    
    
                ctx.fireExceptionCaught(t);
            }
        } else {
    
    
            // Either read or write occurred before the timeout - set a new
            // timeout with shorter delay.
            allIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
        }
    }
}

6. まとめ

  1. IdleStateHandler はハートビート機能を実装でき、サーバーとクライアントが読み取り/書き込みのやり取りを行わず、指定された時間が経過すると、ユーザー ハンドラーの userEventIriggered メソッドがトリガーされます。この方法では、ユーザーは相手に情報の送信を試み、送信に失敗すると接続が切断されます。
  2. IdleStateHandler の実装は、EventLoop のタイミング タスクに基づいています。読み取りと書き込みのたびに値が記録されます。タイミング タスクの実行中に、現在時刻、設定時刻、および最後のイベントの時間。
  3. 内部には、読み取りイベント、書き込みイベント、読み取りおよび書き込みイベントに対応する 3 つのタイミング タスクがあります。通常、ユーザーは読み取りおよび書き込みイベントをリッスンするだけで十分です。
  4. 同時に、IdleStateHandler はいくつかの極端な状況も考慮します。つまり、クライアントの受信が遅く、一度にデータを受信する速度が設定されたアイドル時間を超えます。Netty は、構築メソッドのobserveOutput 属性を通じて送信バッファの状態を判断するかどうかを決定します。
  5. アウトバウンドが遅い場合、Netty はアイドル状態であるとは考えないため、アイドル イベントはトリガーされません。しかし、とにかく初回がトリガーされます。下りは初めてでは遅いのか空いているのか判断できないからです。もちろん、アウトバウンドが遅いと OOM が発生する可能性があり、OOM はアイドル状態よりも大きな問題です。
  6. したがって、アプリケーションにメモリ オーバーフローや OOM などが発生し、書き込みアイドル状態がほとんど発生しない場合 (rue としてobservoutput を使用する)、データのアウトバウンド速度が遅すぎるかどうかに注意する必要があります。
  7. もう 1 つ注意すべき点は、IdeStateHandler から継承する ReadTimeouthandler で、読み取りアイドル イベントがトリガーされると、ctx.fireExceptionCaught メソッドがトリガーされ、ReadTimeoutException が渡されて、Socket が閉じられます。
  8. WriteTimeoutHandlerの実装はIdleStateHandlerに基づいたものではなく、writeメソッドが呼び出されると時限タスクが作成されるという原理であり、タスク内容は受信したPromiseの完了に応じて書き込み時間を超過したかどうかを判断するというものである。スケジュールされたタスクが指定された時間に従って実行を開始すると、Promise の isDone メソッドが書き込みが完了していないことを示す false を返し、タイムアウトしたことがわかり、例外がスローされます。書き込みメソッドが完了すると、スケジュールされたタスクは中断されます。

おすすめ

転載: blog.csdn.net/A_art_xiang/article/details/130357721