1、Netty学习(一):初识Netty
2、Netty学习(二):Netty的核心组件
3、Netty学习(三):Netty的流程分析
4、Netty学习(四):Netty零拷贝(转载)
5、Netty学习(五):Netty实现UDP的Server端
作为当前最流行的 NIO 框架,Netty 在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,知名的 Elasticsearch 、Dubbo 框架及SpringWebFlux框架内部都采用了 Netty。
Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,它已经得到成百上千的商用项目验证,例如Hadoop的RPC框架avro使用Netty作为底层通信框架。很多其它业界主流的RPC框架,也使用Netty来构建高性能的异步通信能力。
正是因为这些优点,Netty逐渐成为Java NIO编程的首选框架。
生活场景
当我们去饭店吃饭时:
• 食堂排队打饭模式:排队在窗口,打好才走;
• 点单、等待被叫模式:等待被叫,好了自己去端;
• 包厢模式:点单后菜直接被端上桌
现实模型 | IO模式 | JDK版本 |
---|---|---|
排队打饭模式 | BIO (同步阻塞) | JDK1.4之前 |
点单被叫模式 | NIO (同步非阻塞) | JDK1.4 |
包厢模式 | AIO(异步非阻塞) | JDK1.7 |
Netty目前仅支持NIO模式,即同步非阻塞。
耗资源、效率低
阻塞意味着等待,等待就会一直占用该线程,当连接数高时,大多线程又在等待,就会耗尽系统的线程资源。
Netty其实曾经是支持AIO的,但是因为AIO模式在WINDOWS环境下性能提高很明显,但是在Liunx环境下性能提升不明显,而WINDOWS很少作为服务器使用,netty 也是联系实际情况才有选择地支持高性能的 IO 模式。
在web服务中,处理web请求通常有两种体系结构,分别为:thread-based architecture(基于线程的架构)、event-driven architecture(事件驱动模型),
通俗的说就是:多线程并发模式,一个连接一个线程,服务器每当收到客户端的一个请求, 便开启一个独立的线程来处理。
这种模式一定程度上极大地提高了服务器的吞吐量,由于在不同线程中,之前的请求在read阻塞以后,不会影响到后续的请求。但是,仅适用于于并发量不大的场景,因为:
事件驱动模型非常常见,当事件被触发运行时,程序感知到不同的时间而产生的不同的动作,比如常见的WEB界面,在点击不同的按钮后会产生不同的效果,为了能够实现这一效果,就需要程序能够实时的感知所关心的事件和触发事件之后要完成的动作。
事件驱动模型有多种设计方式,这里主要介绍Netty的Reactor设计模式。
生活场景:饭店规模变化
小饭馆: 一个人包揽所有:迎宾、点菜、做饭、上菜、送客等;
普通饭店: 多招几个伙计:大家一起做上面的事情;
海底捞: 进一步分工:搞一个或者多个人专门做迎宾,迎宾完之后有专门的服务员对应一定的桌台。
生活场景类比:
• 饭店伙计:线程
• 迎宾工作:接入连接
• 点菜:请求
• 做菜:业务处理
• 上菜:响应
• 送客:断连
所有操作都在同一个NIO线程处理,在这个单线程中要负责接收请求,处理IO,编解码所有操作,相当于一个饭馆只有一个人,同时负责前台和后台服务,效率低。
EventLoopGroup eventGroup = new NioEventLoopGroup(1);
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(eventGroup);
多线程的优点在于有单独的一个线程去处理请求,另外有一个线程池创建多个NIO线程去处理IO。相当于一个饭馆有一个前台负责接待,有很多服务员去做后面的工作,这样效率就比单线程模型提高很多。
EventLoopGroup eventGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(eventGroup);
多线程模型的缺点在于并发量很高的情况下,只有一个Reactor单线程去处理是来不及的,就像饭馆只有一个前台接待很多客人也是不够的。为此需要使用主从线程模型。
主从线程模型:一组线程池接收请求,一组线程池处理IO。
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup);
在客户端发送数据时,实际是把数据写入到了TCP发送缓存里面的。
发送两条消息:“ABC”,“DEF”,那么对方接收到的消息不一定是这种格式的,对方可能一次性就接收到“ABCDEF”,也可能分好几次接收到“AB”,“CD”,“EF”,或者更为恶劣一次接收到了两个消息“A”,“BCDEF”。那么这样一次接收两个消息的称为粘包现象,分三次四接收到多个不完整的现象是半包现象。
如果发送的包的大小比TCP发送缓存容量小,并且TCP缓存可以存放多个包,那么客户端和服务端的一次通信就可能传递了多个包,这时候服务端从接受缓存就可能一下读取了多个包
发送方每次写入数据 < 套接字缓冲区大小,接收方读取套接字缓冲区数据不够及时。
顾名思义就是接收到半个包,如果发送的包的大小比TCP发送缓存的容量大,那么这个数据包就会被分成多个包,通过socket多次发送到服务端,服务端第一次从接受缓存里面获取的数据,实际是整个包的一部分,半包不是说只收到了全包的一半,是说收到了全包的一部分。
发送方写入数据 > 套接字缓冲区大小,还有就是发送的数据大于协议的MTU(Maximum Transmission Unit 最大传输单元)的时候必须拆包。(MTU其实就是TCP协议每层的大小
封装成帧,找出消息的边界
封装成帧有三种方式
长度理论上有限制,需提前预知可能的最大长度从而定义长度占用字节数
XML或者JSON,他的<>和{}都是成对出现的,只需要找出对应的边界即可。
为什么编解码要分为两层,难道一层不可以吗?
一层当然是可以的,但是这样就没有了分层,导致代码的耦合度很高,如果有新的数据需要进行编解码,就要修改一大堆代码。
在项目中,除了可选的的压缩解压缩之外,还需要一层解码,因为一次解码的结果是字节,需要和项目中所使用的对象做转化,方便使用,这层解码器可以称为“二次解码器”,相应的,对应的编码器是为了将 Java 对象转化成字节流方便存储或传输。
流程如下
io.netty.buffer.ByteBuf (原始数据流)-> io.netty.buffer.ByteBuf (用户数据)
io.netty.buffer.ByteBuf (用户数据)-> Java Object
Netty支持多种编解码方式,
• Java 序列化
• Marshaling
• XML
• JSON
• MessagePack
• Protobuf
• 其他