netty基本概念
netty作为java阵营的网络库。因为其性能优势,使用相当广泛。
- 很好的抽象,能够方便切换各种实现(select,poll,epoll)模型
- 一个给定channel的io操作都是由相同的Thread(对应的EventLoop)执行的,实际上消除了对于同步的需要。
- 处理链,channelPipeline。
netty了解了下面几个类,对netty就有了初步的理解了。
- ByteBuf:封装ByteBuffer,提供对网络数据的支持
- Channel:代表socket。关联EventPoll:代表work线程。
- ChannelHandler:对于事件对具体的业务处理。也是程序员关心最多的接口
- ChannelFuture:扩展系统的Future。不用阻塞等待,可以添加listenner,并且用于channle关联的线程执行listenner。
1. ByteBuf
jdk提供的ByteBuffer,提供能在分配直接内存,在堆外的内存,提供更高的性能,更适合用来做io。netty在其基础上实现了ByteBuf。扩展为更加易用的功能。包括提供读写双index,动态扩展,池化,和自定义扩展。
+-------------------+------------------+------------------+
| discardable bytes | readable bytes | writable bytes |
| | (CONTENT) | |
+-------------------+------------------+------------------+
| | | |
0 <= readerIndex <= writerIndex <= capacity
直接内存
直接内存,是堆外内存。Netty默认的I/O buffer 使用的是DirectByteBuf
直接内存,可以在调用系统api的时候,直接传入,少了从java堆到本机堆的拷贝,提供所谓的零拷贝。
1:传送文件的时候,直接从文件到网络接口。不用先读取文件到用户空间,再发送。(对应的系统层面,Linux 提供的 mmap 系统调用, 它可以将一段用户空间内存映射到内核空间,) 2:CompositeByteBuf,消除了用户层,逻辑和逻辑之间的copy。两个生成的ByteBuf,当做一个直接发送。 3:ByteBuffer,在调用io,系统api的时候,少了本地堆和系统堆的copy
ByteBuf的管理
- 内存池 在网络请求,返回中,频繁申请的ByteBuf内存,往往是“朝生夕灭”。通过内存池的方式,可以大大降低GC频率,提高性能。
ByteBuf buf = Unpooled.buffer(10*1024)
buf.release();
\\内存池
ByteBufAllocator alloc = channel.alloc();
ByteBuf buffer = alloc.buffer();
提到内存池,也顺带提下netty实现的FastThreadLocal。比JDK版本提供更高的访问效率。用自增的index而不是hash来查找变量。并且解决了伪共享的问题。 netty处理消息,需要频繁的分配内存。内存池就用到了FastThreadLocal。一批channel公用一个内存池。
- 引用计数
平常使用bytebuf的时候,一般不用手都调用release释放内存,因为netty在许多地方会隐私调用release情况,情况如下:
- 继承SimpleChannelInboundHandler。在channelRead0后,基类会调用ReferenceCountUtil.release(msg)
- 解码器的自动释放。解码器的基类在解码后会嗲用rellease释放bytebuf。
- 调用发送自动释放。
ctx.writeAndFlush(byteBuf)
2. netty的线程模型
eventLoop
eventLoop也是一种设计模式。通常和reactor配合使用。 reactor和proactor是常拿来比较的两种高性能IO设计模式。区别在于谁负责真正的读取操作,reactor相当于linux的epoll,proactor相当于windows的iocp)
EventLoopGroup由EventLoopGroup指定线程数。创建对应的EventLoop。
继承ScheduledExecutorService接口,能像java线程池一样执行任务。其实和标准的epoll实现上,有点类似。
eventLoop是维护一个单线程,并且循环处理所有io事件和任务。不同的eventLoop继承自SingleThreadEventLoop
并实现各自的run
方法。
任务队列
基类用LinkedBlockingQueue
实现的任务队列,NioEventLoop用JCTOOls里的队列,专门优化多个生产者一个消费者的情况。最大化的减少锁的竞争。消息发送就封装成任务被执行。
每次run的时候,会根据ioRatio
来合理分配io和任务的执行时间。
传输模型
提供多种传输模型,并且能够方便的切换。
常用的非阻塞,nio,NioEventLoopGroup。利用jdk 1.4 提供的Selector API。底层可能使用poll或者epoll来实现的。如果是linux就是epoll实现的。但是已更加通用的方式使用epoll。所以性能上有妥协
- nioEventLoop
用Selector的Api,实现对事件的等待。每个EventLoop,只会等待,对应的socket。
- epollEventLoop
netty自己实现native的代码,调用epool的api,来等待事件。每个epollEventLoop都有自己的epoll内核对象,保证了特点2.
Executor
eventLoop还有executor。ThreadPerTaskExecutor。为什么用eventLoopGroup,而不用线程池。因为标准的线程池,设计成为不阻塞的任务。但是netty的任务都是循环,selector的结构。
线程绑定
新建立的channel,会将channel和其中一个eventLoop绑定。一个eventLoop会负责与其绑定的所有channel。这样,同一个连接的所有请求都是串行执行,避免了channelHandler的并发问题。
即使增加了线程池,将channelhandler和io线程eventloop分开。同一个channel还是会分配对应的业务线程,保证串行。
pipeline.addLast(executor, "handler", handler);
eventloop执行的时候会判断是否在eventloop。channel.eventLoop()
。
if (inEventLoop()) {
register0(ch, interestOps, task);
} else {
try {
submit(new Runnable() {
@Override
public void run() {
register0(ch, interestOps, task);
}
}).sync();
} catch (InterruptedException ignore) {
Thread.currentThread().interrupt();
}
}
3. 其他
channelPipeline
pipeline用到了两个设计模式。
- adapter:ChannelInboundHandlerAdapter。提供了默认实现,传递给下一个handler,可以继承来修改需要修改的地方
- 拦截过滤器:Pipeline,事件链
netty提供了很多可以方便使用的channelhandle或者解码器。
ByteToMessageDecoder
提供字节到了msg的解码基类。有很多继承于它的实现可以直接使用,满足大部分使用场景。LineBasedFrameDecoder
可以每隔每行输入。LengthFieldBasedFrameDecoder
通过长度分割。
服务器的初始化
public void start() {
final CmdChannelHandler cmdChannelHandler = new CmdChannelHandler();
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LineBasedFrameDecoder(100)).addLast(cmdChannelHandler);
}
});
bootstrap.bind("0.0.0.0", 9977).sync().channel();
}
- 设置服务器的监听和work的线程组,bossGroup和workerGroup。
- 指定class NioServerSocketChannel,去创建NIO的Channel
- 自定义一个channelHandler来创建channel,并设置其pipeline。