netty学习(二)Netty线程模型与核心组件

netty学习(二)Netty线程模型与核心组件

1.I/O线程模型

1.1 传统阻塞IO模型

传统阻塞IO模型就是每一个请求都需要一个线程来进行处理,这个处理包括两方面:

  1. 连接的处理
  2. 业务的处理

有如下问题:

  1. 当请求过多时,创建的线程也会过多,那么线程的切换也会很频繁,上下文处理造成资源浪费
  2. 操作系统能处理的线程是有限制的,当线程达到限制后,新的请求就会被阻塞
  3. 单个线程大部分时间都是阻塞的,因为此请求并没有数据可以读写,造成了资源浪费

1.2 Reactor模型

针对传统IO模型的问题:reactor模型有如下解决方案:

  1. 使用IO复用模型,使得一个线程可以处理多个请求
  2. 基于线程池来复用线程资源,减少线程的创建造成的资源浪费,同时可以达到负载均衡的效果

在这里插入图片描述

如图,reactor模式将请求的连接处理和业务处理分开,使得只有一个ServiceHandler对象进行阻塞,当有线程的业务需要处理时,才从线程池里取线程进行业务处理,这样就大大减少了线程的阻塞。

Reactor中包含两大核心组件:

  1. reactor:即上图中的ServiceHandler,负责监听和分发事件(阻塞)
  2. Handler:即上图中的EventHandler,负责具体的业务处理(非阻塞)
1.2.1 单Reactor单线程

在这里插入图片描述

单 reactor单线程即将所有的处理交给一个线程,通过多路复用处理所有的IO操作(就是之前的NIO),优点是编码比较简单(只有一个线程),缺点是单线程无法处理高并发(在handler处理一个连接的业务时无法处理其他事件),且当线程以外终止会导致整个线程不可用,可靠性不高。

1.2.2 单Reactor多线程

在这里插入图片描述

单reactor多线程即每一个handler只负责事件的读取和返回,而具体的业务处理则交给woker线程处理,优点时充分利用了多核处理器的性能(采用多线程),缺点是虽然并发量比单线程高很多,但是reactor仍存在性能瓶颈(reactor仍在一个线程中,handler的数量很多)

1.2.3 主从Reactor多线程

在这里插入图片描述

所以就再分咯,建立一个reactor主线程和多个reactor子线程,主线程只负责连接的建立和handler的分发,将handler分配给子线程进行处理,这样就不会存在单线程下的性能瓶颈。

1.3 Netty线程模型

在这里插入图片描述

netty线程模式是基于主从reactor多线程模型的,我们直接来捋一下流程

  1. netty中抽象出了两组线程池,BossGroup专门负责客户端的连接,WokerGroup专门负责读写
  2. BossGroup中的每一个线程中都包含了一个selector选择器和一个任务队列,然后进入一个accept事件循环中
  3. accept事件循环包含三个步骤,a. 调用select得到所有待处理的accept请求 b. 将这些待处理的accept请求注册到WorkerGroup中的线程的selector中 c. 处理任务队列的任务
  4. WorkerGroup中的线程与BossGroup中线程的流程一致,但监听的是read/write事件,且step 2会将读写任务交予对应的Channel处理器处理
  5. 每一个WokerGroup中的线程处理业务时会使用pipeline,其中包含了多个channel处理器

2. Netty核心组件

2.1 事件的接收

2.1.1 channel接口

channe时javaNIO中的组件(前面已经讲过了),它代表了一个连接事件的载体(一个文件FileChannel、一个套接字SocketChannel等)

2.1.2 EventLoop接口

EventLoopGroup中包含了多个EventLoop,以达到多线程处理的效果。

EventLoop处理了多个连接生命周期中所发生的事件,而且每一个EventLoop只能绑定一个线程。

这就好比与我们学习NIO中时的主线程,其中也包含了两个组件:selector和TaskQueue。selector即之前学习的选择器,TaskQueue里可以存放我们需要异步处理的任务,后面会详细讲解。

2.1.3 ChannelFuture接口

在Netty中,所有的IO操作都是异步的,Netty提供了ChannelFuture接口让我们很方便的执行异步操作,该接口有addListener方法和removeListener方法用于添加和删除监听者,当操作完成时(不论成功还是失败)都会给监听者发送通知。

2.2 事件的处理

2.2.1 ChannelHandler接口

ChannelHandler是一个由事件触发的处理应用程序逻辑的容器,我们在编写代码时大部分的业务逻辑都在这里完成,最为经常使用的是ChannelInboundHandler子接口,该接口接收和处理入站事件和数据,也可以冲刷数据发送给客户端,这点在后面的实战中会大量使用。

2.2.2 ChannelPipeline接口

pipeline管道即一个操作的流程,所以在ChannelPipeline中就包含了多个ChannelHandler事件流,他们的执行顺序由添加顺序所决定。

2.3 数据容器ByteBuf

还记得我们使用NIO时使用的数据容器ByteBuffer吗,该容器是一个双向的读写容器,可以与channel进行数据交互。

而在netty中,实现了一个ByteBuf类来代替ByteBuffer,ByteBuf中提供了大量的API供我们使用,且数据结构的不同也使得效率较高。

2.3.1 数据结构的区别

在ByteBuffer中,实现双向读写的方式是flip方法,每一次切换时,都要调用一次flip方法来将指针进行调整。

而在ByteBuf中的数据结构是这样的:

*<pre>
*      +-------------------+------------------+------------------+
*      | discardable bytes |  readable bytes  |  writable bytes  |
*      |                   |     (CONTENT)    |                  |
*      +-------------------+------------------+------------------+
*      |                   |                  |                  |
*      0      <=      readerIndex   <=   writerIndex    <=    capacity
* </pre>

ByteBuf分别为读和写都设置了一定的区域,这样就避免了切换。

ByteBuf允许我们像使用字节数组一样来对其进行使用。

2.4 编解码之Protobuf

在netty中自带了很多的编解码器用于在网络中进行传输,比如常用的StringEncode/StringDecode(对字符串进行编解码)和ObjectEncode/ObjectDecode(对java对象进行编解码)。但是Object编解码器存在如下问题:1.无法跨语言,只能是java对象 2.使用的是java的序列化技术,效率不高

Protobuf是Google发布的开源项目,是一种轻便级高效的结构化数据存储格式,可以用于结构化数据序列化。很适合做数据存储或者RPC的数据交换格式。

ProtoBuf支持跨平台跨语言(支持c++、java、python、go等)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值