作为一个分布式消息队列,通信的质量至关重要。基于TCP协议和Socket实现一个高效、稳定的通信程序并不容易,有很多大大小小的“坑”等待着经验不足的开发者。RocketMQ选择不重复发明轮子,基于Netty库来实现底层的通信功能。
1 Netty介绍
Netty是一个网络应用框架,或者说是一个Java网络开发库。Netty提供异步事件驱动的方式,使用它可以快速地开发出高性能的网络应用程序,比如客户端/服务器自定义协议程序,大大简化了网络程序的开发过程。
Netty是一个精心设计的框架,它从许多协议实现中吸收了丰富的经验,比如FTP、SMTP、HTTP等许多基于二进制和文本的传统协议。借助Netty,可以比较容易地开发出达到Java网络专家+并发编程专家水平的通信程序。
了解Netty前需要对Java NIO有个基本的了解,熟悉Channel、ByteBuffer、Selector等基本概念。对于Java网络编程经验不多的读者,可以试着先用Java NIO的基本类写一个简单的Client/Server程序,然后再用Netty对比着实现一遍,这样比较容易理解Netty里各种组件存在的原因。
2 Netty架构总览
如图13-1所示,Netty主要分为三部分:一是底层的零拷贝技术和统一通信模型;二是基于JVM实现的传输层;三是常用协议支持。如果想深入了解的话可以阅读一些专门介绍Netty的书籍。
图13-1 Netty整体架构
3 重新实现ByteBuffer
在网络通信中,CPU处理数据的速度大大快于网络传输数据的速度,所以需要引入缓冲区,将网络传输的数据放入缓冲区,累积足够的数据再发给CPU处理。
Netty使用自己重新实现的buffer API,而不是使用NIO的ByteBuffer来表示一个连续的字节序列。新实现的buffer类型ByteBuf可以从底层解决ByteBuffer的一些问题,是一种更适合日常网络应用开发需要的缓存类型。重新实现的ByteBuf特性包括允许使用自定义的缓存类型、透明的零拷贝实现、比ByteBuffer更快的响应速度等。
字节缓存在网络通信中会被频繁地使用,ByteBuf实现的是一个非常轻量级的字节数组包装器。ByteBuf有读操作和写操作,为了便于用户使用,该缓冲区维护了读索引和写索引。ByteBuf由三个片段构成:废弃段、可读段和可写段。其中,可读段表示缓冲区实际存储的可用数据。当用户使用read或者skip方法时,将会增加读索引。读索引之前的数据将进入废弃段,表示该数据已被使用过了。此外,用户可主动使用discardReadBytes清空废弃段以便得到更多的可写空间。简单来说和ByteBuffer相比,ByteBuf用在网络编程时更合适,更易用。
3 统一的异步I/O接口
传统的Java I/O API在应对不同的传输协议时需要使用不同的类型和方法。例如java.net.Socket和java.net.DatagramSocket,但它们没有相同的父类型,因此需要使用不同的调用方式执行Socket操作。因为在模式上不匹配,所以更换网络应用的传输协议时工作会变得很繁杂。由于(Java I/O API)缺乏协议间的可移植性,无法在不修改网络传输层的前提下增加多种协议的支持。从理论上讲,多种应用层协议可运行在多种传输层协议之上,例如TCP/IP、UDP/IP、SCTP和串口通信。
还有个复杂的情况是,Java的新I/O(NIO)API与原有的阻塞式I/O(OIO)API不兼容。这两者无论是在设计上还是在性能上,其特性都不相同,可是在开发时一般只选择某一种API。例如,在用户数较小的时候可以选择使用传统的OIO(Old I/O)API,毕竟与NIO相比使用OIO更加容易;但是当业务快速增长,服务器需要同时处理成千上万的客户连接时问题就来了,这时候不得不尝试使用NIO来解决,新的NIO Selector编程接口和Old I/O差别很大,很难做到快速升级。
Netty有一个被称为Channel的统一异步I/O编程接口,这个编程接口抽象了所有点对点的通信操作。这样,如果应用是基于Netty的某一种传输方式来实现的,则可以快速迁移到另一种传输实现上。Netty提供了几种拥有相同编程接口的基本传输实现:
·基于NIO的TCP/IP传输(io.netty.channel.nio);
·基于OIO的TCP/IP传输(io.netty.channel.oio);
·基于OIO的UDP/IP传输(io.netty.channel.oio);
·本地传输(io.netty.channel.local)。
切换不同的传输实现通常只需修改几行代码,而且由于核心API具有高度的可扩展性,很容易定制自己的传输实现。
4 基于拦截链模式的事件模型
一个定义良好并具有扩展能力的事件模型可以大大提高事件驱动程序的效率,Netty就具有定义良好的I/O事件模型,它采用严格的层次结构来区分不同的事件类型,Netty也允许在不破坏现有代码的情况下实现自己的事件类型。事件模型是Netty的一个亮点,很多NIO通信框架没有或者仅有有限的事件模型概念,当需要一个新的事件类型的时候常常需要修改已有的代码,有的甚至不允许进行自定义的扩展。
在Netty中,ChannelPipeline内部的一个ChannelEvent被一组ChannelHandler处理。这个管道是Intercepting Filter(拦截过滤器)模式的一种高级形式的实现,因此对于一个事件如何被处理,以及管道内部处理器间的交互过程,用户拥有绝对的控制力。
5 高级组件
Netty提供了一系列的高级组件来让开发过程更加快捷,比如Codec框架、SSL/TLS支持、HTTP实现等。
首先看看Codec框架。从业务逻辑代码中分离协议处理部分可以让代码结构变得更清晰,但是如果从零开始实现会有很高的复杂性,比如处理分段消息,相互叠加的多层协议,还有些协议复杂到无法在一台独立的状态机上实现。Netty提供了一组构建在其核心模块之上的codec实现,是一种可扩展、可重用、可单元测试,并且是多层的codec框架,为用户提供容易维护的codec代码。
Netty还提供对SSL/TLS的支持,不同于传统阻塞式的I/O实现,在NIO模式下支持SSL功能不能只是简单地包装一下流数据并进行加密或解密工作,还需要借助于javax.net.ssl.SSLEngine。SSLEngine是一个有状态的实现,使用SSLEngine必须管理所有可能的状态,例如密码套件、密钥协商(或重新协商)、证书交换以及认证等,而且SSLEngine不是一个绝对的线程安全实现。在Netty内部,SslHandler封装了所有艰难的细节,以及使用SSLEngine可能带来的陷阱。用户只需要配置并将该SslHandler插入你的ChannelPipeline中即可,而且Netty允许实现像StartTlS那样的高级特性。
HTTP是互联网上最受欢迎的协议,与现有的HTTP实现相比,Netty的HTTP实现是相当与众不同的。在HTTP消息的低层交互过程中用户拥有绝对的控制力,因为Netty的HTTP实现只是一些HTTP Codec和HTTP消息类的简单组合,不存在任何限制,例如那种被迫选择的线程模型。用户可以根据自己的需求编写那种可以完全按照你期望的工作方式工作的客户端或服务器端代码,比如线程模型、连接生命期、快编码等。基于这种高度可定制化的特性,用户可以开发一个非常高效的HTTP服务器,例如要求持久化链接以及服务器端推送技术的聊天服务,需要保持链接直至整个文件下载完成的媒体流服务,需要上传大文件并且没有内存压力的文件服务,支持大规模混合客户端应用用于连接以万计的第三方异步web服务等。
Netty的WebSockets实现,WebSockets允许双向,全双工通信信道。在TCP socket中,它被设计为允许一个Web浏览器和Web服务器之间通过数据流交互。WebSocket协议已经被IETF列为RFC 6455规范,并且Netty实现了RFC 6455和一些老版本的规范。
此外Netty还支持Google Protocol Buffer,Google Protocol Buffers是快速实现一个高效的二进制协议的理想方案。通过使用ProtobufEncoder和ProtobufDecoder,我们可以把Google Protocol Buffers编译器(protoc)生成的消息类放入Netty的codec实现中。