1,netty 是什么?
netty是一款基于NIO(非阻塞IO)的通信框架,对于BIO(阻塞IO)来说,很大的提高了其并发性。极大的简化并优化了tcp和udp套接字服务器等网络编程,并且性能和安全性更好,支持多种协议,如ftp,smtp,http等
BIO可参考java jdk中的 ServerSocket的写法,在读取字节流时,没有可用于前后移动的索引,只是一个连续的数据流,每次客户端请求链接到服务器,它都会阻塞一个线程,如果需要很多同时链接,就需要一个线程池。
NIO是基于套接字连接的非阻塞API,也就意味着对可用线程的数量并不紧密,使用它,一个线程可以同时处理多个连接。比如netty的channel。
2,netty的特点是什么?为什么要用netty?
- 高并发:比java api有更好的吞吐量,较低的延时,减少内存拷贝,资源耗时更少(这个得益于共享池和重用)
- 传输快: Netty 的传输依赖于零拷贝特性,尽量减少不必要的内存拷贝,实现了更高效率的传输。
- 易用性:设置了多种编解码功能,支持多种主流协议。扩展性强,而且稳定。
netty比jdk自带的NIO相关的API更加易用。
- 简单而强大的线程模型
- 自带编解码解决TCP粘包/拆包问题
- 自带各种协议栈
- 真正的无连接数据包套接字支持
- 比直接使用jdk的api有更高的吞吐量,更低的延时,更低的资源和更少的内存复制
- 安全性强,有完整的SSL/TLS以及StartTLS支持
- 成熟稳定,现在很多开源项目使用了netty,比如dubbo和es等
3,什么是netty的零拷贝?
netty的接受和发送byteBuffer 采用 direct buffers,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝,如果使用传统的堆内存进行socket读写,jvm会将堆内存buffer拷贝一份到直接内存中,然后才写入socket中,相对于堆外直接内存,消息在发送过程中多了一次缓存区的内存拷贝。
netty提供了组合Buffer对象,可以聚合多个byteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个buffer合并成一个大的buffer。
netty的文件传输采用了transferTo方法,它可以直接将文件的缓冲区的数据发送到目标Channel,避免了传统通过循环write的方法导致的内存拷贝问题。
4,netty的使用场景?
dubbo框架就是基于netty的通信组件,还有ElasticSearch 也是。由Transport负责通信,基于tcp通信采用netty实现。RocketMQ,gRPC 等
作为RPC框架的网络通信工具:我们在分布式系统中,不通服务节点之间经常需要相互调用,这个时候就需要RPC框架了,就可以用netty实现,比如我调用另外一个节点的方法的化,至少需要让对方知道我调用的是哪个类中的哪个方法以及相关参数
实现一个即时通讯系统: 类似与微信
实现消息推送系统:
5,netty高性能的具体表现?
- 同步非阻塞IO,
- 内存零拷贝,
- 内存池设计,申请的内存可以重用,主要指直接内存,内部实现是一颗二叉树找树管理内存分配情况,
- 串形化处理读写:避免使用锁带来的性能开销
- 高性能序列化协议: 支持protobuf等高性能序列化协议。
6,netty和tomcat的区别?
- 作用不同:tomcat是Servlet容器,可以视为web服务器,而netty是异步事件驱动的网络应用程序框架和工具用于简化网络编程。
- 协议不同:tomcat是基于http协议的web服务器,而netty是通过编程自定义各种协议,因为netty本身自己能编码/解码字节流,所有netty可以实现,http服务,ftp服务,udp服务,rpc服务,websocket服务,redis的proxy服务,mysql的proxy服务等
7,netty中有哪些重要的组件?
- Channel:netty网络操作抽象类。它包括了基本的 I/O 操作,如 bind、connect、read、write 等。比较常用的Channel接口实现类是NioServerSocketChannel(服务端)和NioSocketChannel(客户端),这两个 Channel 可以和 BIO 编程模型中的ServerSocket以及Socket两个概念对应上。Netty 的 Channel 接口所提供的 API,大大地降低了直接使用 Socket 类的复杂性。
- EventLoop:主要是配合 Channel 处理 I/O 操作,用来处理连接的生命周期中所发生的事情。主要作用实际就是负责监听网络事件并调用事件处理器进行相关 I/O 操作的处理。Channel 为 Netty 网络操作(读写等操作)抽象类,EventLoop 负责处理注册到其上的Channel 处理 I/O 操作,两者配合参与 I/O 操作。
- ChannelFuture:Netty 框架中所有的 I/O 操作都为异步的,因此我们需要 ChannelFuture 的 addListener()注册一个 ChannelFutureListener 监听事件,当操作执行成功或者失败时,监听就会自动触发返回结果。
- ChannelHandler:充当了所有处理入站和出站数据的逻辑容器。ChannelHandler 主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。
- ChannelPipeline:为 ChannelHandler 链提供了容器,当 channel 创建时,就会被自动分配到它专属的 ChannelPipeline,这个关联是永久性的。
8,Netty 发送消息有几种方式?
- 直接写入 Channel 中,消息从 ChannelPipeline 当中尾部开始移动;
- 写入和 ChannelHandler 绑定的 ChannelHandlerContext 中,消息从 ChannelPipeline 中的下一个 ChannelHandler 中移动。
9,默认情况 Netty 起多少线程?何时启动?
Netty 默认是 CPU 处理器数的两倍,
// 从1,系统属性,CPU核心数*2 这三个值中取出一个最大的 //可以得出 DEFAULT_EVENT_LOOP_THREADS 的值为CPU核心数*2
private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
bind 完之后启动。
channelFuture = serverBootstrap.bind(PORT).sync()
10,什么是TCP粘包/拆包?有什么解决方法?
TCP粘包/拆包是基于TCP发送数据的时候,出现了多个字符串“粘”在了一起或者一个字符串被“拆”开的问题,
解决方案:
- 使用netty自带的解码器:
- LineBasedFrameDecoder : 发送端发送数据包的时候,每个数据包之间以换行符作为分隔,LineBasedFrameDecoder 的工作原理是它依次遍历 ByteBuf 中的可读字节,判断是否有换行符,然后进行相应的截取。
- DelimiterBasedFrameDecoder : 可以自定义分隔符解码器,LineBasedFrameDecoder 实际上是一种特殊的 DelimiterBasedFrameDecoder 解码器。
- FixedLengthFrameDecoder: 固定长度解码器,它能够按照指定的长度对消息进行相应的拆包。
- LengthFieldBasedFrameDecoder
- 自定义序列化编解码器:
在 Java 中自带的有实现 Serializable 接口来实现序列化,但由于它性能、安全性等原因一般情况下是不会被使用到的。
通常情况下,我们使用 Protostuff、Hessian2、json 序列方式比较多,另外还有一些序列化性能非常好的序列化方式也是很好的选择:
- 专门针对 Java 语言的:Kryo,FST 等等
- 跨语言的:Protostuff(基于 protobuf 发展而来),ProtoBuf,Thrift,Avro,MsgPack 等等
11,TCP长连接和短连接
我们知道 TCP 在进行读写之前,server 与 client 之间必须提前建立一个连接。建立连接的过程,需要我们常说的三次握手,释放/关闭连接的话需要四次挥手。这个过程是比较消耗网络资源并且有时间延迟的。所谓,短连接说的就是 server 端 与 client 端建立连接之后,读写完成之后就关闭掉连接,如果下一次再要互相发送消息,就要重新连接。短连接的优点很明显,就是管理和实现都比较简单,缺点也很明显,每一次的读写都要建立连接必然会带来大量网络资源的消耗,并且连接的建立也需要耗费时间。长连接说的就是 client 向 server 双方建立连接之后,即使 client 与 server 完成一次读写,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。长连接的可以省去较多的 TCP 建立和关闭的操作,降低对网络资源的依赖,节约时间。对于频繁请求资源的客户来说,非常适用长连接。
12,为什么需要心跳机制?Netty 支持哪些心跳类型设置?
在 TCP 保持长连接的过程中,可能会出现断网等网络异常出现,异常发生的时候, client 与 server 之间如果没有交互的话,他们是无法发现对方已经掉线的。为了解决这个问题, 我们就需要引入 心跳机制 。
心跳机制的工作原理是: 在 client 与 server 之间在一定时间内没有数据交互时, 即处于 idle 状态时, 客户端或服务器就会发送一个特殊的数据包给对方, 当接收方收到这个数据报文后, 也立即发送一个特殊的数据报文, 回应发送方, 此即一个 PING-PONG 交互。所以, 当某一端收到心跳消息后, 就知道了对方仍然在线, 这就确保 TCP 连接的有效性.
TCP 实际上自带的就有长连接选项,本身是也有心跳包机制,也就是 TCP 的选项:SO_KEEPALIVE。但是,TCP 协议层面的长连接灵活性不够。所以,一般情况下我们都是在应用层协议上实现自定义信跳机制的,也就是在 Netty 层面通过编码实现。通过 Netty 实现心跳机制的话,核心类是 IdleStateHandler 。
- readerIdleTime:为读超时时间(即测试端一定时间内未接受到被测试端消息)。IdleState.READER_IDLE
- writerIdleTime:为写超时时间(即测试端一定时间内向被测试端发送消息)。IdleState.WRITER_IDLE
- allIdleTime:所有类型的超时时间。IdleState.ALL_IDLE
//心跳机制 PING-PONG 交互 第一个参数设置未读时间,第二个参数设置为未写时间,第三个为都未进行操作的时间 //一般第一个时间是服务器设置多长时间没有收到客户端消息的时间,第二个是netty客户端设置多长时间给服务器发送一条消息的时间.心跳检测主要是通过向线程任务队列中添加定时任务,判断channelRead()方法或write()方法是否调用空闲超时,如果超时则触发超时事件执行自定义userEventTrigger()方法 ch.pipeline().addLast(new IdleStateHandler(0, 0, idleTImeSec) { @Override protected final void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception { log.info("evt {}", evt.state()); //IdleState.READER_IDLE 读空闲超时时间设定,如果channelRead()方法超过readerIdleTime时间未被调用则会触发超时事件调用userEventTrigger()方法; 表示 如果客户端没有在规定的时间内给你发送消息,服务端就会认为这个客户端宕掉了,关闭这个链接 //IdleState.WRITER_IDLE 写空闲超时时间设定 allIdleTime所有类型的空闲超时时间设定,包括读空闲和写空闲; if (evt.state() == IdleState.ALL_IDLE) { ctx.fireExceptionCaught(ReadTimeoutException.INSTANCE); } } });