一
由于本人的码云太多太乱了, 于是决定一个一个的整合到一个 springboot 项目里面.
附上自己的 GitHub 项目地址 https://github.com/247292980/spring-boot
附上汇总博文地址 https://www.cnblogs.com/ydymz/p/9391653.html
以整合功能
spring-boot,FusionChart,thymeleaf,vue,ShardingJdbc,mybatis-generator, 微信分享授权, drools,spring-security,spring-jpa,webjars,Aspect,drools-drt,rabbitmq,zookeeper,MongoDB,MySQL 存储过程, 前端的延迟加载, netty,PostgreSQL, 树的遍历
这次再次说下 netty, 为什么在学 netty 呢? 因为面试一家大公司的时候, 被问倒了,,, 所以只好耐下心来看原理了.
目前看了两本 netty 的书 (市面上就两本 orz) 第一感觉就是 netty 的书怎么说呢, 就是 demo 集合, 某程度还是挺好看懂的, 但是过时啊.
不过参考之前写 drools 的痛苦经验, 国内大部分环境应该也是用过时的 netty, 不信你去技术群里问下新版 netty 的特性, 大家一定会劝你去踩坑...
而这篇文章也不介绍新特性, 只是对 netty 的原理研究. 说实话就是丢图,,,
二 模型
说到 netty 一定要知道他的基本模型.
1. 先说下之前的 bio - 同步阻塞 io
显然有点网络基础的人都写过类似的代码. 优缺点很明显
缺点: 性能瓶颈. 每个链接一个线程, 怎么保证线程的即时回收和可靠回收是一大技术问题. 而该模型在遇到大量线程的时候 (超过某个阈值, 例如 java 默认的线程上限或者 Linux 的默认线程上限之类的) 性能指数性下降啊.
优点: 简单. 所以我才说有点网络基础的人都写过类似的代码,,, 除非直接起手 springboot 的
2. 后面就是 nio - 同步非阻塞 io
acceptor 收到所有的连接请求
acceptor 上注册了 selector,
服务器构建对应的 Channel, 并在其上注册 Selector
selector 分配请求到不同的 channel, 进行相应的读写处理.
ps. 写完发现可能有点难理解, 具体代码项目里面有.
其实就是 selector 分配请求, channel 处理请求并能通过 selector 得到 client, 等处理完成后直接返回 client, 不走 selector.
优点: 一个线程处理全部连接, 不阻塞后面的连接. 性能大提升!
缺点: 模型复杂, 代码复杂(至少我觉得不看一下代码, 很难说清); 处理半包问题
ps. 半包问题
我们知道 TCP/IP 在发送消息的时候, 可能会拆包. 这就导致接收端无法知道什么时候收到的数据是一个完整的数据.
例如: 发送端分别发送了 ABC,DEF,GHI 三条信息, 发送时被拆成了 AB,CDRFG,H,I 这四个包进行发送, 接受端如何将其进行还原呢? 在 BIO 模型中, 当读不到数据后会阻塞, 而 NIO 中不会! 所以需要自行进行处理! 说到底就是自定义网络协议, 额其实这个有点基础的都写过, 我也不细说了.
3. 所以为了解决这问题, netty 就使用了 Reactor 模型 - bio 的变种
具体来说这是 reactor 的单线程模型, 可以看出和 bio 基本没什么差别, 就是把 channel 的数据处理提到 handler, 并且统一在 reactor 注册了, 而不用去 acceptor,channel 分别注册在两个地方, 统一处理了.
但是这玩意有个问题就是, 一个 client 多次请求, handler 中的处理特别慢, 那么后续的请求就会被积压.
4.Reactor 多线程模型
reactor 将接受发送分离, client 发送的请求丢到线程池中, 所以后续请求不会被阻塞.
但是当用户进一步增加的时候, Reactor 会出现瓶颈! 因为 Reactor 既要处理 IO 操作请求, 又要响应连接请求! 为了分担 Reactor 的负担, 所以引入了主从 Reactor 模型!
上面的这一句话是书上原话, 其实我是不太赞成的, 因为说到底就是一个线程的事, 所以没可能是性能瓶颈, 而在我看来 netty 使用主从 reactor 的主要原因是代码可读性和易于理解.
5.Reactor 主从模型
简单明了
client 发送请求
acceptor 就是个死循环监听
mainreactor 分配 acceptor 监听到的请求到 channel
channel 通过 subreactor 将请求发到 handler 处理, 然后直接返回给 client
ps. 注意的是 mainreactor 一定是单线程, 多线程可能导致为 client 分配 channel 的时候, 一个线程分配了一个, 浪费资源; 也存在一个 client 的请求去了两线程, 而其中一个处理很久, 另一个则早早完成, 而此时 client 的等待时间是取最大等待时间的, 那么早早完成的还不如一起在另一个处理很久的线程里面;
三 术语
Bootstrap: Netty 通过设置 Bootstrap(引导)类的开始, 该类提供了一个应用程序网络层配置的容器
Channel: 是一个客户端用来进行连接的 Channel, 记录了 client 信息, 用于 io 操作的交互
ChannelHandler: 数据处理的容器
ChannelPipeline: 提供了一个容器给 ChannelHandler 链并提供了一个 API 用于管理沿着链入站和出站事件的流动
EventLoop: EventLoop 用于处理 Channel 的 I/O 操作. 一个单一的 EventLoop 通常会处理多个 Channel 事件
EventLoopGroup: 可以含有多于一个的 EventLoop 和 提供了一种迭代用于检索清单中的下一个
ChannelFuture: ChannelFuture 是 netty 的一个回调不管是否成功返回
ChannelFutureListener:ChannelFuture 通过 addListener 注册. 当操作完成时, 可以被通知(不管成功与否)
ps.ChannelFutureListener 加上了之后就可以说是实现异步.
四 channel
通常来说, 所有的 netty 的 I/O 操作都是从 Channel 开始的. 一个 channel 类似于一个 stream.
Stream 和 Channel 对比
我们可以在同一个 Channel 中执行读和写操作, 然而同一个 Stream 仅仅支持读或写.
Channel 可以异步地读写, 而 Stream 是阻塞的同步读写.
Channel 总是从 Buffer 中读取数据, 或将数据写入到 Buffer 中.
Channel 类型有:
FileChannel, 文件操作
DatagramChannel, UDP 操作
SocketChannel, TCP 操作
ServerSocketChannel, TCP 操作, 使用在服务器端.
五 Buffer
与 Channel 进行交互时, 我们就需要使用到 Buffer, 即数据从 Buffer 读取到 Channel 中, 并且从 Channel 中写入到 Buffer 中.
Buffer 其实就是一块内存区域, 并提供了一些操作方法让我们能够方便地进行数据的读写.
Buffer 类型有:
ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer(就是基本类型啊)
六 Selector
Selector 允许一个单一的线程来操作多个 Channel. 如果我们的应用程序中使用了多个 Channel, 那么使用 Selector 很方便的实现这样的目的, 但是因为在一个线程中使用了多个 Channel, 因此也会造成了每个 Channel 传输效率的降低.
为了使用 Selector, 我们首先需要将 Channel 注册到 Selector 中, 随后调用 Selector 的 select()方法, 这个方法会阻塞, 直到注册在 Selector 中的 Channel 发送可读写事件. 当这个方法返回后, 当前的这个线程就可以处理 Channel 的事件了.
七 Bootstrap
Bootstrap 是 Netty 提供的一个便利的工厂类, 我们可以通过它来完成 Netty 的客户端或服务器端的 Netty 初始化.
来源: https://www.cnblogs.com/ydymz/p/10219637.html