Netty面试题和解答(一)

基础

  1. TCP和UDP的区别?

    1. TCP是面向连接的(在客户端和服务器之间传输数据之前要先建立连接),UDP是无连接的(发送数据之前不需要先建立连接)
    2. TCP提供可靠的服务(通过TCP传输的数据。无差错,不丢失,不重复,且按序到达);UDP提供面向事务的简单的不可靠的传输。
    3. UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性比较高的通讯或广播通信。随着网速的提高,UDP使用越来越多。
    4. 没一条TCP连接只能是点到点的,UDP支持一对一,一对多和多对多的交互通信。
    5. TCP对系统资源要去比较多,UDP对系统资源要求比较少
    6. UDP程序结构更加简单
    7. TCP是流模式,UDP是数据报模式
  2. TCP协议如何保证可靠传输?
    TCP通过序列号、检验和、确认应答信号、重发控制、连接管理、窗口控制、流量控制、拥塞控制实现可靠性。
    参考:https://www.jianshu.com/p/6aac4b2a9fd7

  3. TCP的握手、挥手机制?
    参考:https://blog.csdn.net/zengrenyuan/article/details/80313449

  4. TCP的粘包/拆包原因及其解决方法是什么?
    TCP是基于字节流的,虽然应用层和TCP传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界。另外从TCP的帧结构也可以看出,在TCP的首部没有表示数据长度的字段。常见的发生原因:

    • 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
    • 待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
    • 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
    • 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

    解决问题的关键在于如何给每个数据包添加边界信息,常用的方法如下:

    • 发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
    • 发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
    • 可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
  5. Netty的粘包/拆包是怎么处理的,有哪些实现?

    • 消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分。
    • 包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。
    • 将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段
      参考:https://www.cnblogs.com/sidesky/p/6913109.html
  6. 同步与异步、阻塞与非阻塞的区别?

    • 同步:发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事。

    • 异步:当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。例如 ajax请求(异步): 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

    • 阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回,它还会抢占cpu去执行其他逻辑,也会主动检测io是否准备好。

    • 非阻塞:指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

  7. 说说网络IO模型?
    网络IO的本质是socket的读取,socket在linux系统被抽象为流,IO可以理解为对流的操作。

    • 阻塞IO(bloking IO)
    • 非阻塞IO(non-blocking IO)
    • 多路复用IO(multiplexing IO)
    • 信号驱动式IO(signal-driven IO)
    • 异步IO(asynchronous IO)
      参考:https://www.jianshu.com/p/a95bcb116765
  8. BIO. NIO. AIO分别是什么?

    • BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
    • NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
    • AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
  9. select. poll. epoll的机制及其区别?
    参考:https://www.cnblogs.com/aspirant/p/9166944.html

  10. 说说你对Netty的了解?

  11. Netty跟Java NIO有什么不同,为什么不直接使用JDK NIO类库?

    • NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector,ServerSocketChannel、SocketChannel、ByteBuffer等。
    • 需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。这是因为NIO编程涉及到Reactor模式,你必须对多线程和网络编程非常熟悉,才能写出高质量的NIO程序。
    • 可靠性能力补齐,工作量和难度非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐的工作量和难度都非常大。
    • JDK NIO的BUG,例如epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方验证例子基于以上原因,在大多数场景下,不建议直接使用JDK的NIO类库,除非你精通NIO编程或者有特殊的需求。在绝大多数的业务场景中,我们可以使用NIO框架Netty来进行NIO编程,它既可以作为客户端也可以作为服务端,同时支持UDP和异步文件传输,功能非常强大。
      Netty特性总结如下:
    • API使用简单,开发门槛低
    • 功能强大,预置了多种编解码功能,支持多种主流协议
    • 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展
    • 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优
    • 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼
    • 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时更多的新功能会加入
    • 经历了大规模的商业应用考验,质量得到验证。
      参考:https://www.520mwx.com/view/20451
  12. Netty组件有哪些,分别有什么关联?

    • Channel
      基础的IO操作,如绑定、连接、读写等都依赖于底层网络传输所提供的原语,在Java的网络编程中,基础核心类是Socket,而Netty的Channel提供了一组API,极大地简化了直接与Socket进行操作的复杂性,并且Channel是很多类的父类,如EmbeddedChannel、LocalServerChannel、NioDatagramChannel、NioSctpChannel、NioSocketChannel等。

    • EventLoop
      EventLoop定义了处理在连接过程中发生的事件的核心抽象,之后会进一步讨论,其中Channel、EventLoop、Thread和EventLoopGroup之间的关系如下图所示:

    • ChannelHandler和ChannelPipeline
      从应用开发者看来,ChannelHandler是最重要的组件,其中存放用来处理进站和出站数据的用户逻辑。ChannelHandler的方法被网络事件触发,ChannelHandler可以用于几乎任何类型的操作,如将数据从一种格式转换为另一种格式或处理抛出的异常。例如,其子接口ChannelInboundHandler,接受进站的事件和数据以便被用户定义的逻辑处理,或者当响应所连接的客户端时刷新ChannelInboundHandler的数据。
      ChannelPipeline为ChannelHandler链提供了一个容器并定义了用于沿着链传播入站和出站事件流的API。当创建Channel时,会自动创建一个附属的ChannelPipeline。

    • Bootstrap 和 ServerBootstrap
      Netty的引导类应用程序网络层配置提供容器,其涉及将进程绑定到给定端口或连接一个进程到在指定主机上指定端口上运行的另一进程。引导类分为客户端引导Bootstrap和服务端引导ServerBootstrap。

参考:https://www.cnblogs.com/leesf456/p/6831661.html

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

高级

  1. Netty高性能体现在哪些方面?

    • 传输:IO模型在很大程度上决定了框架的性能,相比于bio,netty建议采用异步通信模式,因为nio一个线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞IO一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。正如代码中所示,使用的是NioEventLoopGroup和NioSocketChannel来提升传输效率。
    • 协议:采用什么样的通信协议,对系统的性能极其重要,netty默认提供了对Google Protobuf的支持,也可以通过扩展Netty的编解码接口,用户可以实现其它的高性能序列化框架。
    • 线程:netty使用了Reactor线程模型,但Reactor模型不同,对性能的影响也非常大,下面介绍常用的Reactor线程模型有三种,分别如下:
      • Reactor单线程模型:单线程模型的线程即作为NIO服务端接收客户端的TCP连接,又作为NIO客户端向服务端发起TCP连接,即读取通信对端的请求或者应答消息,又向通信对端发送消息请求或者应答消息。理论上一个线程可以独立处理所有IO相关的操作,但一个NIO线程同时处理成百上千的链路,性能上无法支撑,即便NIO线程的CPU负荷达到100%,也无法满足海量消息的编码、解码、读取和发送,又因为当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了NIO线程的负载,最终会导致大量消息积压和处理超时,NIO线程会成为系统的性能瓶颈。
      • Reactor多线程模型:有专门一个NIO线程用于监听服务端,接收客户端的TCP连接请求;网络IO操作(读写)由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现。但百万客户端并发连接时,一个nio线程用来监听和接受明显不够,因此有了主从多线程模型。
      • 主从Reactor多线程模型:利用主从NIO线程模型,可以解决1个服务端监听线程无法有效处理所有客户端连接的性能不足问题,即把监听服务端,接收客户端的TCP连接请求分给一个线程池。因此,在代码中可以看到,我们在server端选择的就是这种方式,并且也推荐使用该线程模型。在启动类中创建不同的EventLoopGroup实例并通过适当的参数配置,就可以支持上述三种Reactor线程模型。
  2. Netty的线程模型是怎么样的?
    参考:https://segmentfault.com/a/1190000015484187#articleHeader2

  3. Netty的零拷贝提体现在哪里,与操作系统上的有什么区别?
    Zero-copy就是在操作数据时, 不需要将数据buffer从一个内存区域拷贝到另一个内存区域。 少了一次内存的拷贝,CPU的效率就得到的提升。在OS层面上的Zero-copy 通常指避免在 用户态(User-space) 与 内核态(Kernel-space)之间来回拷贝数据。Netty的Zero-copy完全是在用户态(Java 层面)的, 更多的偏向于优化数据操作。

    • Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
    • Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。
    • Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。
      参考:https://www.jianshu.com/p/a199ca28e80d
  4. Netty的内存池是怎么实现的?
    https://blog.csdn.net/kuangzhanshatian/article/details/80881904
    https://www.jianshu.com/p/8d894e42b6e6

  5. Netty的对象池是怎么实现的?
    参考:https://www.jianshu.com/p/3bfe0de2b022

posted @ 2019-09-16 17:06  编码专家  阅读(22993)  评论(0编辑  收藏  举报