#todo
- 使用Netty替代springboot的tomcat作为服务器
- 使用Netty替代websocket来通信
- 更新一下一致性哈希算法,如何更高效,顺便加上解释
基本介绍和使用介绍
I/O模型:
- 阻塞I/O BIO:程序向内核发起I/O调用,发起调用的线程就一直等待内核返回结果。如果使用BIO实现异步,只能使用多线程模型,会导致增加线程切换的开销。
- 同步非阻塞IO NIO:应用程序通过轮询的方式获取请求结果。
- 多路复用I/O:多个进程的I/O可以注册到一个复用器上(Selector),如果Selector监听的所有的I/O内核缓冲区都没有可读数据,select调用会被阻塞,直到任意I/O在内核缓冲区中有刻度数据时,会立刻返回。并通知其他进程再次发起I/O,也就是使用一个线程监控多个IO操作。
- 信号驱动I/O:进程预先告知内核,向内核注册一个信号处理函数,之后进程返回不阻塞,进程通过信号处理函数继续调用I/O读取数据。IO操作就绪后,操作系统会发送信号通知应用程序。 通知何时可以开始一个I/O操作
- 异步I/O AIO:应用程序发起IO操作之后,立刻返回,操作系统在IO操作执行后,会通知应用程序,应用程序无需轮询IO状态。 通知I/O操作何时结束
- BIO和NIO的区别:
- AIO->异步I/O
Reactor 模型
是一种处理并发I/O操作的设计模式。核心思想是通过实践多路复用机制来管理多个I/O事件。
主要的组件:1. 事件多路分发器(负责监听多个I/O事件,将这些事件分发给相应的事件处理器)
2.事件处理器 3.事件分发器:将事件从多路分发器中分发到响应的处理器。
select、poll、epoll的区别
- select:将已连接的Socket都放在一个文件描述符集合中,然后调用select函数将文件描述集合拷贝到内核中,让内核来检查是否有网络事件发生,检查方式就是遍历文件描述符集合。
- poll:与select区别是,不再使用BitMap来存储文件描述符,突破了个数上限
- epoll:内核使用红黑树来关注待检测的Socket高效,使用事件驱动,内核维护了一个链表来记录就绪事件,只会将事件发生的Socket集合传递给应用程序吗,而不是轮询整个集合
如何解决对象创建和销毁问题
- 对象池服用技术
- 零拷贝技术:Netty在进行I/O读写时,直接使用DirectBuffer,从而直接避免了数据在堆内存和堆外内存之间的拷贝
零拷贝
数据从读取到内核缓冲区,然后直接由内核缓冲区发送到网络。而不是拷贝到用户缓冲区域再拷贝到内核缓冲区进行发送。
Netty零拷贝实现
- 使用堆外内存,避免JVM堆内存到堆外内存的数据拷贝
- CompositeByteBuf 类,可以组合多个 Buffer 对象合并成一个逻辑上的对象,避免通过传统内存拷贝的方式将几个 Buffer 合并成一个大的 Buffer。
- 通过 Unpooled.wrappedBuffer 可以将 byte 数组包装成 ByteBuf 对象,包装过程中不会产生内存拷贝。
- ByteBuf.slice ,slice 操作可以将一个 ByteBuf 对象切分成多个 ByteBuf 对象,切分过程中不会产生内存拷贝,底层共享一个 byte 数组的存储空间。
- Netty 使用 封装了transferTo() 方法 FileRegion,可以将文件缓冲区的数据直接传输到目标 Channel,避免内核缓冲区和用户态缓冲区之间的数据拷贝
Java中的IO模型
- BIO java.io是BIO
- NIO java.nio包是NIO的实现,实质上仍然是同步的,读写操作时非同步的,但是这些操作是由应用程序主动发起和处理的,所以仍然是同步的。
- AIO java.nio.channels是NIO的实现
按照定义来说,Netty是一个异步的、事件驱动的、用来做高性能高可靠性的网络应用的框架
Netty相比于Tomcat,不需要遵循Serlet规范,可以最大化的发挥NIO的特性。
如果需要面向TCP的网络应用开发,那么Netty才是最佳选择
三大组件
- Channel & Buffer
- Selector
- Bootstrap
逻辑架构
- 网络通信层:支持多种网络协议,当网络数据读取到内核缓冲区后,会触发各种网络事件,会分发给事件调度层进行处理。核心是
- BootStrap,负责Netty客户端的启动、初始化、服务器连接等过程
- ServerBootStrap:用于服务端绑定本地端端口,会绑定Boss和Worker两个EventLoopGroup
- Channle 通道,基于NIO更高层次的抽象吗
- 事件调度层:通过Reactor线程模型对各种事件进行聚合处理。通过Selector主循环线程继承u洞中时间,实际业务处理逻辑是交由服务编排曾中的Handler解决的。
- EventLoopGroup(核心):本质是一个线程池,主要负责接收I/O请求,并分配线程执行处理请求。通过创建不同的EventLoopGroup参数可以支持Reactor三种线程模型:
- 单线程模型:Group中只包含一个EventLoop,Boss和Worker使用同一个Group
- 多线程:多个EventLoop,B和W使用同一个
- 主从多线程:多个EventLoop,B和W使用不同的Group
- EventLoop负责处理Channel生命周期中所有的I/O事件,一个EventLoopGroup包含一个或者多个EventLoop,一个EventLoop同一时间只会绑定一个Channel,Channel生命周期内可以和多个EventLoop进行多次绑定和解绑
- EventLoopGroup(核心):本质是一个线程池,主要负责接收I/O请求,并分配线程执行处理请求。通过创建不同的EventLoopGroup参数可以支持Reactor三种线程模型:
- 服务编排层:负责组装各种服务,是核心处理链。
- ChannelPipeline核心编排组件,负责组装各种ChannelHandler,I/O读写触发时,会依次调用Handler进行拦截和处理。
- ChannelHandler完成具体的树decode和encode工作和处理公国。
- ChannelHandlerContext:用于保存上下文,通过HandlerContext可以知道Pipeline和Handler的关联关系,包含Handler生命周期的所有事件。
Bootstrap
Netty客户端的启动
ServerBootStrap
服务端启动,会绑定Boss和 Worker两个EventLoopGroup
Boss线程会不断接受新的连接,然后将连接分给Worker去处理
Channel
常用的Channel实现类
- NioServerSocketChannel 异步 TCP 服务端。
- NioSocketChannel 异步 TCP 客户端。
- OioServerSocketChannel 同步 TCP 服务端。
- OioSocketChannel 同步 TCP 客户端。
- NioDatagramChannel 异步 UDP 连接。
- OioDatagramChannel 同步 UDP 连接。
Channel与事件
- channelRegistered:Channel创建后被注册后被注册
- channelUnregistered:Channel创建后未注册或者从EventLoop取消注册
- channelActive:Channel处于就绪状态,可以被读写
- channelInactive:处于非就绪状态
- channelRead:Channel可以从远端读取数据
- channelReadComplete:Channel读取数据完成
其他
- ChannelFuture
- 其 addListener()方法会注册一个ChannelFutureListener来等待通知
- ChannelHandler:事件处理的具体逻辑
- 生存周期:handlerAddedChannelHandler被添加到ChannelPipeline中时被调用,handlerRemoved被被移除,exceptionCaught处理过程中在ChannelPipeline中有错误产生时。
- ChannelInboundHandler->处理入站数据及状态变化。
- 方法:
- SimpleChannelInboundHandler会自动释放资源
- 方法:
- ChannelPipeline
- 每建立一个新的Channel都会分配一个ChannelPipeline,不可修改
- 提供了 ChannelHandler 链的容器,channel被创建时会被分配到ChannelPipeline中,ChannelHandler会被安装到其中
- 流程: ChannelInitializer的实现被注册进ServerBootstrapy中
- 调用initChannel()方法, ChannelInitializer会在ChannelPipeline中安装一组自定义的ChannelHandler
- ChannelInitializer将自己从 ChannelPipeline中移除
.childHandler(new ChannelInitializer<SocketChannel>() { //添加一个EchoServerHandle到子Channel的ChannelPipeline //ChannelInitializer是一个特殊的处理程序,用于帮助用户配置新的Channel @Override protected void initChannel(SocketChannel socketChannel) throws Exception { //EchoServerHandle被标注为@Shareable,所以我们可以总是使用同样的实例 //@Shareable表示一个ChannelHandler可以被多个Channel安全地共享 //在哪标注的呢?在EchoServerHandle类上 socketChannel.pipeline().addLast(serverHandle); }
- ChannerlHandlerContext接口
- 用于管理关联的ChannelHandler和同一个pipeline中其他的Handler
- 二者的联系和区别:
通过使用作为参数传递到每个方法的 ChannelHandlerContext,事件可以被传递给当前ChannelHandler 链中的下一个 ChannelHandler。- ChannelInboundHandler
- SimpleChannelInboundHandler< T > T是要处理消息的Java类型,适用于只需要解码消息并处理逻辑
- channelRead0(ChannelHandlerContext,T)
- BootStrap 引导类,用于为应用程序的网络层配置提供了容器
- Bootstrap 用于客户端 ,可以直接.connect来连接服务器,
- ServerBootstrap 用于客户端,需要.connect(new InetSocketAddress) 才能等待客户端的连接
- Channel接口:
- 方法:
- Channel的生命周期:ChannelUnregistered已创建但是没有注册到EventLoop中;已注册ChannelRegistered;ChannelActive处于活跃状态,已经连接到远程节点, ChannelInactive 没有连接到远程给节点。
- 内置的传输
- NIO 非阻塞->基于选择器
- Epoll -> 基于JNI驱动的,速度更快,完全非阻塞的
- 使用:EpollEventLoopGroup 和EpollServerSocketChannel.class
- OIO ->阻塞流
- Local->在VM内部通过管道进行本地传输
- Embedded 测试ChannelHandler使用
- NIO 非阻塞->基于选择器
- ByteBuf
- 使用两个索引:readIndex和writerIndex,开始时,两个都位于开头。也就是队列
- ByteBufHolder接口用来存储各种属性值。
- Netty提供了两种ByteBufAllocator的实现:PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片。后者不池化。
- Unpooled缓冲区:静态工具类来创建未池化的ByteBuf实例
- RPC框架的调用流程
- 解码器:ByteToMessageDecoder 编码器:MessageToByteEncoder实际上这两个实现了Handler
- 协议的支持,自己去看数,Netty实战
EventLoopGroup 事件调度层
- 如何使用自定义线程池,如何确定线程池的参数
EventLoopGroup是一个线程池,负责接受I/O请求并分配线程执行处理请求。
- EventLoopGroup包含多个EventLoop
- EventLoop用于处理Channel生命周期内的所有I/O事件
- EventLoop同一时间只会与一个线程绑定,每个EventLoop处理多个Channel
- Channel在生命周期内可以对EventLoop进行多次绑定和解绑
服务编排层
- ChannelPipeline
负责组装各种ChannelHandler,内部通过双向链表将不同的ChannelHandler链接起来,依次调用,对Channel中的数据进行拦截和处理。
ChannelPipeline是线程安全的,因为一个新的Channel都会绑定一个新的ChannelPipeline,一个ChannelPipeline关联一个EventLoop,然后一个EventLoop绑定一个线程 - ChannelHandler & ChannelHandlerContext
ChannelHandlerContext用于保存ChannelHanler的上下文,可以实现ChannelHandler之间的交互。
同时包含了ChannelHandler生命周期的所有事件。
使用方式:
#todo
- 如何做,搜搜
如果ChannelHandler有一些通用的逻辑需要实现,那么就可以放在这里。
Selector
Netty实现的一些有特色
FastThreadLocal
使用Object数组替代Entry数据,Object[0]
存储的是一个Set<FastThreadLocal<?>>
集合,1以后都是存value的数据,而不是使用键值对的方式实现。
set方法:
- 找到index位置,设置新的value
- 将FastThreadLocal对象保存到待清理的Set中
优点: - 高效查找:可以直接通过数组下标获取,而且扩容不需要进行rehash
- 安全性更高:ThreadLocal有可能会造成内存泄漏,只能等待线程销毁,但是的线程池下只能主动检测,而FastThreadLocal则封装了FastThreadLocalRunnable,任务执行完成之后一定会执行
FastThreadLocal.removeAll()
,从而将Set中所有的对象都销毁。
#todo
- 以下内容是新开的文章《深入浅出Netty:原理与源码解读》记得新开一篇文章
- 看看能不能写个爬虫把这个文章爬下来,或者自己复制下来
曹工杂谈:Spring boot应用,自己动手用Netty替换底层Tomcat容器 - 三国梦回 - 博客园 (cnblogs.com)
00 学好 Netty,是你修炼 Java 内功的必经之路 (lianglianglee.com)
深入和源码解读
线程模型
单线程模型
所有的I/O操作都由一个线程完成,会造成积压
多线程模型
业务逻辑交给多个线程进行处理
主从多线程模型
MainReactor负责处理客户端的连接,SubReactor分配线程池中的线程处理连接生命周期内的所有的I/O模型