在JAVA领域的网络通信中,我们会想到应用层的HTTP协议和传输层的TCP协议。 基于HTTP会更简单,因为它解决了一些基于TCP需要解决的问题,比如解包、粘包等。 但其性能较差:半双工、文本传输、重数据包头等,所以我们追求高效的网络通信。 通常选择基于TCP的通信实现。 对于TCP通信,我们可以选择BIO阻塞实现或者NIO非阻塞实现(当然还有AIO)。 对于NIO,我们可以选择JDK自带的API或者NIO框架。 对于NIO框架,我们可以选择Netty、MINA或者(基于JDK API实现)。 这里我们介绍一下Netty。 在此之前,我们先介绍一下相关的基础知识。
基础知识
输入/输出模型
Unix 提供了五种 I/O 模型:阻塞 I/O、非阻塞 I/O、I/O 复用、信号驱动 I/O 和异步 I/O。 JDK没有五个相应的实现。 JDK 1.4 之前的版本是阻塞 I/O。 1.4 引入了NIO包,并提供了将阻塞复用为同一个阻塞,从而可以在单个线程中同时处理多个客户端请求。 1.7引入的NIO2.0提供了更完整的AIO功能。 当然,JDK提供的这些功能都依赖于底层操作系统的实现。
零拷贝
当我们进行I/O操作时,无论是文件I/O还是网络I/O,传统的实现都会涉及到内核空间和用户空间之间的数据传输。 为什么存在内核空间和用户空间? 因为操作系统为了保证自身的安全稳定,做了这样的划分,普通进程无法直接使用内核空间,底层操作依赖于操作系统,所以就有了这种数据复制。 例如一个典型的数据流向(读取硬盘数据并通过网络发送):
如果我们的应用程序不需要对数据进行操作,那么步骤2和步骤3显然需要优化,所以就有了零拷贝技术。 零复制是一种防止 CPU 将数据从一个存储复制到另一个存储的技术。 具体实现有mmap()、()等。
Netty简介
内蒂
Lee写的一个nio框架(mina也是他的作品,但Netty是后来的)。 方便我们快速开发高性能、高可靠的网络服务器和客户端程序。
Netty并不是那样基于JDK NIO实现的。 它重写了大部分底层实现,如and等,同时也依赖于JDK NIO。 例如,在分配直接内存时,它会调用JDK API。
Netty的API简单,开发门槛低,不像JDK NIO那么复杂。 强大的功能预设了多种编解码功能(多种)。 高性能,成熟稳定(解决了jdk的epoll bug)。 社区活跃,大规模商业应用测试。
Netty核心模块
JDK的结构如左图所示。 由于没有单独的指针来标识读写数据,因此在调用read()或write()方法之前需要调用flip()和clear()方法来在读写之间切换。 如果忘记,将会调用错误的数据。 Netty的结构(右图)进行了重新设计。 有分别用于读取和写入的位置指针。 可以直接调用读写方法,无需进行相关的复位操作。
具体实现是根据空间类型进行分类。 堆分配在普通堆空间中,但分配在直接内存中(基于mmap,通过API级别打开),避免了内核空间和用户空间数据的无效拷贝。 直接内存是一个特殊的空间。 它的大小不受Xmx的限制,也不会被GC回收。
它是Netty网络操作的抽象,聚合了一组功能,包括但不限于网络读写、客户端发起连接、主动关闭连接等。 实现:el(tcp传输服务器)、(tcp传输)、(udp传输)
和
与机制和过滤器类似,这类拦截器实际上是责任链模型的变种,主要是为了方便事件的拦截和用户业务逻辑的定制。 这是我们的业务逻辑实现的地方。 他们的关系如下:
Netty 附带了一些预设供开发人员使用。 例如r、der等用于处理拆包和粘贴。 序列化和反序列化/等等。 还有一些方法可以实现长连接。
/
从类继承关系来看,它们都与线程池相关。 根据用户启动参数,可同时支持单线程模型、多线程、主从等多种模型。 单线程模型意味着所有I/O操作都在同一个NIO线程上完成。 多线程和单线程最大的区别就是有一组NIO线程来处理I/O操作(一个单独的NIO线程监听服务器监听客户端的tcp链接请求,一个NIO线程池是负责阅读和写作)。 主从使用线程池来处理与客户端的连接。 如果链路上有大量的业务逻辑处理,比如登录、握手、安全认证等,显然一个线程无法处理。
总结与应用
Netty主要负责底层通信,其功能远不止上面介绍的这些。 在Java引入nio或aio之前,底层高性能通信主要由c或c++主导,但现在我们有多种选择。
由于Netty是用于网络通信的,所以涉及到通信的地方可以考虑使用Netty,比如Web服务器、消息中间件、RPC框架等。 京东的RPC框架JSF、阿里巴巴的消息中间件均采用Netty进行底层通信(RPC是主动调用模式,消息中间件是推送模式),还有美团点评的实时监控系统CAT等,由此可见netty在底层是高效的。 沟通仍然需要时间的考验。 这里介绍一下它在RPC框架实现中的使用。
RPC(Call)远程方法调用并不是什么新技术,它只是应用框架的一种解决方案。 它是一个相对纯粹的进程间通信解决方案。 比如京东的SOAP或者REST就是RPC,京东的jsf、阿里巴巴的dubbo、腾讯的tars、grpc也是RPC的实现。 、grpc和tars可以跨语言,所以需要IDL来规范需要传输的对象。 不要太拘泥于概念,只需了解其本质即可。 除了使用一些开源的RPC框架之外,我们如何自己实现RPC呢? 这里介绍一下使用Netty实现的RPC。 黄勇(:/.git)和李业兵(:/ares-.git)都是基于Netty实现的RPC框架,都是供我们学习的demo。 虽然它与生产平台的 RPC 有所不同,但 很小,并且具有所有必需的功能。
框架结构如下:
底层通信是使用Netty实现的,流程类似。
启动服务器并注册服务
客户端获取服务提供的信息并组装请求参数
序列化请求参数并请求服务器等待响应结果。
服务器获取结果并反序列化
反射调用服务实现
封装结果并将响应序列化给客户端
客户端反序列化获取结果