标签:ack group 指针 site java程序 coder write 刷新 读取数据
《Netty权威指南》在网上的评价不是非常高,尤其是第一版,第二版能稍好些?入手后高速翻看了大半本,不免还是想对《Netty权威指南(第二版)》吐槽一下:
源代码解析部分的条理性和代码排版好多了,感觉比其它部分的质量高多了。
不管如何,假设你是网络通信或后台中间件的入门者。尤其是Java程序猿,那么这本书还是值得入手的。尤其是书中对I/O模型、协议解析、可靠性等方面的点拨还是会让你有非常多收获的。好了吐槽就到这了,下面就是《Netty权威指南(第二版)》的重点摘录,抽掉了水分,全部干货都在这里了。
Linux从select -> poll -> epoll机制。简要说epoll的长处就是:从主动轮询+线性扫描变为被动事件通知,mmap避免到用户态的拷贝,更加简单的API。
Java方面呢,JDK 1.3之前仅仅有堵塞I/O,到1.4增加了NIO。
在JDK 1.5 update 10和Linux 2.6以上版本号。JDK使用epoll替换了select/poll。1.7增加了AIO。
堵塞BIO是我们最常见的一种形式。就不具体说了。
伪异步I/O利用堵塞I/O的Acceptor+线程池实现的是伪异步I/O。它仅仅是对同步堵塞I/O在系统资源方面使用方面做了“一小点”的优化(重用了线程)。可是 它没法从根本上解决同步I/O导致的通信线程堵塞问题。
TCP/IP知识复习:当消息接收方处理缓慢时,将不能及时从TCP缓冲区读取数据,这将会导致发送方的TCP window size不断变小直到为0。
此时两方处于Keep-Alive状态,发送方将不能再向TCP缓冲区写入消息。
假设使用的是同步堵塞I/O,write操作将无限期堵塞直到window size大于0或发生I/O异常。
非堵塞NIO的特点是:
NIO提供了非堵塞的读写操作。相比于BIO的确是异步的。因此从这个角度我们能够说NIO是异步非堵塞的。
然而假设严格依照UNIX网络编程模型定义的话,NIO并不能算是异步的,由于当事件完毕时不是由系统触发回调函数,而是须要我们不断轮询。
AIO才是真正的异步I/O:NIO仅仅是实现了读写操作的非堵塞。但它还是要靠轮询而非事件通知(虽然前面说过JDK 1.5里升级为epoll。但上层API还是轮询没有变化)。说它是异步的事实上就是想说它是非堵塞的。JDK 1.7 NIO 2中提供的AIO才是真正的异步I/O。
使用原生NIO开发的特点就是功能开发相对easy,但兴许的可靠性方面的工作量非常大。须要我们自己处理如断连重连、半包读写、网络拥堵等问题。而且,NIO中还可能有bug,如“臭名昭著”的Selector空轮询导致CPU使用率100%(大学做大作业就碰到过这个问题。当时还纳闷呢,原来是个bug啊)。
所以,要想自己高速开发出健壮可靠的高性能网络公共组件,还真不是件easy事!
Netty为我们提供了开箱即用的高性能、高可靠、安全可扩展的网络组建,同一时候还修复了NIO的一些bug,社区非常活跃。版本号升级快。相比而言,Netty真是个不错的选择!
Netty有下面几个核心API:
TCP是流协议,TCP底层并不了解上层业务数据的含义,它会依据TCP缓冲区的实际情况进行包的划分,一个完整的包可能被TCP拆分成多个包发送。也可能与其它小包封装成一个大的数据包发送,这就是所谓的拆包和粘包。
发生拆包的原因可能有:
经常使用的解决策略:
我们能够在自己定义Decoder和Encoder中实现序列化和反序列化,如常见的Jackson,MsgPack。ProtoBuf等等。
Reactor模型主要由多路复用器(Acceptor)、事件分发器(Dispatcher)、事件处理器(Handler)三部分组成。
深入研究的话,Reactor模型能够细分成三种:
在一些小型应用场景下也的确能够使用单线程模型。但对于高并发应用是不合适的。即便这个NIO线程将CPU跑满也无法满足海量消息的编解码和读写。此外这种模型在可靠性上也存在问题。由于一旦这个NIO线程进入死循环就会导致整个系统的不可用。
Netty对这三种都支持。通过调整线程池的线程个数、是否共享线程池等參数在三种方式间方便的切换。一般的Netty最佳实践例如以下:
由于在Handler内的数据读写、协议解析经常要保存一些状态,所以为了避免资源竞争。Netty对Handler採用串行化设计。即一个I/O线程会对我们配置到Netty中的Handler链的运行“负责究竟”。
正是有了这种设计,我们就能够放心的在Handler中保存各种状态。甚至使用ThreadLocal,全然无锁化的设计。
Netty的Handler在这一点上是不是与Struts2中的Action有点像呢?
在Netty内部,ByteBuffer默认使用堆外内存(Direct Buffer)作为缓冲区。这就避免了传统堆内存作缓冲区时的拷贝问题。使用传统堆内存时进行Socket读写时,JVM会先将堆内存缓冲区中的数据复制到直接内存中,然后再写入Socket。
此外。Netty也提供给开发人员一些工具实现零拷贝,这些工具都是我们能够利用的,比如:
随着JVM虚拟机和JIT即时编译技术的发展。对象的分配和回收成了一件非常轻量级的工作。可是对于缓冲区。特别是对于堆外直接内存,分配和回收却仍然是一件耗时的操作。所以,Netty提供了内存池来实现缓冲区的重用机制。
这里再简单介绍一下Netty内部的内存管理机制。
首先,Netty会预先申请一大块内存。在内存管理器中一般叫做Arena。
Netty的Arena由很多Chunk组成。而每一个Chunk又由一个或多个Page组成。Chunk通过二叉树的形式组织Page,每一个叶子节点表示一个Page,而中间节点表示内存区域,节点自己记录它在整个Arena中的偏移地址。当区域被分配出去后,中间节点上的标记位会被标记,这样就表示这个中间节点下面的全部节点都已被分配了。
在凌晨等业务低谷期。假设发生网络闪断、连接Hang住等问题时,由于没有业务消息,应用进程非常难发现。到了白天业务高峰期时,会发生大量的网络通信失败。导致应用进程一段时间内无法处理业务消息。因此能够採用心跳检測机制。一旦发现网络故障则马上关闭链路。并主动重连。
具体来看。心跳检測机制一般的设计思路是:
1)当连续周期T没有读写消息,client主动发送Ping心跳消息给服务端。
2)假设在下一周期T到来时没有收到服务端的Pong心跳或业务消息,则心跳失败计数器加1。
3)每当client接收到服务端的Pong心跳或业务消息,则心跳失败计数器清零。当计数器达到N次,则关闭链路。间隔INTERVAL后发起重连操作(保证服务端有充足的时间释放资源。所以不能失败后马上重连)。
4)同理,服务端也要用上面的方法检測client(保证不管通信哪一方出现网络故障,都能被及时检測出来)。
Netty依据ByteBuf的maxCapacity保护内存不会超过上限。
此外默认的TailHandler会负责自己主动释放ByteBuf的缓冲区。
Netty利用JVM注冊的Shutdown Hook拦截到退出信号量。然后运行退出操作:释放各个模块的占用资源、将缓冲区中剩余的消息处理完毕或者清空、将待刷新的数据持久化磁盘或数据库等。
(略)
在Netty中能够非常方便地改动TCP的參数。比如缓冲区大小的參数SO_RCVBUF/SO_SNDBUF、关闭将大量小包优化成大包的Nagle算法的參数SO_TCPNODELAY參数来避免对时延敏感应用的影响、以及Linux软中断等。
- Netty协议栈不区分服务端和client,开发完毕后可同一时候支持。
- 可靠性设计:心跳机制。重连机制
- 安全性设计:内网採取IP白名单进行安全过滤。外网採取更加严格的SSL/TSL安全传输。
- 扩展性设计:业务功能能够在消息头中附加流水号等。利用Netty提供的attachment字段扩展。
Netty中关键的类库都提供了接口或抽象类以及大量的工厂类供开发人员扩展,像Handler则是直接提供了ChannelPipeline实现了责任链模式。方便我们做随意的组合和扩展。
标签:ack group 指针 site java程序 coder write 刷新 读取数据
原文地址:http://www.cnblogs.com/lxjshuju/p/7163285.html