目前在java socket服务器开发 基本上都是基于MINA 和 Netty.
所以这2个东西是非常的重要.
MINA不在本次说明的范围之内,有兴趣可以自行脑补(会用netty了,MINA基本上没问题.)
下面咱们说一下Netty,纵观市面上 Netty3X-Netty4X-Netty5X。一时让人摸不到头绪
我之前用的是Netty3X,后来看了一下Netty4X.真心发现坑很多。所以在这里记录一下.有助大家一起进步学习
主要看了2块内容 线程模型和PooledBytebuf
先说一下线程模型
Netty3X的线程模型
Netty 3.X的I/O操作线程模型比较复杂,它的处理模型包括两部分:
1:Inbound:主要包括链路建立事件、链路激活事件、读事件、I/O异常事件、链路关闭事件等;
2:Outbound:主要包括写事件、连接事件、监听绑定事件、刷新事件等。
我们首先分析下Inbound操作的线程模型:
从上图可以看出,Inbound操作的主要处理流程如下:
1:I/O线程(Work线程)将消息从TCP缓冲区读取到SocketChannel的接收缓冲区中;
2:由I/O线程负责生成相应的事件,触发事件向上执行,调度到ChannelPipeline中;
3:I/O线程调度执行ChannelPipeline中Handler链的对应方法,直到业务实现的Last Handler;
4:Last Handler将消息封装成Runnable,放入到业务线程池中执行,I/O线程返回,继续读/写等I/O操作;
5:业务线程池从任务队列中弹出消息,并发执行业务逻辑。
通过对Netty 3的Inbound操作进行分析我们可以看出,Inbound的Handler都是由Netty的I/O Work线程负责执行。
大概分析如下 Work线程(IO)收到消息各种decode + Hander以后 push到业务线程池
EndHandler以后 切换到 业务线程池这块内容.对于我的项目.我是自己实现的。
下面我们继续分析Outbound操作的线程模型:
从上图可以看出,Outbound操作的主要处理流程如下:
1:业务线程发起Channel Write操作,发送消息;
2:Netty将写操作封装成写事件,触发事件向下传播;
3:写事件被调度到ChannelPipeline中,由业务线程按照Handler Chain串行调用支持Downstream事件的Channel Handler;
4:执行到系统最后一个ChannelHandler,将编码后的消息Push到发送队列中,业务线程返回;
5:Netty的I/O线程从发送消息队列中取出消息,调用SocketChannel的write方法进行消息发送。
大概的分析流程就是我们在一个地方调用channel.write方法(业务线程中)其实一套的Handler+数据Encode是在业务线程中调用的,
全部完成以后发送到IO线程(这块和用户没关系).
所以在Netty3中 我们其实最最最关心的是收数据这块(消息吞吐.消息分发等).对于发数据其实没什么关心的.毕竟是在业务线程
Netty 4.X 版本线程模型
从上图可以看出,Outbound操作的主要处理流程如下:
I/O线程NioEventLoop从SocketChannel中读取数据报,将ByteBuf投递到ChannelPipeline,触发ChannelRead事件;
I/O线程NioEventLoop调用ChannelHandler链,直到将消息投递到业务线程,然后I/O线程返回,继续后续的读写操作;
业务线程调用ChannelHandlerContext.write(Object msg)方法进行消息发送;
如果是由业务线程发起的写操作,ChannelHandlerInvoker将发送消息封装成Task,放入到I/O线程NioEventLoop的任务队列中,由NioEventLoop在循环中统一调度和执行。放入任务队列之后,业务线程返回;
I/O线程NioEventLoop调用ChannelHandler链,进行消息发送,处理Outbound事件,直到将消息放入发送队列,然后唤醒Selector,进而执行写操作。
通过流程分析,我们发现Netty 4修改了线程模型,无论是Inbound还是Outbound操作,统一由I/O线程NioEventLoop调度执行
Note:
可以看到在Netty4X版本中 所有的 Encode Decode Handler 都在IO线程中调用了.原来的Wirte方法.会在业务线程封装成Task push到IO线程
所以这里对于业务会有一定的影响(PooledByteBuf的内存泄漏,耗时方法等) 都是很危险的.
但是除了缺点当然也有优点(以前业务线程池调用channel.write的时候)对于业务线程池可能出现多个线程同时访问一个实例的情况
所以要用synchronized修饰封装的Write方法
我这里在Read进入业务线程的时候 使用FSM 进行了 TryLock.所以在一个基类的源头就Cut了这种情况.
但是在Netty4X里面.我想大家可能就不必太Care这个问题了.毕竟Write这块是Push到PipeLineChannel线程中成为消息了.已经大大的降低了出现问题的情况(因为我用了FSM+TryLock)所以这块没做处理.
Pooledbytebuffer 使用
这个东西没怎么太关注过.但是实际使用的时候经常问题多多.所以记录一下自己对这东西的理解
1:在4X初始化的时候做一个配置可以提高效率
主要是加了以下两句:
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);//关键是这句
https://github.com/netty/netty/issues/3319---good case
2:以前在业务线程使用Bytebuf的.直接移植会造成内存泄漏
3:ReferenceCountUtil.release(msg); 和 SimpleChannelInboundHandler的关系
http://blog.csdn.net/linuu/article/details/51307060 一个我们需要注意的case(当然直接 netty + protobuf)可能已经会避免这些坑了
4:2进制数据处理 处理需谨慎哈哈