2.1 源码介绍
源码专题,自然我们需要下载自己去好好看一下才行。
访问GitHub不慢的也可以自行访问下载https://github.com/netty/netty.git
这里我提供一下我百度网盘里的Netty-4.1的源码。
链接:https://pan.baidu.com/s/19oO_IA_cRBA1EgkeBXVWMQ
提取码:6348
源码整体介绍
2.1.1 Core 核心层模块
netty-common模块是 Netty 的核心基础包,提供了丰富的工具类,其他模块都需要依赖它。在 common 模块中,常用的包括通用工具类和自定义并发包。
- 通用工具类:比如定时器工具 TimerTask、时间轮 HashedWheelTimer 等。
- 自定义并发包:比如异步模型Future & Promise、相比 JDK 增强的 FastThreadLocal 等。
在netty-buffer 模块中Netty自己实现了的一个更加完备的ByteBuf 工具类,用于网络通信中的数据载体。由于人性化的 Buffer API 设计,它已经成为 Java ByteBuffer 的完美替代品。ByteBuf 的动态性设计不仅解决了 ByteBuffer 长度固定造成的内存浪费问题,而且更安全地更改了 Buffer 的容量。此外 Netty 针对 ByteBuf 做了很多优化,例如缓存池化、减少数据拷贝的 CompositeByteBuf 等。
netty-resover模块主要提供了一些有关基础设施的解析工具,包括 IP Address、Hostname、DNS 等。
2.1.2 Protocol Support 协议支持层模块
netty-codec模块主要负责编解码工作,通过编解码实现原始字节数据与业务实体对象之间的相互转化。如下图所示,Netty 支持了大多数业界主流协议的编解码器,如 HTTP、HTTP2、Redis、XML 等,为开发者节省了大量的精力。此外该模块提供了抽象的编解码类 ByteToMessageDecoder 和 MessageToByteEncoder,通过继承这两个类我们可以轻松实现自定义的编解码逻辑。
netty-handler模块主要负责数据处理工作。Netty 中关于数据处理的部分,本质上是一串有序 handler 的集合。netty-handler 模块提供了开箱即用的 ChannelHandler 实现类,例如日志、IP 过滤、流量整形等,如果你需要这些功能,仅需在 pipeline 中加入相应的 ChannelHandler 即可。
2.1.3 Transport Service 传输服务层模块
netty-transport 模块可以说是 Netty 提供数据处理和传输的核心模块。该模块提供了很多非常重要的接口,如 Bootstrap、Channel、ChannelHandler、EventLoop、EventLoopGroup、ChannelPipeline 等。其中 Bootstrap 负责客户端或服务端的启动工作,包括创建、初始化 Channel 等;EventLoop 负责向注册的 Channel 发起 I/O 读写操作;ChannelPipeline 负责 ChannelHandler 的有序编排,这些组件在介绍 Netty 逻辑架构的时候都有所涉及。
2.2 Netty的Reactor模型
Reactor三种模式用生活场景对比
Netty的I/O线程NioEventLoop由于聚合了多路复用器Selector,可以同时并发处理成百上千个客户端SocketChannel。由于读写操作都是非阻塞的,这就可以充分提升I/O线程的运行效率,避免由频繁的I/O阻塞导致的线程挂起。另外,由于Netty采用了异步通信模式,一个I/O线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞I/O一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
2.1.1 Reactor单线程模型
Reactor单线程模型,指的是所有的I/O操作都在同一个NIO线程上面完成,NIO线程的职责如下:
- 作为NIO服务端,接收客户端的TCP连接;
- 作为NIO客户端,向服务端发起TCP连接;
- 读取通信对端的请求或者应答消息;
- 向通信对端发送消息请求或者应答消息;
Reactor线程是个多面手,负责多路分离套接字,Accept新连接,并分派请求到处理器链中。该模型 适用于处理器链中业务处理组件能快速完成的场景。不过,这种单线程模型不能充分利用多核资源,所以实际使用的不多。
Netty中的Reactor单线程模型实现,每一个连接一个线程
对于一些小容量应用场景,可以使用单线程模型,但是对于高负载、大并发的应用却不合适,主要原因如下:
- 一个NIO线程同时处理成百上千的链路,性能上无法支撑。即便NIO线程的CPU负荷达到100%,也无法满足海量消息的编码、解码、读取和发送;
- 当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往进行重发,这更加重了NIO线程的负载,最终导致大量消息积压和处理超时,NIO线程会成为系统的性能瓶颈;
- 可靠性问题。一旦NIO线程意外跑飞,或者进入死循环,会导致整个系统通讯模块不可用,不能接收和处理外部信息,造成节点故障。
为了解决这些问题,演进出了Reactor多线程模型,下面我们一起学习下Reactor多线程模型。
2.1.2 Reactor多线程模型
Reactor多线程模型与单线程模型最大区别就是有一组NIO线程处理I/O操作,它的特点如下:
- 有一个专门的NIO线程–acceptor新城用于监听服务端,接收客户端的TCP连接请求;
- 网络I/O操作–读、写等由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送;
- 1个NIO线程可以同时处理N条链路,但是1个链路只对应1个NIO线程,防止发生并发操作问题。
在绝大多数场景下,Reactor多线程模型都可以满足性能需求;但是,在极特殊应用场景中,一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题。例如百万客户端并发连接,或者服务端需要对客户端的握手信息进行安全认证,认证本身非常损耗性能。这类场景下,单独一个Acceptor线程可能会存在性能不足问题,为了解决性能问题,产生了第三种Reactor线程模型–主从Reactor多线程模型。
2.1.3 主从Reactor多线程模型
特点是:服务端用于接收客户端连接的不再是1个单独的NIO线程,而是一个独立的NIO线程池。Acceptor接收到客户端TCP连接请求处理完成后(可能包含接入认证等),将新创建的SocketChannel注册到I/O线程池(sub reactor线程池)的某个I/O线程上,由它负责SocketChannel的读写和编解码工作。
Acceptor线程池只用于客户端的登录、握手和安全认证,一旦链路建立成功,就将链路注册到后端subReactor线程池的I/O线程上,有I/O线程负责后续的I/O操作。
第三种模型比起第二种模型,是将Reactor分成两部分,mainReactor负责监听server socket,accept新连接,并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket,读写网 络数据,对业务处理功能,其扔给worker线程池完成。通常,subReactor个数上可与CPU个数等同。
public class StompWebSocketChatServer { static final int PORT = Integer.parseInt(System.getProperty("port", "8080")); public void start(final int port) throws Exception { NioEventLoopGroup boosGroup = new NioEventLoopGroup(1); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap() .group(boosGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new StompWebSocketChatServerInitializer("/chat")); bootstrap.bind(port).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { if (future.isSuccess()) { System.out.println("Open your web browser and navigate to http://127.0.0.1:" + PORT + '/'); } else { System.out.println("Cannot start server, follows exception " + future.cause()); } } }).channel().closeFuture().sync(); } finally { boosGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { new StompWebSocketChatServer().start(PORT); } }
这就是源码,不会找的直接按住Ctrl + Shift + R 然后输入StompWebSocketChatServer 全局搜索 ,就可以看到源码。
可以看到Netty仅仅支持NIO了,对于BIO和AIO都是采用Deprecated()表示此方法已废弃、暂时可用,但以后此类或方法都不会再更新、后期可能会删除,建议后来人不要调用此方法。
当然在连接数少、并发度低的情况下,BIO性能也不一定比NIO差