完全理解分布式Netty,这一篇文章就够了!

1、Netty究竟是什么

一、从HTTP提及java

有了Netty,你能够实现本身的HTTP服务器、FTP服务器、UDP服务器、RPC服务器、WebSocket服务器、Redis的Proxy服务器、MySQL的Proxy服务器等等。面试

咱们回顾一下传统的HTTP服务器的原理:spring

  1. 建立一个ServerSocket,监听并绑定一个端口
  2. 一系列客户端来请求这个端口
  3. 服务器使用Accept,得到一个来自客户端的Socket链接对象
  4. 启动一个新线程处理链接
  • 读Socket,获得字节流
  • 解码协议,获得Http请求对象
  • 处理Http请求,获得一个结果,封装成一个HttpResponse对象
  • 编码协议,将结果序列化字节流 写Socket,将字节流发给客户端

最后,继续循环步骤3后端

HTTP服务器之因此称为HTTP服务器,是由于编码解码协议是HTTP协议,若是协议是Redis协议,那它就成了Redis服务器,若是协议是WebSocket,那它就成了WebSocket服务器,等等。 使用Netty你就能够定制编解码协议,实现本身的特定协议的服务器。api

二、NIO数组

上面是一个传统处理http的服务器,可是在高并发的环境下,线程数量会比较多,System load也会比较高,因而就有了NIO。缓存

它并非Java独有的概念,NIO表明一个叫IO多路复用的词汇,它是由操做系统提供的系统调用,早期这个操做系统调用的名字是select,可是性能低下,后来渐渐演化成了Linux下的epoll和Mac里的kqueue。咱们通常就说是epoll,由于没有人拿苹果电脑做为服务器使用对外提供服务,而Netty就是基于Java NIO技术封装的一套框架。为何要封装,由于原生的Java NIO使用起来没那么方便,并且还有臭名昭著的bug,Netty把它封装以后,提供了一个易于操做的使用模式和接口,用户使用起来也就便捷多了。安全

说NIO以前先说一下BIO(Blocking IO),如何理解这个Blocking呢?服务器

  • 客户端监听(Listen)时,Accept是阻塞的,只有新链接来了,Accept才会返回,主线程才能继;
  • 读写socket时,Read是阻塞的,只有请求消息来了,Read才能返回,子线程才能继续处理;
  • 读写socket时,Write是阻塞的,只有客户端把消息收了,Write才能返回,子线程才能继续读取下一个请求;
  • 传统的BIO模式下,从头至尾的全部线程都是阻塞的,这些线程就干等着,占用系统的资源,什么事也不干。

那么NIO是怎么作到非阻塞的呢?它用的是事件机制,用一个线程把Accept,读写操做,请求处理的逻辑全干了。若是什么事都没得作,它也不会死循环,它会将线程休眠起来,直到下一个事件来了再继续干活,这样的一个线程称之为NIO线程。用伪代码表示:微信

clipboard.png

2、Reactor线程模型

一、Reactor单线程模型

一个NIO线程+一个accept线程:

clipboard.png

二、Reactor多线程模型

clipboard.png

三、Reactor主从模型

主从Reactor多线程:多个acceptor的NIO线程池用于接受客户端的链接

clipboard.png

Netty能够基于如上三种模型进行灵活的配置。

四、小结

Netty是创建在NIO基础之上,Netty在NIO之上又提供了更高层次的抽象,在Netty里面,Accept链接可使用单独的线程池去处理,读写操做又是另外的线程池来处理。

Accept链接和读写操做也可使用同一个线程池来进行处理,而请求处理逻辑既可使用单独的线程池进行处理,也能够跟放在读写线程一块处理。线程池中的每个线程都是NIO线程,用户能够根据实际状况进行组装,构造出知足系统需求的高性能并发模型。

3、为何选择Netty

若是不用netty,使用原生JDK的话,有以下问题:

  1. API复杂
  2. 对多线程很熟悉:由于NIO涉及到Reactor模式
  3. 高可用的话:须要出路断连重连、半包读写、失败缓存等问题
  4. JDK NIO的bug

而Netty来讲,他的api简单、性能高并且社区活跃(dubbo、rocketmq等都使用了它)

4、什么是TCP 粘包/拆包

一、现象

先看以下代码,这个代码是使用netty在client端重复写100次数据给server端,ByteBuf是netty的一个字节容器,里面存放是的须要发送的数据:

clipboard.png

从client端读取到的数据为:

clipboard.png

从服务端的控制台输出能够看出,存在三种类型的输出

  1. 一种是正常的字符串输出。
  2. 一种是多个字符串“粘”在了一块儿,咱们定义这种 ByteBuf 为粘包。
  3. 一种是一个字符串被“拆”开,造成一个破碎的包,咱们定义这种 ByteBuf 为半包。

二、透过现象分析缘由

应用层面使用了Netty,可是对于操做系统来讲,只认TCP协议,尽管咱们的应用层是按照 ByteBuf 为单位来发送数据,server按照Bytebuf读取,可是到了底层操做系统仍然是按照字节流发送数据,所以,数据到了服务端,也是按照字节流的方式读入,而后到了 Netty 应用层面,从新拼装成 ByteBuf,而这里的 ByteBuf 与客户端按顺序发送的 ByteBuf 多是不对等的。所以,咱们须要在客户端根据自定义协议来组装咱们应用层的数据包,而后在服务端根据咱们的应用层的协议来组装数据包,这个过程一般在服务端称为拆包,而在客户端称为粘包。

拆包和粘包是相对的,一端粘了包,另一端就须要将粘过的包拆开,发送端将三个数据包粘成两个 TCP 数据包发送到接收端,接收端就须要根据应用协议将两个数据包从新组装成三个数据包。

三、如何解决

在没有Netty的状况下,用户若是本身须要拆包,基本原理就是不断从 TCP 缓冲区中读取数据,每次读取完都须要判断是不是一个完整的数据包 若是当前读取的数据不足以拼接成一个完整的业务数据包,那就保留该数据,继续从 TCP 缓冲区中读取,直到获得一个完整的数据包。若是当前读到的数据加上已经读取的数据足够拼接成一个数据包,那就将已经读取的数据拼接上本次读取的数据,构成一个完整的业务数据包传递到业务逻辑,多余的数据仍然保留,以便和下次读到的数据尝试拼接。

而在Netty中,已经造好了许多类型的拆包器,咱们直接用就好:

clipboard.png

选好拆包器后,在代码中client段和server端将拆包器加入到chanelPipeline之中就行了:

如上实例中:

客户端:

clipboard.png

服务端:

clipboard.png

clipboard.png

5、Netty 的零拷贝

1、传统意义的拷贝

是在发送数据的时候,传统的实现方式是:

  1. File.read(bytes)
  2. Socket.send(bytes)

这种方式须要四次数据拷贝和四次上下文切换:

  1. 数据从磁盘读取到内核的read buffer
  2. 数据从内核缓冲区拷贝到用户缓冲区
  3. 数据从用户缓冲区拷贝到内核的socket buffer
  4. 数据从内核的socket buffer拷贝到网卡接口(硬件)的缓冲区

二、零拷贝的概念

明显上面的第二步和第三步是没有必要的,经过java的FileChannel.transferTo方法,能够避免上面两次多余的拷贝(固然这须要底层操做系统支持)

  1. 调用transferTo,数据从文件由DMA引擎拷贝到内核read buffer
  2. 接着DMA从内核read buffer将数据拷贝到网卡接口buffer

上面的两次操做都不须要CPU参与,因此就达到了零拷贝。

  1. Netty中的零拷贝

主要体如今三个方面:

一、bytebuffer

Netty发送和接收消息主要使用bytebuffer,bytebuffer使用对外内存(DirectMemory)直接进行Socket读写。

缘由:若是使用传统的堆内存进行Socket读写,JVM会将堆内存buffer拷贝一份到直接内存中而后再写入socket,多了一次缓冲区的内存拷贝。DirectMemory中能够直接经过DMA发送到网卡接口

二、Composite Buffers

传统的ByteBuffer,若是须要将两个ByteBuffer中的数据组合到一块儿,咱们须要首先建立一个size=size1+size2大小的新的数组,而后将两个数组中的数据拷贝到新的数组中。可是使用Netty提供的组合ByteBuf,就能够避免这样的操做,由于CompositeByteBuf并无真正将多个Buffer组合起来,而是保存了它们的引用,从而避免了数据的拷贝,实现了零拷贝。

三、对于FileChannel.transferTo的使用

Netty中使用了FileChannel的transferTo方法,该方法依赖于操做系统实现零拷贝。

除了这些,针对当前互联网公司的技术需求以及结合主流技术,我本身整理了一套系统的架构技术体系,当你技术过硬的时候,可以解决技术问题才会服众。很多公司都很重视高并发高可用的技术,特别是一线互联网公司,分布式、JVM、spring源码分析、微服务等知识点已经是面试的必考题,这些东西可能大家平时在工做中接触过,可是缺乏的全面系统的学习,加入后端开发群:943918498,或是关注微信公众号:Java资讯库,回复“架构”,免费领取架构资料。

6、Netty 内部执行流程

一、服务端:

clipboard.png

clipboard.png

  1. 建立ServerBootStrap实例
  2. 设置并绑定Reactor线程池:EventLoopGroup,EventLoop就是处理全部注册到本线程的Selector上面的Channel
  3. 设置并绑定服务端的channel
  4. 建立处理网络事件的ChannelPipeline和handler,网络时间以流的形式在其中流转,handler完成多数的功能定制:好比编解码
    SSl安全认证
  5. 绑定并启动监听端口
  6. 当轮训到准备就绪的channel后,由Reactor线程:NioEventLoop执行pipline中的方法,最终调度并执行channelHandler

二、客户端

clipboard.png

clipboard.png

相关文章
相关标签/搜索