1 简述BIO、NIO和AIO。
BIO:客户端并发数和后端的线程数是一比一,线程的创建和销毁很消耗系统资源。并发量大,服务器性能下降,会出现栈和堆溢出错误。当前线程创建连接后,如果没有操作进入阻塞操作,浪费服务器资源。
NIO:通过多路复用器一个线程处理多个通道,避免多线程之间的上下文切换导致系统开销过大。并且通道中有了事件,才能进行读写操作。减少系统开销。
AIO:异步IO,用户线程通过系统调用,告知内核启动某个IO操作(数据准备、数据复制),完成后通知用户程序,执行后续的操作。但是依赖操作系统底层,而且linux系统支持不完善。
2 为什么使用Netty进行项目开发?
- Netty是基于NIO实现的,实现了阻塞和非阻塞Socket。而且对各种协议API进行统一封装,简化开发。
- Netty只依赖JDK底层api,不需要引入额外依赖。
- 社区活跃,发现bug可以快速修复。
- 高度可定制线程模型,单线程、一个或多个线程池。
- 网络通信更加高性能、低延迟、尽可能的减少不必要的内存拷贝。
- NIO可能会导致Selector空轮询问题,官网JDK1.6修复该问题,只是概率低点。
3 什么是Reactor线程模型?
不是Java和Netty专有的,是一种并发编程模型,是一种思想。
Reactor有三种角色,Reactor负责监听和分配事件。Acceptor处理客户端新连接,并分派请求到处理链中。Handler将自身和事件绑定,执行读写操作。
Reactor线程模型有三种,单线程、多线程、主从多线程。
4 介绍下单线程、多线程、主从多线程模型。
单线程:
Reactor充当多路复⽤器⻆⾊,监听多路连接的请求,由单线程完成 。Reactor收到客户端发来的请求时,如果是新建连接通过Acceptor完成,其他的请求由Handler完成(读写操作)。
试用一些简单业务逻辑和并发量不高的地方。没有线程之间切换和线程之间通信问题。但是Reactor负担过重,导致系统性能下降,如果当前线程进入死循环,整个系统不可用。
多线程:
和单线程之间区别是,Handler只负责响应用户请求,真正读写操作在其他线程完成。降低Reactor的性能开销,充分利⽤CPU资源,从⽽更专注的做事件分发⼯作。提升整 个应⽤的吞吐。
多线程数据共享和访问⽐较复杂。如果⼦线程完成业务处理后,把结果传递给主线程Reactor进⾏ 发送,就会涉及共享数据的互斥和保护机制。 Reactor承担所有事件的监听和响应,只在主线程中运⾏,可能会存在性能问题。例如并发百万客户端连接,或者服务端需要对客户端握⼿进⾏安全认证,但是认证本身⾮常损耗性能。
主从多线程模型:
MainReactor负责监听server socket,处理⽹络IO连接建⽴,将建⽴的socketChannel指 定注册给SubReactor。
SubReactor主要完成和建⽴起来的socket的数据交互和事件业务处理操作。
响应快,不必为单个同步事件所阻塞,虽然Reactor本身依然是同步的。 可扩展性强,可以⽅便地通过增加SubReactor实例个数来充分利⽤CPU资源。 可复⽤性⾼,Reactor模型本身与具体事件处理逻辑⽆关,具有很⾼的复⽤性。
总结:在netty中多Reactor三种模型都有很好的支持,一般情况下,服务端采用主从架构模型。
5 IO模型:阻塞IO模型、非阻塞IO模型、select、epoll线程模型。
阻塞IO模型
创建socket,fd绑定监听,调用系统的accept()函数等待系统内核响应,如果内核没有接受连接,一直阻塞中,接受客户端数据函数也是阻塞中,发送信息也是等待客户端信息发送。
非阻塞IO模型
调用系统的连接函数、接受客户端数据函数、发送数据函数都变成异步了,不用等待内核响应,直接执行下一步。应用程序要得到响应不断的轮询调用。
select、epoll线程模型
redis模块中已总结过,https://www.jianshu.com/p/fe6d9f7d9e9b。
6 Netty中的核心组件
ChannerHandler、Channel、EventLoop、EventLoopGroup、ChannelPipeline、Bootstrap、Future
Channel:每个客户端连接都会创建一个Channel,它负责基本的IO操作。
EventLoop:用于监控和协调事件,每个EventLoop会占用一个Thread,当前线程处理发生在EventLoop上所有IO操作。
EventLoopGroup:用于生成EventLoop。服务端需要两个,客户端需要一个。因为服务端需要建立连接和业务处理。
ChannelHandler:对于数据的⼊站和出站的业务逻辑的编写都是在ChannelHandler中完成的。
ChannelPipeline:channel进行数据传递时候,需要进行编码和节码等一系列操作操作。ChannelPipeline是对ChannelHandler管理。
Bootstrap:将各个组件串起来,并绑定端口,启动服务。
Future:了⼀种在操作完成时通知应⽤程序。
7 Netty零拷贝解释下?
Bytebuf 使⽤的是⽤池化的Direct Buffer类型使⽤的堆外内存,不需要进⾏字节缓冲区的⼆次拷 ⻉,如果使⽤堆内存,JVM会先拷⻉到堆内,再写⼊Socket,就多了⼀次拷⻉。 CompositeByteBuf将多个ByteBuf封装成⼀个ByteBuf,在添加ByteBuf时不需要进程拷⻉。 Netty的⽂件传输类DefaultFileRegion的transferTo⽅法将⽂件发送到⽬标channel中,不需要进 ⾏循环拷⻉,提升了性能。
8 ByteBuf 释放问题
手动释放:ReferenceCountUtil.release(byteBuf); 进⾏释放。
自动释放:
HeadHandler出站自动释放。
⼊站的TailHandler,TailHandler放在最后面,每个处理器将消息下穿。
继承SimpleChannelInboundHandler自动释放。
9 知道哪些编解码器?
在netty开发中定义编解码器需要实现ByteToMessageDecoder、MessageToByteEncoder。
对象编解码器:JDk自带的编解码器、Hessian编解码器。
基于Redis协议的编解码器。字符串编解码器。
10 Netty 拆包粘包的实质?TCP通信的粘包和拆包怎么处理?
在Netty的应用层,按照 ByteBuf 为单位来发送/读取数据,但是到了底层操作系统仍然是按照字节流发送数据。因此,从底层到应用层,需要进行二次拼装。操作系统底层,是按照字节流的方式读入,到了 Netty 应用层面,需要二次拼装成 ByteBuf。在Netty 层面,拼装成ByteBuf时,就是对底层TCP缓冲的读取,这里就有问题了。上层应用层每次读取底层缓冲的数据容量是有限制的,当TCP底层缓冲数据包比较大时,将被分成多次读取,造成断包,在应用层来说,就是半包。其次,如果上层应用层一次读到多个底层缓冲数据包,就是粘包。
在发送消息包时候,在头部加上两个字节,存储发送数据字节长度。在bytebuf获取数据时候,先读取两个字节,即字节长度len1,然后从bytebuf中读取len1长度的字节。
11 Netty服务启动流程?
创建服务端channel,
初始化服务端channel,
注册selector,
绑定端口。
12 连接过程刨析?
新连接接入:
注册读事件
13 使用Netty如何优化?
2. 使⽤EventLoop的任务调度
直接放⼊channel所对应的EventLoop的执⾏队列,⽽直接使⽤channel.writeAndFlush(data),会导致线程的切换。
3. 减少ChannelPipline的调⽤长度
ctx.channel().writeAndFlush(msg); ,所有的handler都会执行一遍。ctx.writeAndFlush(msg),从当前的handler一直执行到pipline尾部。
4. 减少ChannelHandler的创建
如果channelhandler是⽆状态的(即不需要保存任何状态参数),那么使⽤Sharable注解,并在 bootstrap时只创建⼀个实例,减少GC。否则每次连接都会new出handler对象。
5. ⼀些配置参数的设置
bossGroup只是需要配置一个,因为ServerSocketChannel只会注册到一个eventLoop上,⽽这个eventLoop只会有⼀个线程在运⾏。workGroup集器核数两倍,即发挥虚拟机内核优势,又防止线程上下文切换开销。
响应时间有⾼要求的场景,禁⽤nagle算法,不等待,⽴即发送。
14 我说Netty不是真正的“异步IO”,你认同吗?
认同。因为使用的IO模型是采用epoll,应用端不断循环调用epoll对象的消息队列,当消息队列中有事件消息,然后应用端对事件消息一个个处理。所以是同步操作。但是由于也是一个线程处理多个客户端的读写操作,类似异步感觉。
15 Netty的IO模型,Reactor模式,Handler线程一定要新起一个线程来进行数据库操作吗?
不一定,因为handler进行业务操作是在workeGroup线程池操作,这个线程池线程数是内核的两倍。所以Handler进行业务操作,如果线程数没有达到核心线程数,则会创建一个新的线程执行。如果达到了核心线程数,则会使用之前的线程执行。
16 Netty 中责任链?
就是ChannelPipeline,用来将每一个handler串成链表,然后进行管理。保证了进行消息传递时候,执行handler顺序。
17 长连接和短链接描述下。
TCP在进行读写前,需要server和client建立连接。建立连接的过程就是三次握手四次挥手。消耗网络资源和有事件延迟。
短链接:就是客户端和服务端读写完毕后,释放连接。再次读取,需要从新建立连接。
长连接:每次读写之后,连接不断开。后续读写依旧使用这个连接。
18 Netty的长连接/心跳机制有了解?
在TCP保持长连接时候,由于网络原理或者其他异常。client和server没有交互,无法发现对方掉线。引入心跳机制。
心跳机制原理,在client和server在一定时间内没有数据交互,服务端或者客户端发送一个特殊数据包给对象,对方立刻回应,代表连接中。
TCP实际上自带长连接选项,本身就有心跳包机制。但是TCP协议层面长连接灵活性不够,所以一般是在应用层自定义心跳机制,在netty中实现,则是通过IdleStateHandler核心类实现。
19 一个请求进入netty具体用了哪些类?
如果是一个连接请求,首先是被EventLoopGroup产生的EventLoop监控到是连接事件,然后由Acceptor进行连接创建。
如果是一个读/写请求,被工作的EventLoopGroup产生EventLoop监控到,然后进入Channel,进行执行链ChannelPipeline,执行每一个ChannelHandler。