营销型公司网站,轻松学做网站,网站要怎么样做排名才上得去,wordpress 登陆失败作为一个高性能的 NIO 通信框架#xff0c;Netty 被广泛应用于大数据处理、互联网消息中间件、游戏和金融行业等。大多数应用场景对底层的通信框架都有很高的性能要求#xff0c;作为综合性能最高的 NIO 框架 之一#xff0c;Netty 可以完全满足不同领域对高性能通信的需求。…作为一个高性能的 NIO 通信框架Netty 被广泛应用于大数据处理、互联网消息中间件、游戏和金融行业等。大多数应用场景对底层的通信框架都有很高的性能要求作为综合性能最高的 NIO 框架 之一Netty 可以完全满足不同领域对高性能通信的需求。本文我们将从架构层对 Netty 的高性能设计和关键代码实现进行剖析看 Netty 是如何支撑高性能网络通信的。RPC 调用性能模型分析传统 RPC 调用性能差的原因1.网络传输方式问题传统的 RPC 框架或者基于 RMI 等方式的远程过程调用采用了同步阻塞 I/O当客户端的并发压力或者网络时延增大之后同步阻塞 I/O 会由于频繁的 wait 导致 I/O 线程经常性的阻塞由于线程无法高效的工作I/O 处理能力自然下降。采用 BIO 通信模型的服务端通常由一个独立的 Acceptor 线程负责监听客户端的连接接收到客户端连接之后为其创建一个新的线程处理请求消息处理完成之后返回应答消息给客户端线程销毁这就是典型的 “ 一请求一应答 ” 模型。该架构最大的问题就是不具备弹性伸缩能力当并发访问量增加后服务端的线程个数和并发访问数成线性正比由于线程是 Java 虛拟机 非常宝贵的系统资源当线程数膨胀之后系统的性能急剧下降随着并发量的继续增加可能会发生句柄溢出、线程堆栈溢出等问题并导致服务器最终宕机。2.序列化性能差Java 序列化存在如下几个典型问题1.Java 序列化机制是 Java 内部的一 种对象编解码技术无法跨语言使用。例如对于异构系统之间的对接Java 序列化后的码流需要能够通过其他语言反序列化成原始对象这很难支持。2.相比于其他开源的序列化框架Java 序列化后的码流太大无论是网络传输还是持久化到磁盘都会导致额外的资源占用。3.序列化性能差资源占用率高 ( 主要是 CPU 资源占用高 )。3.线程模型问题由于采用同步阻塞 I/O这会导致每个 TCP 连接 都占用 1 个线程由于线程资源是 JVM 虚拟机 非常宝贵的资源当 I/O 读写阻塞导致线程无法及时释放时会导致系统性能急剧下降严重的甚至会导致虚拟机无法创建新的线程。http://4.IO 通信性能三原则尽管影响 I/O 通信性能的因素非常多但是从架构层面看主要有三个要素。1.传输用什么样的通道将数据发送给对方。可以选择 BIO、NIO 或者 AIOI/O 模型 在很大程度上决定了通信的性能2.协议采用什么样的通信协议HTTP 等公有协议或者内部私有协议。协议的选择不同性能也不同。相比于公有协议内部私有协议的性能通常可以被设计得更优3.线程模型数据报如何读取读取之后的编解码在哪个线程进行编解码后的消息如何派发Reactor 线程模型的不同对性能的影响也非常大。5.异步非阻塞通信在 I/O 编程过程中当需要同时处理多个客户端接入请求时可以利用多线程或者 I/O 多路复用技术进行处理。I/O 多路复用技术通过把多个 I/O 的阻塞复用到同一个 select 的阻塞上从而使得系统在单线程的情况下可以同时处理多个客户端请求。 与传统的多线程 / 多进程模型比I/O 多路复用的最大优势是系统开销小系统不需要创建新的额外进程或者线程也不需要维护这些进程和线程的运行降低了系统的维护工作量节省了系统资源。JDK1.4 提供了对非阻塞 I/O 的支持JDK1.5 使用 epoll 替代了传统的 select / poll极大地提升了 NIO 通信 的性能。与 Socket 和 ServerSocket 类相对应NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。 阻塞模式使用非常简单但是性能和可靠性都不好非阻塞模式则正好相反。开发人员一般可以根据自己的需要来选择合适的模式一般来说低负载、低并发的应用程序可以选择同步阻塞 I/O 以降低编程复杂度。但是对于高负载、高并发的网络应用需要使用 NIO 的非阻塞模式进行开发。Netty 的 I/O 线程 NioEventLoop 由于聚合了多路复用器 Selector可以同时并发处理成百上千个客户端 SocketChannel。由于读写操作都是非阻塞的这就可以充分提升 I/O 线程 的运行效率避免由频繁的 I/O 阻塞 导致的线程挂起。另外由于 Netty 采用了异步通信模式一个 I/O 线程 可以并发处理 N 个客户端连接和读写操作这从根本上解决了传统 同步阻塞 I/O “ 一连接一线程 ” 模型架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。高效的 Reactor 线程模型常用的 Reactor 线程模型有三种分别如下1.Reactor 单线程模型2.Reactor 多线程模型3.主从 Reactor 多线程模型。Reactor 单线程模型指的是所有的 I/O 操作都在同一个 NIO 线程上面完成NIO 线程的职责如下1.作为 NIO 服务端接收客户端的 TCP 连接2.作为 NIO 客户端向服务端发起 TCP 连接3.读取通信对端的请求或者应答消息4.向通信对端发送消息请求或者应答消息。由于 Reactor 模式使用的是异步非阻塞 I/O所有的 I/O 操作 都不会导致阻塞理论上一个线程可以独立处理所有 I/O 相关的操作。从架构层面看一个 NIO 线程确实可以完成其承担的职责。例如通过 Acceptor 接收客户端的 TCP 连接请求消息链路建立成功之后通过 Dispatch 将对应的 ByteBuffer 派发到指定的 Handler 上进行消息解码。用户 Handler 可以通过 NIO 线程 将消息发送给客户端。对于一些小容量应用场景可以使用单线程模型但是对于高负载、大并发的应用却不合适主要原因如下。1.一个 NIO 线程 同时处理成百上千的链路性能上无法支撑。 即便 NIO 线程 的 CPU 负荷 达到 100%也无法满足海量消息的编码解码、读取和发送2.当 NIO 线程 负载过重之后处理速度将变慢这会导致大量客户端连接超时超时之后往往会进行重发这更加重了 NIO 线程 的负载最终会导致大量消息积压和处理超时NIO 线程会成为系统的性能瓶颈3.可靠性问题。一旦 NIO 线程意外跑飞或者进入死循环会导致整个系统通信模块不可用不能接收和处理外部消息造成节点故障。为了解决这些问题演进出了 Reactor 多线程模型下面我们看一下 Reactor 多线程模型。Rector 多线程模型与单线程模型最大的区别就是有一组 NIO 线程 处理 I/O 操作它的特点如下。1.有一个专门的 NIO 线程 —— Acceptor 线程 用于监听服务端口接收客户端的 TCP 连接请求2.网络 IO 操作 —— 读、写等由一个 NIO 线程池 负责线程池可以采用标准的 JDK 线程池 实现它包含一个任务队列和 N 个可用的线程由这些 NIO 线程 负责消息的读取、解码、编码和发送3.1 个 NIO 线程 可以同时处理 N 条链路但是 1 个链路只对应 1 个 NIO 线程以防止发生并发操作问题。在绝大多数场景下Reactor 多线程模型 都可以满足性能需求但是在极特殊应用场景中一个 NIO 线程负责监听和处理所有的客户端连接可能会存在性能问题。例如百万客户端并发连接或者服务端需要对客户端的握手消息进行安全认证认证本身非常损耗性能。在这类场景下单独一个 Acceptor 线程 可能会存在性能不足问题为了解决性能问题产生了第三种 Reactor 线程模型 —— 主从 Reactor 多线程模型。主从 Reactor 线程模型的特点是服务端用于接收客户端连接的不再是个单线程的连接处理 Acceptor而是一个独立的 Acceptor 线程池。Acceptor 接收到客户端 TCP 连接请求 处理完成后 ( 可能包含接入认证等 )将新创建的 SocketChannel 注册到 I/O 处理线程池 的某个 I/O 线程 上由它负责 SocketChannel 的读写和编解码工作。Acceptor 线程池 只用于客户端的登录、握手和安全认证一旦链路建立成功就将链路注册到 I/O 处理线程池的 I/O 线程 上每个 I/O 线程 可以同时监听 N 个链路对链路产生的 IO 事件 进行相应的 消息读取、解码、编码及消息发送等操作。利用主从 Reactor 线程模型可以解决 1 个 Acceptor 线程 无法有效处理所有客户端连接的性能问题。因此Netty 官方也推荐使用该线程模型。事实上Netty 的线程模型并非固定不变通过在启动辅助类中创建不同的 EventLoopGroup 实例 并进行适当的参数配置就可以支持上述三种 Reactor 线程模型。可以根据业务场景的性能诉求选择不同的线程模型。Netty 单线程模型服务端代码示例如下EventLoopGroup reactor new NioEventLoopGroup(1);ServerBootstrap bootstrap new ServerBootstrap(); bootstrap.group(reactor, reactor) .channel(NioServerSocketChannel.class) ......Netty 多线程模型代码示例如下EventLoopGroup acceptor new NioEventLoopGroup(1);
EventLoopGroup ioGroup new NioEventLoopGroup();
ServerBootstrap bootstrap new ServerBootstrap();
bootstrap.group(acceptor, ioGroup)
.channel(NioServerSocketChannel.class) ......Netty 主从多线程模型代码示例如下EventLoopGroup acceptorGroup new NioEventLoopGroup(); EventLoopGroup ioGroup new NioEventLoopGroup(); ServerBootstrap bootstrap new ServerBootstrap(); bootstrap.group(acceptorGroup, ioGroup) .channel(NioServerSocketChannel.class) ......无锁化的串行设计在大多数场景下并行多线程处理可以提升系统的并发性能。但是如果对于共享资源的并发访问处理不当会带来严重的锁竞争这最终会导致性能的下降。为了尽可能地避免锁竞争带来的性能损耗可以通过串行化设计即消息的处理尽可能在同一个线程内完成期间不进行线程切换这样就避免了多线程竞争和同步锁。为了尽可能提升性能Netty 对消息的处理采用了串行无锁化设计在 I/O 线程 内部进行串行操作避免多线程竞争导致的性能下降。Netty 的串行化设计工作原理图如下图所示。Netty 的 NioEventLoop 读取到消息之后直接调用 ChannelPipeline 的 fireChannelRead(Object msg)只要用户不主动切换线程一直会由 NioEventLoop 调用到 用户的 Handler期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争从性能角度看是最优的。零拷贝Netty 的“ 零拷贝 ”主要体现在如下三个方面。第一种情况。Netty 的接收和发送 ByteBuffer 采用堆外直接内存 (DIRECT BUFFERS) 进行 Socket 读写不需要进行字节缓冲区的二次拷贝。如果使用传统的 堆内存(HEAP BUFFERS) 进行 Socket 读写JVM 会将 堆内存 Buffer 拷贝一份到 直接内存 中然后才写入 Socket。相比于堆外直接内存消息在发送过程中多了一次缓冲区的内存拷贝。下面我们继续看第二种“ 零拷贝 ” 的实现 CompositeByteBuf它对外将多个 ByteBuf 封装成一个 ByteBuf对外提供统一封装后的 ByteBuf 接口。CompositeByteBuf 实际就是个 ByteBuf 的装饰器它将多个 ByteBuf 组合成一个集合然后对外提供统一的 ByteBuf 接口添加 ByteBuf不需要做内存拷贝。第三种 “ 零拷贝 ” 就是文件传输Netty 文件传输类 DefaultFileRegion 通过 transferTo() 方法 将文件发送到目标 Channel 中。很多操作系统直接将文件缓冲区的内容发送到目标 Channel 中而不需要通过循环拷贝的方式这是一种更加高效的传输方式提升了传输性能降低了 CPU 和内存占用实现了文件传输的 “ 零拷贝 ” 。内存池随着 JVM 虚拟机 和 JIT 即时编译技术 的发展对象的分配和回收是个非常轻量级的工作。但是对于缓冲区 Buffer情况却稍有不同特别是对于堆外直接内存的分配和回收是一件耗时的操作。为了尽量重用缓冲区Netty 提供了基于内存池的缓冲区重用机制。 ByteBuf 的子类中提供了多种 PooledByteBuf 的实现基于这些实现 Netty 提供了多种内存管理策略通过在启动辅助类中配置相关参数可以实现差异化的定制。原文地址https://www.toutiao.com/i6860457783901291019/