浅谈对Netty的认识

记录Netty的一些面试问题

        • ①谈谈对Netty的认识?
        • ②Netty有什么优点?
        • ③Netty的执行原理
        • ④BossEventLoopGroup 与WorkerEventLoopGroup的区别?
          • ⑤说说Java Netty Reactor三种线程模型?
        • 谈谈Netty的心跳机制?
          • ①首先,为什么需要心跳机制?
          • ②Netty是如何实现心跳机制的?
        • 与TCP协议的心跳机制区别?

①谈谈对Netty的认识?

Netty是建立在NIO的基础上,提供了更高层次抽象,对NIO进行封装的网络通讯框架,它类似于Spring框架一样可以方便开发人员在两台不同的系统上传输信息。Netty通过配置可以实现单线程Reactor,多线程Reactor以及主从Reactor的三种线程模型。

②Netty有什么优点?
  1. netty有灵活的配置,如可以使用netty自带的编解码方式来也可以自己去编写Encoder和Decoder类解决粘包问题。
  2. Netty使用transferTo方法实现零拷贝,减少了上下文切换与多余的数据拷贝,提高效率。
③Netty的执行原理

以服务端为例:

  1. 创建ServerBootStrap实例
  2. 通过group方法设置并绑定Reactor线程池组: EventLoopGroup,EventLoop就是处理所有注册到本线程的epoll上的channel。 这里可以分为主从线程组,主线程组只需要处理channel中的连接请求,从Reactor处理channel中的读写请求。
  3. 通过channel方法设置channel是异步还是同步的
  4. 通过一些option方法定义TCP连接的一些配置,如长连接是否开启,是否立即发送数据(关闭Nagle算法)
  5. 配置每一个channel的channelPipeline责任链,网络事件以流的方式在责任链中流转,在这里可以配置一些编解码方式以及为channel中的网络请求添加handler处理器。
  6. 将ServerBootStrap实例与端口绑定,并开始监听。
④BossEventLoopGroup 与WorkerEventLoopGroup的区别?

Boss Group里面的NioEventLoop会调用epoll_wait()方法轮询accept事件,遇到有新的连接,就生成NioSocketChannel,并把这个Channel注册到Worker Group的Selector上。Worker Group轮询read和write事件,在可读或者可写条件满足时,就进行处理。

⑤说说Java Netty Reactor三种线程模型?

1.1. 单线程模型

Reactor单线程模型,指的是所有的IO操作都在同一个NIO线程上面完成,NIO线程的职责如下:

1)作为NIO服务端,接收客户端的TCP连接; 2)作为NIO客户端,向服务端发起TCP连接; 3)读取通信对端的请求或者应答消息; 4)向通信对端发送消息请求或者应答消息。

Reactor单线程模型示意图如下所示:
浅谈对Netty的认识_第1张图片

Reactor单线程模型

由于Reactor模式使用的是异步非阻塞IO,所有的IO操作都不会导致阻塞,理论上一个线程可以独立处理所有IO相关的操作。从架构层面看,一个NIO线程确实可以完成其承担的职责。例如,通过Acceptor类接收客户端的TCP连接请求消息,链路建立成功之后,通过Dispatch将对应的ByteBuffer派发到指定的Handler上进行消息解码。用户线程可以通过消息编码通过NIO线程将消息发送给客户端。

对于一些小容量应用场景,可以使用单线程模型。但是对于高负载、大并发的应用场景却不合适,主要原因如下: 1)一个NIO线程同时处理成百上千的链路,性能上无法支撑,即便NIO线程的CPU负荷达到100%,也无法满足海量消息的编码、解码、读取和发送; 2)当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了NIO线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈; 3)可靠性问题:一旦NIO线程意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。

为了解决这些问题,演进出了Reactor多线程模型,如下。

1.2. 多线程模型

Rector多线程模型与单线程模型最大的区别就是有一组NIO线程处理IO操作,它的原理图如下:

浅谈对Netty的认识_第2张图片
Rector多线程模型

Reactor多线程模型的特点:

1)有专门一个NIO线程-Acceptor线程用于监听服务端,接收客户端的TCP连接请求; 2)网络IO操作-读、写等由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送; 3)1个NIO线程可以同时处理N条链路,但是1个链路只对应1个NIO线程,防止发生并发操作问题。

在绝大多数场景下,Reactor多线程模型都可以满足性能需求;但是,在极个别特殊场景中,一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题。例如并发百万客户端连接,或者服务端需要对客户端握手进行安全认证,但是认证本身非常损耗性能。在这类场景下,单独一个Acceptor线程可能会存在性能不足问题,为了解决性能问题,产生了第三种Reactor线程模型-主从Reactor多线程模型。

1.3. 主从多线程模型

主从Reactor线程模型的特点是:服务端用于接收客户端连接的不再是个1个单独的NIO线程,而是一个独立的NIO线程池。Acceptor接收到客户端TCP连接请求处理完成后(可能包含接入认证等),将新创建的SocketChannel注册到IO线程池(sub reactor线程池)的某个IO线程上,由它负责SocketChannel的读写和编解码工作。Acceptor线程池仅仅只用于客户端的登陆、握手和安全认证,一旦链路建立成功,就将链路注册到后端subReactor线程池的IO线程上,由IO线程负责后续的IO操作。

主从多线程模型如下图所示:

浅谈对Netty的认识_第3张图片

主从多线程模型

利用主从NIO线程模型,可以解决1个服务端监听线程无法有效处理所有客户端连接的性能不足问题。

它的工作流程总结如下:

1)从主线程池中随机选择一个Reactor线程作为Acceptor线程,用于绑定监听端口,接收客户端连接;Acceptor线程接收客户端连接请求之后创建新的SocketChannel,将其注册到主线程池的其它Reactor线程上,由其负责接入认证、IP黑白名单过滤、握手等操作;

2)步骤2完成之后,业务层的链路正式建立,将SocketChannel从主线程池的Reactor线程的多路复用器上摘除,重新注册到Sub线程池的线程上,用于处理I/O的读写操作。

谈谈Netty的心跳机制?
①首先,为什么需要心跳机制?

1.防止无效连接占用服务端资源。当客户端出现宕机,停电等意外情况时,服务端能够通过心跳机制及时发现。

②Netty是如何实现心跳机制的?

在 Netty 中, 实现心跳机制的关键是 IdleStateHandler, 服务端与客户端在pipeline都要添加上IdleStateHandler处理器。客户端通过IdleStateHandler的构造方法设置在规定时间内没有写数据到channel中,即写超时,则写入一个写事件,通过useEventTrigger方法发送心跳包到服务端。服务端同理,当出现读超时时,即长时间没有接收到心跳包,断开连接,防止接口占用。

与TCP协议的心跳机制区别?

虽然在 TCP 协议层面上, 提供了 keepalive 保活机制, 但是使用它有几个缺点:

  1. 它不是 TCP 的标准协议, 并且是默认关闭的.
  2. TCP keepalive 机制依赖于操作系统的实现, 默认的 keepalive 心跳时间是 两个小时, 并且对 keepalive 的修改需要系统调用(或者修改系统配置), 灵活性不够.
  3. TCP keepalive 与 TCP 协议绑定, 因此如果需要更换为 UDP 协议时, keepalive 机制就失效了.

虽然使用 TCP 层面的 keepalive 机制比自定义的应用层心跳机制节省流量, 但是基于上面的几点缺点, 一般的实践中, 人们大多数都是选择在应用层上实现自定义的心跳。

你可能感兴趣的:(Java,java,面试,开发语言)