一、先理解相关概念
BIO阻塞 | 排队点餐,阻塞等待取餐 | 耗资源,效率低,基本已被废弃 |
NIO非阻塞 | 先取号,等待被叫,再去取餐 | |
AIO异步 | 包厢,点餐后等待端上来就行 | 1、windows实现成熟,但windows很少用来作为服务器; 2、linux常用来作为服务器,但AIO实现不够成熟; 3、linux下AIO相比NIO性能提升不够明显; |
阻塞 | 1、读取数据没准备好时,等待数据返回(死等);2、写操作时缓冲区数据满了,等待数据被消费(死等); |
非阻塞 | 读取数据没准备好或写缓冲满时,都直接返回。 |
同步 | 数据准备就绪后需要调用方主动来读就是同步; |
异步 | 数据准备好在回调给程序就是异步 |
二、BIO的阻塞问题
随着各类NIO框架的发展,以及基于NIO的Web服务器的发展,Java在很多领域取代了C和C++,成为企业服务端应用开发的首选语言。在JDK 1.4推出Java NIO之前,基于Java的所有Socket通信都采用了同步阻塞模式(BIO),这种一请求一应答的通信模型简化了上层的应用开发,但是在性能和可靠性方面却存在着巨大的瓶颈。当并发访问量增大、响应时间增大之后,采用Java BIO开发的服务端软件只有通过硬件的不断扩容来满足高并发和低时延,它极大地增加了企业的成本,并且随着集群规模的不断膨胀,系统的可维护性也面临巨大的挑战,只能通过采购性能更高的硬件服务器来解决问题,这会导致恶性循环。显然BIO无法满足高性能、高并发接入的场景。
同步阻塞是一请求一连接,socket服务端有个Acceptor来处理所有的请求,然后分发给一个对应的线程去处理,每个客户端请求对应一个线程,当请求并发量高的时候,就会出现阻塞,后面会出现大量的连接请求超时,导致客户端以为服务已经挂掉。后面的伪异步I/O只是在此基础上将线程的创建交给线程池来完成,client建立连接作为线程池的一个任务来执行,虽做了一点优化,仍然还是面对同样的阻塞问题。
三、使用NIO编程的优点有什么?
1)客户端发起的连接操作是异步的,可以通过在多路复用器Selector上注册请求,用一个线程来处理多个channel
2)SocketChannel的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回,这样I/O通信线程就可以处理其他的链路,不需要同步等待这个链路可用。
3)线程模型的优化:一个Selector线程可以同时处理成千上万个客户端连接,而且性能不会随着客户端的增加而线性下降,因此,它非常适合做高性能、高负载的网络服务器。
四、主要的模块
1)Buffer(缓冲区):buffer是一个对象,NIO的API相对以前的inputstream和outputstream来说的,所有的数据都是通过缓冲区来处理的(读写),缓冲区实际上是一个数组,通常他是一个字节数组(ByteBuffer),但是这样的buffer是非常难用的。
ByteBuffer.allocate(8) // 初始化
2)Channel(管道):
3)Selector(多路复用器):
selector用一个线程长轮询多个Channel,根据其状态对缓冲区和通道的数据进行操作
4)CharSet 用于将Unicode编解码为对应的序列化码流
五、应用场景有哪些?
在互联网、大数据、网络游戏、企业应用、电信软件等众多行业得到成功商用,证明了它已经完全能够满足不同行业的商业应用了。
数据库:Cassandra
大数据处理:Spark、Hadoop
Message Queue:RocketMQ
检索:Elasticsearch
框架:gRPC、Apache Dubbo、Spring5
分布式协调器:ZooKeeper
工具类:async-http-client
其他参考:Netty.docs: Adopters
六、为什么用Netty?
使用jdk提供的NIO API开发异步通信十分复杂,Netty是一个通信框架,有着简单的API,开发容易上手。
1、Netty做得更多:
• 支持常用应用层协议;
• 解决传输问题:粘包、半包现象;
• 支持流量整形;
• 完善的断连、Idle等异常处理等。
2、Netty做得更好之一:规避JDK NIO bug:
3、Netty做得更好之二:API更友好更强大
JDK的NIO一些API不够友好,功能薄弱,例如ByteBuffer-> Netty’sByteBuf
除了NIO外,也提供了其他一些增强:Theadlocal-> Netty’sFastThreadLocal
七、netty实现通信的步骤
1、创建两个NIO线程组,一个专门用于网络事件处理(接受客户端连接),另一个则进行网络通信处理。
2、创建一个ServerBootStrap,配置netty的一些列参数,例如接收传出数据的缓存大小等。
3、创建一个实际处理数据的类ChannelInitializer,进行初始化的准备工作,比如设置传出数据的字符集、格式、已经实际处理数据的接口。
4、绑定端口,执行同步阻塞方法等待服务器端启动即可。
通过这么简单的几个步骤,就可以实现一个健壮、稳定的服务端通信,这在之前的NIO是不可能实现的。
八、Reactor的三种模式
Reactor单线程 | 单个线程处理所有事情,如饭店迎宾、点菜、做菜、上菜、结账、送客一系列操作 |
Reactor多线程 | 多线程分别处理以上所有事情 |
主从Reactor | 一个线程专门处理连接建立和关闭,其他线程处理业务bossGroup和workerGroup |
九、二次编解码器
一次解码器 | ByteToMessageDecoder |
二次解码器 | MessageToMessageDecoder |
netty内置的三种编解码器依次为FixedLengthFrameDecoder、LengthFieldBasedFrameDecoder 、DelimiterBasedFrameDecoder,由于一次解码的结果是字节,为了方便和项目中所使用的对象做转化,就需要二次解码
常用二次编解码器:
Java序列化、Marshaling、XML、JSON、MessagePack、Protobuf
十、心跳keepalive+idle
前者即心跳消息,后者为时间间隔,好比和别人通电话,对方突然不讲话了,等待几秒后,询问还在吗,或者直接挂断,或者重拨。Idle监测,只是负责诊断,诊断后作出不同的行为。
实际应用中:结合起来使用。按需keepalive,保证不会空闲,如果空闲,关闭连接。
1、Server端开启TCP keepalive
bootstrap.childOption(ChannelOption.SO_KEEPALIVE,true)
bootstrap.childOption(NioChannelOption.of(StandardSocketOptions.SO_KEEPALIVE), true)
提示:.option(ChannelOption.SO_KEEPALIVE,true) 存在但是无效
2、 开启不同的Idle Check:
ch.pipeline().addLast(“idleCheckHandler",
new IdleStateHandler(0, 20, 0, TimeUnit.SECONDS));