文章主要从以下几个方面进行介绍:
- 为什么要学习netty
- netty是什么
- netty与日常工作如何结合
- netty的高阶使用
1. 为什么要学习netty
Netty
是由JBOSS
提供的一个Java
开源框架,现为Github
上的独立项目。Netty 本质是一个基于 Java NIO 的高性能网络编程框架,在很多应用场景中都可以找到netty的身影,比如游戏场景的高性能通信,Hadoop、Spark 等大数据处理框架都使用了 Netty 来实现节点之间的通信,RPC框架、消息中间件等也都使用到netty。
- 简单的 API 设计:Netty 提供了简洁、易用的 API,使得开发者可以快速上手并进行网络应用的开发。开发者无需深入了解底层的网络编程细节,如 Socket 编程、I/O 多路复用等,只需要关注业务逻辑的实现。例如,使用 Netty 编写一个简单的 TCP 服务器,只需要几行代码就可以完成服务器的启动和客户端连接的处理。
- 丰富的组件和工具:Netty 提供了丰富的组件和工具,如编解码器、ChannelHandler 等。编解码器可以方便地实现数据的序列化和反序列化,而 ChannelHandler 则可以对网络数据进行拦截和处理。这些组件和工具可以帮助开发者快速构建出功能强大的网络应用。
另一方面,netty作为高性能网络通信基础,有很多很好的设计可以借鉴到我们的日常研发过程中,比如零拷贝操作,bytebuffer理念,pipeline设计等。当然,netty的相关知识也是我们面试时重点考察的对象。
2. netty是什么
上面提到netty是一个高性能的网络通信框架,对netty高性能分为三部分,即网络IO、线程模型和内存优化:
2.1 网络IO模型
Java IO流从概念上来说是一个连续的数据流,既可以从流中读取数据,也可以往流中写数据。IO相关的媒介包括:
- 文件
- 管道
- 网络连接
- 内存缓存
- System.in, System.out
IO的设计,主要是解决IO相关的操作。从数据传输的方式上,分为字节流和字符流。字节流一次性读取传输一个字节,而字符流则是以字符为单位进行读取传输。
LINUX中进程无法直接操作I/O设备,必须通过系统调用请求kernel来协助完成I/O动作。内核会为每个I/O设备维护一个缓冲区,IO输入时应用进程请求内核,内核会先看缓冲区中有没有相应的缓存数据,有数据则直接复制到进程空间,没有的话再到设备中读取。通常用户进程中的一个完整IO分为两阶段:用户进程空间<-->内核空间、内核空间<- ->设备空间。
由于CPU和内存的速度远远高于外设的速度,所以在IO编程中,就存在速度严重不匹配的问题,所以有了同步/异步,阻塞和非阻塞IO之分。
2.1.1 IO 模型
IO模型分为:BIO、NIO、IO多路复用、信号驱动IO和AIO。
BIO:进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程,操作成功则进程获取到数据。BIO阻塞时,其它应用进程还能正常执行,所以不消耗CPU时间,这种模型的CPU利用率较高。
NIO:非阻塞IO模型在内核数据没准备好,需要进程阻塞的时候,就返回一个错误,以使得进程不被阻塞;由于CPU需要不断轮询内核数据是否准备好,CPU相比利用率会低一些。
IO多路复用:多个的进程的IO可以注册到一个复用器(selector)上,然后用一个进程调用该select,,select会监听所有注册进来的IO,这一过程会被阻塞,当某一个套接字可读时返回,之后再用recvfrom吧数据从内核拷贝到进程中。 IO多路复用也被称为事件驱动IO。
信号驱动IO:当进程发起一个IO操作,会向内核注册一个信号处理函数,内核立即返回,应用程序可以继续进行。当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。相比于非阻塞的IO轮询,信号驱动IO的CPU利用率更高。
AIO:当进程发起一个IO操作,应用进程执行aio_read系统调用会立即返回,应用程序可以继续进行,内核会在所有操作完成后向应用程序发送信号。AIO和信号驱动IO的区别是:AIO的信号是通知进程IO完成,而信号驱动IO是通知应用程序可以进行IO。
2.1.2 Java NIO
NIO即new IO,是在JDK1.4引入的,NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。
NIO的核心对象包括:
- Buffer:在NIO中,所有的数据都是用Buffer处理的,它是NIO读写数据的中转池。Buffer实质上是一个数组,通常是一个字节数据,但也可以是其他类型的数组。
- Channel:是一个对象,可以通过channel读取和写入数据,是IO中流的抽象。但是channel是双向的,也可以是异步读写,并且channel读写必须通过buffer。
- Selector:是一个对象,可以同时监听多个channel上发生的事件,并且能够根据事件情况决定Channel读写。
// 打开Selector
Selector selector = Selector.open();
// 将channel注册到selector
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
Selector感兴趣的事件有SelectionKey.OP_CONNECT, SelectionKey.OP_ACCEPT, SelectionKey.OP_READ, SelectionKey.OP_WRITE。
SelectionKey表示通道channel在Selector上的注册,事件的传递是通过SelectionKey,也可以通过selectionKey获取注册的channel和对应绑定的selector。
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
一旦向selector注册一个或者多个通道后,就可以调用重载的select()方法,select()方法会返回读事件已经就绪的那些通道
- int select():阻塞到至少有一个通道的事件就绪
- int select(long timeout):与select一样,多个一个超时时间
- int selectNow():不会阻塞,不管什么通道就绪都立刻返回,如果没有通道可选择,就返回0.
一旦调用了select()
方法,它就会返回一个数值,表示一个或多个通道已经就绪,然后你就可以通过调用selector.selectedKeys()
方法返回的SelectionKey集合来获得就绪的Channel。某个线程调用select()方法后阻塞了,即使没有通道就绪,也有办法让其从select()方法返回。
- 让其它线程在调用select方法的对象上调用
Selector.wakeup()
方法即可,阻塞在select()方法上的线程会立马返回。 - 如果其它线程调用了wakeup(),但是当前没有线程阻塞在select上,下一个调用select阻塞的线程会被立即唤醒。
2.1.3 Java NIO和netty关系
直接使用Java NIO的缺点:
- NIO的类库和API繁杂,需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等才能很好使用。
- 可靠性较弱,需要自行维护,工作量大,例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题;
- JDK NIO的BUG,例如epoll bug,它会导致Selector空轮询,最终导致CPU 100%。
Netty是对Java NIO的封装框架,简化了NIO的使用难度,Netty特性总结如下:
- API使用简单,开发门槛低
- 功能强大,预置了多种编解码功能,支持多种主流协议
- 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展
- 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优
- 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼
- 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时更多的新功能会加入
- 经历了大规模的商业应用考验,质量得到验证。
2.2 Netty中的NIO关键组件
2.2.1 Buffer缓冲区
缓冲区本质上是一个可以读写数据的内存块,Netty封装Buffer对象提供了一组方法,可以更轻松地使用内存块,并且跟踪和记录缓冲区的状态变化。从Channel
读取或写入的数据都必须经由Buffer。
Buffer的类图结构有:
public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf> {
public ByteBuf() {
}
// 存储大小
public abstract int capacity();
public abstract ByteBuf capacity(int var1);
...
public abstract int readerIndex();
public abstract int writerIndex();
public abstract ByteBuf clear();
public abstract ByteBuf markReaderIndex();
public abstract ByteBuf resetReaderIndex();
public abstract ByteBuf markWriterIndex();
public abstract ByteBuf resetWriterIndex();
...
}
public abstract class AbstractByteBuf extends ByteBuf {
...
int readerIndex;
int writerIndex;
private int markedReaderIndex;
private int markedWriterIndex;
private int maxCapacity;
// 可读
public ByteBuf readerIndex(int readerIndex) {
if (readerIndex >= 0 && readerIndex <= this.writerIndex) {
this.readerIndex = readerIndex;
return this;
} else {
throw new IndexOutOfBoundsException(String.format("readerIndex: %d (expected: 0 <= readerIndex <= writerIndex(%d))", readerIndex, this.writerIndex));
}
}
// 可写
public ByteBuf writerIndex(int writerIndex) {
if (writerIndex >= this.readerIndex && writerIndex <= this.capacity()) {
this.writerIndex = writerIndex;
return this;
} else {
throw new IndexOutOfBoundsException(String.format("writerIndex: %d (expected: readerIndex(%d) <= writerIndex <= capacity(%d))", writerIndex, this.readerIndex, this.capacity()));
}
}
// 清理Buffer
public ByteBuf clear() {
this.readerIndex = this.writerIndex = 0;
return this;
}
public ByteBuf markReaderIndex() {
this.markedReaderIndex = this.readerIndex;
return this;
}
public ByteBuf resetReaderIndex() {
this.readerIndex(this.markedReaderIndex);
return this;
}
public ByteBuf markWriterIndex() {
this.markedWriterIndex = this.writerIndex;
return this;
}
public ByteBuf resetWriterIndex() {
this.writerIndex = this.markedWriterIndex;
return this;
}
...
}
netty的ByteBuffer有几个特性:
- Netty的ByteBuffer提供两个指针变量用于顺序读和顺序写,ReadIndex和WriteIndex。两个指针将Buffer分成3个区域,读取的数据只能位于readIndex和writeIndex之间,可写数据位于writeIndex和capacity之间。0到readIndex之间的区域是已经读取过的区域,可以调用discardReadBytes来重用这部分区间,以节约内存,防止Buffer的动态扩张。
- clear操作,并不会清空buffer的存储内容,而是用来充值readIndex和writeIndex,position,Mark和limit的,将他们还原为初始设置值。
- Mark和reset,Mark操作会将当前的位置指针备份到Mark变量中,调用reset后会将指针恢复到备份的位置,这种操作主要是因为对于某些读写需要回滚。
从存储结构上,buffer分为堆内存和直接内存buffer。
- 堆内存的特点是分配和回收速度快,也能被JVM自动管理,缺点是做Socket IO 操作时,需要从用户态拷贝到内核态,多一次复制操作。
- 直接内存buffer在堆外分配,缺点是分配和回收速度慢,但是做Socket IO操作时,会少一次内存拷贝。
结合对内对外内存分配的特点,在IO通信的时候使用直接内存分配,可以减少JVM的内存分配限制,还可以直接使用zeroCopy技术。后端业务编码的时候,可以直接使用HeapByteBuffer。
Netty的容量自动扩展,在小于4M的时候,采用倍增;大于等于4M时,采用每次增加4M。
select, poll和epoll
在OS上,对多个IO事件的监听是通过select/poll和epoll来实现的。他们的区别如下:
select
int select(int n, fd_set *readfds, fd_set *writefds, struct timeval *timeout)
- fd_set使用数组实现,数组大小使用FD_SETSIZE,所以只能监听少于FD_SETSIZE的描述符
- timeout为超时参数,调用select会一直阻塞直到描述符事件ready或者超时;
- 成功调用会返回1,错误返回-1,超时返回0
poll
int poll(struct pollfd *fds, unsigned int nfds, int timeout)
poll的功能和select类似,但poll中描述符是polld类型数组。polld定义如下:
struct pollfd{
int fd;
short events; /* request events */
short revents; /* return events */
}
epoll
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * enevts, int maxevents, int timeout);
epoll_ctl()用于向内核注重心的描述符或者改变某个文件描述的状态。已注册的描述符会在内核中维护在一颗红黑树上,通过回调函数内核会将IO准备好的描述符加入到一个链表中管理,进程调用epoll_wait()便可以得到事件完成的描述符。
epoll的描述符事件有两种触发模式:LT(level trigger)和ET(edge trigger)。
比较
- select和poll的功能基本相同,但是select会修改描述符,poll不会;
- select的描述符使用数组实现,默然大小不超过1024,而poll没有限制;
- poll提供了更多的事件类型,并且对描述符重复利用率比select高
- 如果一个线程对某个描述符调用了select或者poll,另一个线程关闭了改描述符,会导致结果不确定。
- 几乎所有的OS都支持select,但只有新的OS支持poll。
- epoll比select和poll更叫灵活而且描述符没有限制,对多线程也更友好,一个线程调用epoll_wait(),另一个线程关闭了这个描述符也不会产生不确定情况,但epoll仅适用于linux OS
场景:
- select 的timeout参数是微妙,而poll和epoll在毫秒级别;
- poll没有最大描述符数量限制,如果平台实时性要求不高,可使用poll;
- 在Linux OS上并且有大量描述符(大于1000)需要同时轮询,选择epoll。
2.2.2 Channel组件
io.netty.channel.Channel时Netty对网络的抽象,它组合了一组功能,包括不限于网络的读、写、客户端发起连接,主动关闭连接,关闭链路,获取通信双方的地址等,还包括获取该channel的eventLoop,获取缓冲区分配类BytebufferAllocator和pipeline等。
jdk自带了channel,定义如下
package java.nio.channels;
import java.io.IOException;
import java.io.Closeable;
public interface Channel extends Closeable {
public boolean isOpen();
public void close() throws IOException;
}
netty自实现了channel,package io.netty.channel;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.AttributeMap;
import java.net.SocketAddress;
public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {
ChannelId id();
EventLoop eventLoop();
Channel parent();
ChannelConfig config();
boolean isOpen();
boolean isRegistered();
boolean isActive();
ChannelMetadata metadata();
SocketAddress localAddress();
SocketAddress remoteAddress();
ChannelFuture closeFuture();
boolean isWritable();
long bytesBeforeUnwritable();
long bytesBeforeWritable();
Unsafe unsafe();
ChannelPipeline pipeline();
ByteBufAllocator alloc();
Channel read();
Channel flush();
}
为什么不适用JDK提供的channel,Netty自实现了channel,主要原因有:
- jdk的serverSocket,socketChannel是SPI类接口,主要职责是网络IO操作,扩展它与重新开发一个Channel工作量差不多;
- Netty的channel需要配合自身的框架特性,自定义channel功能上更加灵活。
channel中常用的方法:
- channel.read(),从当前channel读取数据到第一个inbound缓冲区中,如果数据被成功读取,会触发一个channelHandler.channelRead(ChannelHanlderContext context, Object)事件。读取操作完后,紧接着会触发ChannelHandler.channelReadComplete(ChannelHandlerContext context)事件,这样业务的channelhandler可判断是否还需要读取数据。
- ChannelFuture write(Object msg),将msg通过pipeline写入channel中。注意,write操作只是将数据存入到消息发送的环形数组中,并没有真正发送,调用flush操作后才会被发送。
- ChannelFuture write(Object msg, ChannelPromise promise),与write功能相同,promise负责设置写入操作的结果。
- ChannelFuture writeAndFlush(Object msg, ChannelPromise promise),相当于write和flush操作的结合ChannelFuture writeAndFlush(Object msg)
- ChannelFuture close( ChannelPromise promise),主动关闭当前连接,promise处理操作结果并负责通知
- ChannelFuture disconnect( ChannelPromise promise),请求断开与远程通信端的连接,promise获取操作结果的通知消息
- ChannelFuture connect(SocketAddress address),与远程地址建立连接,如果请求连接被拒绝,操作结果为ConnectException。这个操作会触发channelHandler.connect(ChannelHandlerContext context, SocketAddress address, ChannelPromise promise)事件。
- ChannelFuture bind(SocketAddress localAddress),绑定指定的本地地址,会触发事件
NioServerSocketChannel和NioSocketChannel,UnSafe类是channel的辅助接口,所以的IO读写都是同UnSafe类完成的。
2.2.3 ChannelPipeline和ChannelHandler
ChannelPipeline和ChannelHandler的关系类似Servlet和filter的关系,channelHandler对channel进行过滤,而ChannelPipeline提供环境,持有channelHandler事件拦截器的链表。可以方便的增加和删除channelHandler来实现对channel中数据流的处理。
public interface ChannelPipeline extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable<Map.Entry<String, ChannelHandler>> {
ChannelPipeline addFirst(String var1, ChannelHandler var2);
ChannelPipeline addFirst(EventExecutorGroup var1, String var2, ChannelHandler var3);
...
ChannelPipeline fireChannelRead(Object var1);
ChannelPipeline fireChannelReadComplete();
ChannelPipeline fireChannelWritabilityChanged();
ChannelPipeline flush();
}
public interface ChannelHandler {
void handlerAdded(ChannelHandlerContext var1) throws Exception;
void handlerRemoved(ChannelHandlerContext var1) throws Exception;
/** @deprecated */
@Deprecated
void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;
@Inherited
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sharable {
}
}
用户在操作Netty不需要自己创建pipeline,因为bootstrap在启动的时候,会为每一个channel创建一个独立的pipeline,对于使用者来说,只需要将channelHandler加入到pipeline中。
ChannelPipeline支持运行时动态添加或删除handler,并且ChannelPipeline是线程安全的。
自定义实现channelHandler,通常只需要继承ChannelHandlerAdapter就行,对需要处理的方法进行覆盖。
2.2.4 ChannelFuture和Promise
Netty中所有的操作都是异步的,为了获取操作的结果,设计了ChannelFuture。ChannelFuture有两种状态,当一个IO操作开始时,会创建一个ChannelFuture,状态是unCompleted。一旦IO操作完成,会被设置为completed状态。
public interface ChannelFuture extends Future<Void> {
Channel channel();
ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> var1);
ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... var1);
ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> var1);
ChannelFuture removeListeners(GenericFutureListener<? extends Future<? super Void>>... var1);
ChannelFuture sync() throws InterruptedException;
ChannelFuture syncUninterruptibly();
ChannelFuture await() throws InterruptedException;
ChannelFuture awaitUninterruptibly();
boolean isVoid();
}
强烈建议通过增加ChannelFuture的监听器GenericFutureListener处理。通过GenericFutureListener代替get的原因是:异步IO时,如果不设置超时时间,有可能导致线程一直被挂起,甚至挂死;一旦设置超时时间,如果时间到达后事件还未完成,就会出现异常,所以通过异步回调的方式最佳。
此外,不要在ChannelHandler中调用ChannelFuture.await()方法,可能会导致死锁,原因是:由IO线程负责异步通知发起IO操作的用户线程,如果IO线程和用户线程是同一个,就会导致IO线程等待自己通知完成操作,陷入相互等待。
public interface Promise<V> extends Future<V> {
Promise<V> setSuccess(V var1);
boolean trySuccess(V var1);
Promise<V> setFailure(Throwable var1);
boolean tryFailure(Throwable var1);
boolean setUncancellable();
Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> var1);
Promise<V> addListeners(GenericFutureListener<? extends Future<? super V>>... var1);
Promise<V> removeListener(GenericFutureListener<? extends Future<? super V>> var1);
Promise<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... var1);
Promise<V> await() throws InterruptedException;
Promise<V> awaitUninterruptibly();
Promise<V> sync() throws InterruptedException;
Promise<V> syncUninterruptibly();
}
Promise是可写的Future,因为jdk自带的Future并没有提供写方法,Netty通过promise对future扩展,用于设置IO操作的结果。
本章续上一章节没讲完的IO模型。
2.2.5 Selector
Selector
能够检测多个注册的通道channel上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的处理,这样就可以只用一个单线程去管理多个通道,即IO多路复用。当channel真正有读写事件发生时,线程才会进行读写,大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,也避免了多线程之间的上下文切换开销。
jdk中selector的定义:
public abstract class Selector implements Closeable {
// 初始化selector
protected Selector() { }
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
public abstract boolean isOpen();
public abstract SelectorProvider provider();
// 返回selector的keyset
public abstract Set<SelectionKey> keys();
// 返回选中的key
public abstract Set<SelectionKey> selectedKeys();
/**
* Selects a set of keys whose corresponding channels are ready for I/O
* operations. 非阻塞方法
*/
public abstract int selectNow() throws IOException;
// 阻塞方法,直到有一个IO ready,或者主动调用selector的wakeUp方法,或者等待timeout过期后返回
public abstract int select(long timeout)
throws IOException;
public abstract int select() throws IOException;
public abstract Selector wakeup();
public abstract void close() throws IOException;
}
netty中将selector封装到了NioEventLoop中。
public final class NioEventLoop extends SingleThreadEventLoop {
...
private Selector selector;
private Selector unwrappedSelector;
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider, SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
} else if (strategy == null) {
throw new NullPointerException("selectStrategy");
} else {
this.provider = selectorProvider;
SelectorTuple selectorTuple = this.openSelector();
this.selector = selectorTuple.selector;
this.unwrappedSelector = selectorTuple.unwrappedSelector;
this.selectStrategy = strategy;
}
}
private SelectorTuple openSelector() {
final AbstractSelector unwrappedSelector;
try {
unwrappedSelector = this.provider.openSelector();
} catch (IOException var7) {
throw new ChannelException("failed to open a new selector", var7);
}
if (DISABLE_KEYSET_OPTIMIZATION) {
return new SelectorTuple(unwrappedSelector);
} else {
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
return Class.forName("sun.nio.ch.SelectorImpl", false, PlatformDependent.getSystemClassLoader());
} catch (Throwable var2) {
return var2;
}
}
});
if (maybeSelectorImplClass instanceof Class && ((Class)maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
final Class<?> selectorImplClass = (Class)maybeSelectorImplClass;
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField);
if (cause != null) {
return cause;
} else {
cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField);
if (cause != null) {
return cause;
} else {
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
return null;
}
}
} catch (NoSuchFieldException var4) {
return var4;
} catch (IllegalAccessException var5) {
return var5;
}
}
});
if (maybeException instanceof Exception) {
this.selectedKeys = null;
Exception e = (Exception)maybeException;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e);
return new SelectorTuple(unwrappedSelector);
} else {
this.selectedKeys = selectedKeySet;
logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
return new SelectorTuple(unwrappedSelector, new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
}
} else {
if (maybeSelectorImplClass instanceof Throwable) {
Throwable t = (Throwable)maybeSelectorImplClass;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
}
return new SelectorTuple(unwrappedSelector);
}
}
}
}
Netty对SelectionKey的优化:
jdk中对selectorImpl定义有:
public abstract class SelectorImpl extends AbstractSelector {
protected Set<SelectionKey> selectedKeys = new HashSet();
protected HashSet<SelectionKey> keys = new HashSet();
private Set<SelectionKey> publicKeys;
private Set<SelectionKey> publicSelectedKeys;
protected SelectorImpl(SelectorProvider sp) {
super(sp);
keys = new HashSet<>();
selectedKeys = new HashSet<>();
publicKeys = Collections.unmodifiableSet(keys);
publicSelectedKeys = Util.ungrowableSet(selectedKeys);
}
}
Netty中selectedKeys对象
final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {
SelectionKey[] keys = new SelectionKey[1024];
int size;
SelectedSelectionKeySet() {}
public boolean add(SelectionKey o) {
if (o == null) {
return false;
} else {
this.keys[this.size++] = o;
if (this.size == this.keys.length) {
this.increaseCapacity();
}
return true;
}
}
...
}
修改成了数组。HashSet的add方法在发生哈希冲突时的时间复杂度是O(n), jdk优化后也是O(log n), 为此Netty通过反射机制, 将底层的这个HashSet用数组替换了, 毕竟向数组中添加数据的时间复杂度是O(1).
补:EventLoop和EventLoopGroup
首先我们看一下EventLoop的类图结构
可以看到EventLoop继承自eventLoopGroup, 同时继承了OrderedEventExecutor。 EventLoop内部包含了线程池所有的执行方法,也包含了Scheduled调度方法,可以独立进行任务调度。同时,OrderedEventExecutor可以判断一个线程是否属于当前的eventLoop以及eventLoop所在的组。
public interface EventLoop extends OrderedEventExecutor, EventLoopGroup {
EventLoopGroup parent();
}
public interface EventLoopGroup extends EventExecutorGroup {
EventLoop next();
ChannelFuture register(Channel var1);
ChannelFuture register(ChannelPromise var1);
/** @deprecated */
@Deprecated
ChannelFuture register(Channel var1, ChannelPromise var2);
}
public interface EventExecutorGroup extends ScheduledExecutorService, Iterable<EventExecutor> {
boolean isShuttingDown();
Future<?> shutdownGracefully();
Future<?> shutdownGracefully(long var1, long var3, TimeUnit var5);
Future<?> terminationFuture();
/** @deprecated */
@Deprecated
void shutdown();
/** @deprecated */
@Deprecated
List<Runnable> shutdownNow();
EventExecutor next();
Iterator<EventExecutor> iterator();
Future<?> submit(Runnable var1);
<T> Future<T> submit(Runnable var1, T var2);
<T> Future<T> submit(Callable<T> var1);
ScheduledFuture<?> schedule(Runnable var1, long var2, TimeUnit var4);
<V> ScheduledFuture<V> schedule(Callable<V> var1, long var2, TimeUnit var4);
ScheduledFuture<?> scheduleAtFixedRate(Runnable var1, long var2, long var4, TimeUnit var6);
ScheduledFuture<?> scheduleWithFixedDelay(Runnable var1, long var2, long var4, TimeUnit var6);
}
EventLoopGroup是一组EventLoop,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,后续这个 Channel 上的 io 事件都由此 EventLoop 来处理(保证了 io 事件处理时的线程安全)。
public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {
...
public interface Unsafe {
...
void register(EventLoop var1, ChannelPromise var2); // 将channel绑定到eventLoop上
void bind(SocketAddress var1, ChannelPromise var2);
void connect(SocketAddress var1, SocketAddress var2, ChannelPromise var3);
void disconnect(ChannelPromise var1);
...
}
}
// 具体的执行register方法
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
} else if (AbstractChannel.this.isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
} else if (!AbstractChannel.this.isCompatible(eventLoop)) {
promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
} else {
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
this.register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
public void run() {
AbstractUnsafe.this.register0(promise);
}
});
} catch (Throwable var4) {
AbstractChannel.logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, var4);
this.closeForcibly();
AbstractChannel.this.closeFuture.setClosed();
this.safeSetFailure(promise, var4);
}
}
}
}
常见主要有以下两种EventLoopGroup:
- 1)NioEventLoopGroup:处理IO事件,普通任务,定时任务;
- 2)DefaultEventLoopGroup:处理普通任务,定时任务。
public class EventLoopTest {
public static void main(String[] args) {
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(2);
for(int i = 10; i>0; i--){
System.out.println(eventLoopGroup.next());
}
}
}
输出:
io.netty.channel.nio.NioEventLoop@45018215
io.netty.channel.nio.NioEventLoop@65d6b83b
io.netty.channel.nio.NioEventLoop@45018215
io.netty.channel.nio.NioEventLoop@65d6b83b
io.netty.channel.nio.NioEventLoop@45018215
io.netty.channel.nio.NioEventLoop@65d6b83b
io.netty.channel.nio.NioEventLoop@45018215
io.netty.channel.nio.NioEventLoop@65d6b83b
io.netty.channel.nio.NioEventLoop@45018215
io.netty.channel.nio.NioEventLoop@65d6b83b
可以看到这里会对生成的两个NioEventLoop循环打印。
public static void main(String[] args) {
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(2);
// 普通的线程提交
eventLoopGroup.next().execute(EventLoopTest::print);
// 定时线程提交
eventLoopGroup.next().scheduleAtFixedRate(EventLoopTest::print, 1,1, TimeUnit.SECONDS);
}
private static void print() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date())+ " " +Thread.currentThread());
}
2022-10-23 11:00:25 Thread[nioEventLoopGroup-2-1,10,main]
2022-10-23 11:00:26 Thread[nioEventLoopGroup-2-2,10,main]
2022-10-23 11:00:27 Thread[nioEventLoopGroup-2-2,10,main]
2022-10-23 11:00:28 Thread[nioEventLoopGroup-2-2,10,main]