EventLoopGroup

2018-04-22  本文已影响0人  码农崛起
EventLoopGroup相关类层次结构

1, EventLoopGroup,EventLoop,EventExecutorGroup,EventExecutor都实现了ScheduledExecutorService,粗略的说他们都是可调度线程池

2,顾名思义,EventLoopGroup包含一组EventLoop,EventExecutorGroup包含一组EventExecutor。

3,EventExecutorGroup主要覆盖了父接口里的几个方法,返回特定的ScheduledFuture类型。最关键的方法是next,返回一个可用的EventExecutor,EventLoopGroup提供了上个register方法用于注册channel到EventLoopGroup。

4,EventExecutor中最关键的是inEventLoop方法,用于判断一个线程是否是EventExecutor内部那个线程,EventExecutor和EventLoop都是单线程实现。inEventLoop的主要使用场景是,当IO变化时,通过channel关联的pipeline会触发对应的事件,这些事件对应的执行pipeline中的处理链中handler的回调方法,每个handler添加到pipeline都可以指定自己的EventLoop,如果没指定,默认使用要添加的pipeline关联的channel注册到的EventLoopGroup中的某个EventLoop。所以channel通过pipeline调用handler时,如果handler没有单独指定EventLoop,那inEventLoop就会返回true,他俩由同一个线程处理,直接调用handler。如果handler单独指定了EventLoop,inEventLoop就会返回false,channel调用handler时就把要调用的方法封装到Runnable里,然后添加到handler指定的EventLoop的任务队列里,稍后会由对应的EventLoop中的线程执行。

5,EventExecutorGroup有两个主要的实现类:

MultithreadEventExecutorGroup:看名字就知道有多个线程,哈哈,主要用于注册支持NIO的channel。在构造时根据指定的线程数量调用抽象方法newChild初始化EventExecutor数组(典型的模版方法设计模式),每个EventExecutorGroup类型都有对应的EventExecutor类型,每个EventLoopGroup都有对应的EventLoop类型。例如:最重要的NioEventLoopGroup的newChild实现方式如下

NioEventLoopGroup的newChild方法实现

第3点里说EventExecutorGroup添加了一个非常重要的方法next,用于返回可用的EventExecutor,对应的选择策略接口是EventExecutorChooser(策略设计模式的典型应用场景)

EventExecutor选择方式

如果executors数组的长度是2的指数就使用上面的,否则使用下面的,主要就是均匀的分布压力到多个线程。

ThreadPerChannelEventLoopGroup:看名字就知道每次有client连接时就启用一个新线程处理。构造时设置最大线程数,内部用Set存储当前活跃的线程,用ConcurrentLinkedQueue存储当前idle状态的线程,不支持next方法,有一个类似的nextChild方法,实现方法简单明了。

nextChild实现

6, 注册channel到EventLoopGroup,使用EventLoopGroup中某个EventLoop处理channel的IO

channel注册到group

一般regist都发生在主线程,所以eventLoop.inEventLoop()会返回false,eventLoop从ScheduledExecutorService继承了execute方法,第一次执行execute时,EventLoop内部的线程开始启动,启动之后循环调用抽象方法run执行任务队列中的任务。

register实现细节

更具体的register实现都在doRegister抽象方法中(模版方法设计模式真是很实用哦),重点是此时会pipeline.fireChannelRegistered()触发channelRegistered事件,如果是第一次注册,还会触发channelActive事件,事件实现细节稍后会有专门的文章分析。

NIO的doRegister是把channel注册到NioEventLoop内部的selector上,线程启动之后就不停的select注册的IO可用的channel。

NioEventLoop run方法

handler在添加到channel关联的pipeline时,如果不指定EventLoop,默认使用channel注册到的EventLoop。所以EventLoop有两个职责:监控IO并触发相应的事件和执行handler的事件方法。ioRatio控制这两个职责消耗的时间比例。

nio的精髓,处理IO可用的channel

OIO的doRegister是空的,啥也没干。

oio run

oio由于是一个线程处理一个channel,直接从任务队列里取任务执行就行了。

7,注册成功之后会触发channelActive事件,channlActive默认会触发channel和关联的pipeline的read事件,由于read事件属于outbound handler,所以从tail开始传播(关于pipeline稍后分析)。最后会传播到pipeline的head,head的read方法会调用unsafe.beginRead。

在NIO中的效果是添加对channel read事件的监听,OIO中的效果是开始读channel里的数据

之前说过EventLoop的职责不只是处理IO,但是OIO的IO操作都是阻塞的,一旦阻塞就无法处理handler回调了,所以需要设置超时来模拟非阻塞。

设置timeout 设置timeout

每次读完一个请求之后,都会关闭输入(OIO是关闭InputStream,NIO是取消监控channel的read事件),之后如果设置了autoread(默认为true,如果设置了false,必须自己在handler中调用channel.read,否则不会收到请求),会再次打开(打开之后,如果有请求到来(同一个连接如果还没收到响应,在超时之前是不能再次发送请求的吧???),但是现在的请求还没处理完,就会在EventLoop的任务队列里排队)。然后开始处理这个请求,一般的pipeline流程是:各种ByteToMessageDecode,接着各种MessageToMessageDecode,转为java对象之后就是正常的业务处理了,每次有要响应的数据时都可以调用write*中间经过各种MessageToMessageEncode和MessageToByteEncode把响应写入ChannelOutboundBuffer,等全部处理完之后,再调用flush,把整个buffer写入channel。

NIO flush的过程是先直接写入channel,写入失败之后开始监听write事件,等write事件触发了,写完所有请求之后,取消监控write事件

nio doWrite

OIO的doWrite就比较简单了,就是死循环拼命写。

oio doWrite

一次完整的读写过程就结束啦。

上一篇下一篇

猜你喜欢

热点阅读