Netty core source code analysis (4) heartbeat detection source code analysis

Series Article Directory

Netty core source code analysis (1), Netty’s server-side startup process source code analysis
Netty core source code analysis (2), Netty’s server-side receiving request process source code analysis
Netty core source code analysis (3) The key to business request execution - ChannelPipeline, ChannelHandler, ChannelHandlerContext source code analysis
Netty core source code analysis (4) heartbeat detection source code analysis
Netty core source code analysis (5) core component EventLoop source code analysis

1. Heartbeat detection case

Netty entry case - Netty realizes heartbeat detection

As a network framework, Netty provides many functions. This article mainly analyzes the source code of its heartbeat mechanism heartbeat.

2. Source code analysis

1. Three Handlers of Netty heartbeat

handler effect
IdleStateHandler When the idle time of the connection (reading or writing) is too long, an IdleStateEvent event will be triggered, and then the event can be handled by rewriting the userEventTriggered method in ChannelInboundHandler
ReadTimeoutHandler If no read event occurs within the specified time, an exception of ReadTimeoutException will be thrown and the connection will be automatically closed. This exception can be handled in the exceptionCaught method
WriteTimeoutHandler When a write operation cannot be completed within a certain period of time, a WriteTimeoutException will be thrown and the connection will be closed. This exception can be handled in the exceptionCaught method

Among them, ReadTimeoutHandler and WriteTimeoutHandler will throw an exception and close the connection, which belongs to exception handling, and the focus is on analyzing IdleStateHandler.

2. IdleStateHandler source code

(1) Four key attributes

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

(2) handlerAdded method

Triggered when the handler is added to the pipeline:

// 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);
    }
}

We found that as long as the given parameter is greater than 0, a timed task is created, each event is judged separately, and the state is set to 1 at the same time to prevent repeated initialization.

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

(3) Four inner classes

Four internal classes are defined in IdleStateHandler, among which AbstractIdleTask is the parent class of the other three, AbstractIdleTask implements the Runnable interface and rewrites the run method, and the other three rewrite the run method on the basis of AbstractIdleTask.
insert image description here

// 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. The run method of reading events - 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);
        }
    }
}

In general, each read operation will record a time. When the scheduled task time is up, the interval between the current time and the last read time will be calculated. If the interval exceeds the set time, the UserEventTriggered method will be triggered.

4. The run method of writing events - 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);
        }
    }
}

The run code logic of the write task is basically the same as that of the read task, the only difference is that there is a judgment hasOutputChanged for outbound slow data.

5. The run method of all events - AllIdleTimeoutTask

Indicates that both read and write events need to be monitored.

// 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. Summary

  1. IdleStateHandler can implement the heartbeat function. When the server and the client do not have any read-write interaction and exceed the given time, the userEventIriggered method of the user handler will be triggered. In this method, the user can try to send information to the other party, and if the sending fails, the connection will be closed.
  2. The implementation of IdleStateHandler is based on the timing task of EventLoop. Every read and write will record a value. When the timing task is running, it can judge whether it is idle by calculating the results of the current time, the setting time and the time of the last event.
  3. There are 3 timing tasks inside, corresponding to read events, write events, and read and write events. Usually it is enough for users to listen to read and write events.
  4. At the same time, IdleStateHandler also considers some extreme situations: the client receives slowly, and the speed of receiving data at one time exceeds the set idle time. Netty decides whether to judge the condition of the outbound buffer through the observeOutput attribute in the construction method.
  5. If the outbound is slow, Netty doesn't think it's idle, so it doesn't trigger the idle event. But the first time is to be triggered anyway. Because it is impossible to judge whether the outbound is slow or idle for the first time. Of course, if the outbound is slow, it may cause OOM, and OOM is a bigger problem than idle.
  6. Therefore, when your application has memory overflow, OOM, etc., and write idleness rarely occurs (use observeOutput as rue), then you need to pay attention to whether the data outbound speed is too slow.
  7. Another thing to pay attention to is ReadTimeouthandler, which inherits from IdeStateHandler. When the read idle event is triggered, the ctx.fireExceptionCaught method is triggered, and a ReadTimeoutException is passed in, and then the Socket is closed.
  8. The implementation of WriteTimeoutHandler is not based on IdleStateHandler. Its principle is that when the write method is called, a timed task will be created. The task content is to judge whether the writing time is exceeded according to the completion of the incoming promise. When the scheduled task starts to run according to the specified time, it is found that the isDone method of the promise returns false, indicating that the writing has not been completed, indicating that it has timed out, and an exception is thrown. When the write method is completed, the scheduled task will be interrupted.

Guess you like

Origin blog.csdn.net/A_art_xiang/article/details/130357721