Skip to content

Latest commit

 

History

History
127 lines (75 loc) · 6.55 KB

17.6.inEventLoop说明.md

File metadata and controls

127 lines (75 loc) · 6.55 KB
title date categories tags description
17.6.inEventLoop说明
2020-03-04
Netty
Netty
网络
零拷贝
  1. 一个EventLoopGroup 事件循环组,里面包含一个或者多个EventLoop

  2. 一个EventLoop在它的整个生命周期中, 只会于一个Thread 进行绑定。

    这一点在SingleThreadEventExecutor 中可以看出来。这里面维护了一个Thread。 这个Thread 就是用于执行事件循环的线程, 单线程的奥。

  3. 所有由EventLoop处理的各种IO事件,都是在这个EventLoop里面的那个Thread 线程上执行的。包括我们各种handler里的方法,其实都在在这个线程里执行的。说它是IO线程? 除了读写,连接注册啥的, 也都是在这个线程里面做的。

  4. 一个Channel , 在它的整个生命周期当中, 只能注册到一个EventLoop上, 更准确的说, 是指能注册到一个EventLoop里面的Seletor 上。一个EventLoop又只能绑定到一个Thread 上,因此,一个Channel 的诸多Handler里面的方法(更准确的说是与channel关联的pipeline 里面的各种handler), 只会由同一个线程去执行, 不会产生多线程问题。因此, 在handler 的回调里面是不用考虑并发的。都是单线程执行

  5. 一个EventLoop在运行过程中,会被分配给很多的channel。说明一下:比如我们的workerGroup, 里面有8个 EventLoop。 就是这8个EventLoop要处理成百上千的 连接(Channel)。根据之前说的 round-robin, 第一个channel 来了, 丢给第一个EventLoop, 第二个channel 来了, 就给第二个EventLoop。。。第八个Channel 分给第8个EventLoop处理。第九个channel 来了, 又循环分给第一个EventLoop去处理了。

    然而, 一个channel , 在它的生命周期当中, 只能注册到唯一一个 EventLoop 上, 且不能再改变。典型一对多的关系。

  6. 因此是单线程的,所以不能在handler 里面执行耗时操作,因为这一个线程要服务很多的channel的。一个耗时了,其他所有的channel都要等着了。性能下降。

  7. 如果想要做耗时操作, 有两种方式: ①在handler 里面使用自己定义的业务线程池来处理 ② 把handler add 到pipeline的时候, 使用addLast的重载方法:addLast(group ...)这样。指定一个EventExectorGroup,这样真正的执行就不在IO线程执行, 而是在单独指定的这个线程组上执行。

     * Inserts {@link ChannelHandler}s at the last position of this pipeline.
     *
     * @param group     the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}s
     *                  methods.
     * @param handlers  the handlers to insert last
     *
     */
    ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers);

    这里, add 哪个handler, 哪个handler 的执行就在这个group上, 并不是全部奥。我觉得是这样。

    如果add 一个handler, 没指定group, 那默认应该还是在io线程执行的。

    如果add handler 指定了group, 那这个handler 里的回调方法就都是在参数中的group上执行。

  8. 属于同一个channel 的操作, 都能保证他执行的顺序与提交的顺序是相同的。

  9. 在Netty 中, channel的实现是线程安全的。因此, 我们可以存储一个channel的引用 , 在需要向远程端点发送数据时,可以通过这个channel 相应的方法, 比如write, 直接发送数据。即便可能有多个线程来访问, 来调用, 也不会出现多线程问题。Netty底层会保证消息按照顺序发出。因为不在channel的EventLoop线程的会, 会提交到它那个任务队列, 最终都是由EventLoop的那个线程去执行的。

问题:::

  1. 顺序问题:::比如channel此时正要执行一个方法,这与任务队列里任务的执行会不会冲突。 比如,正在执行任务队列里的1,耗时10秒,在第5秒的时候,又添加的一个任务2到任务队列。 在10秒钟的时候,我们直接代码让channel执行一个事情3(同一线程的),那此时,2和3谁先执行???
  2. 如果客户端请求服务器,服务器进行耗时操作(查询数据库),完了之后把数据返回给客户端,这个怎么做? 对于springboot这样的,接口方法返回什么,前端就拿到什么。因此需要同步处理 对于netty的channel read0,可以开线程去处理,处理完了直接用ctx. write andflush就好了,因为全双工socket通信。
  3. 那问题又来了,如果是netty实现的http服务器呢?在channel read 0里面,又要返回,又不能执行耗时操作?

我知道了, 大概是这样:

Http 是基于请求响应模式的,客户端向服务器发送一个Http请求,然后服务器将返回一个Http响应。

对于 SpringBoot 或者 Serverlet 这样的,在接口里面返回什么,就直接进行Http响应了。所以要同步处理。如果开个线程去处理的话, 接口方法一返回, 底层就去进行Http响应了,这样一次Http请求就完了。等线程处理完了, 也没用了。

对于Netty 这样的实现的Http服务器,请求来了,在 channelRead方法里,返回了, 并没有进行HTTP响应。开个线程,执行操作,操作执行完了, 再write,这时候才执行HTTP响应,这个时候, 一次完整的HTTP请求才完成。

inEventLoop说明:

if (eventLoop.inEventLoop()) {
    register0(promise);
} else {
    try {
        eventLoop.execute(new Runnable() {
            @Override
            public void run() {
                register0(promise);
            }
        });
    } catch (Throwable t) {
        logger.warn(
                "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                AbstractChannel.this, t);
        closeForcibly();
        closeFuture.setClosed();
        safeSetFailure(promise, t);
    }
}

eventLoop.inEventLoop() 这个判断,就是为了防止产生多线程的问题。

如果是, 就代表: 当前正在执行注册操作的线程, 就是eventLoop里面的那个线程, 那么就放心的交给他来做。

如果不是,就嗲表:正在执行注册流程操作的这个线程, 不是EventLoop里面的那个线程。如果直接执行, 就会产生多线程问题。 那么就提交一个任务, 提到eventloop上, 让eventloop的那个thread 再去执行,还是单线程执行, 这样避免多线程的问题。

Netty精髓