netty笔记---各个组件的理解

EventLoopGroup

  1. 一个EventLoopGroup当中会包含一个或多个EventLoop。
  2. 一个EventLoop在它的整个生命周期当中都只会与唯一一个Thread进行绑定。
  3. 所有由EventLoop所处理的各种I/O事件都将在它所关联的那个Thread上进行处理。
  4. 一个Channel在它的整个生命周期中只会注册在一个EventLoop上。
  5. 一个EventLoop在运行过程当中,会被分配给一个或者多个Channel。

重要结论

  • 在netty中,Channel的实现一定是线程安全的;基于此,我们可以储存一个Channel的引用,并且在需要向远程端点发送数据时,通过这个引用来调用Channel相应的方法;即便当时有很多线程同时在使用它也不会出现线程问题;而且,消息一定会按照顺序发送出去的。
  • 我们在业务开发中,不要将长时间执行的耗时任务放到EventLoop的执行队列中,因为它将会一直阻塞改线程所对应的所有Channel上的其他执行任务,如果我们需要进行阻塞调用耗时操作(实际开发中很常见),那么我们就需要使用一个专门的EventExecutor(业务线程池)。

通常会有两种实现方式:

  1. 在ChannelHandler的回调方法中,使用自己的业务线程池,这样就可以实现异步调用。
  2. 借助于netty提供的向ChannelPipeline添加ChannelHandler时调用的addLast方法来传递EventExecutor。

说明

默认情况下调用addLast(handler),ChannelHandler中的回调方法都是由I/O线程所执行,如果调用了ChannelPipeline.addLast(EventExecutorGroup group,ChannelHandler… handlers)方法,那么ChannelHandler中的回调方法就是由参数中的group线程组来执行。

Future

JDK所提供的Future只能通过手工方式检查执行结果,而这个操作是会阻塞的;Netty则对ChannelFuture进行了增强,通过ChannelFutureListener以回调的方式来获取执行结果,去除了手工检查阻塞的操作;值得注意的是:ChannelFutureListener的operationComplete方法是I/O线程执行的,因此要注意的是不要在这里执行耗时的操作,否则需要通过另外的线程或者线程池来执行。

ChannelHandler与ChannelHandlerContext

在netty中有两种发送消息的方式,可以直接是写到Channel中,也可以写到与ChannelHandler所关联的那个ChannelHandlerContext中,对于前一种方式来说,消息会从ChannelPipeline的末尾开始流动;对于后一种方式来说,消息将从ChannelPipleline中的下一个ChannelHandler开始流动。

结论

  1. ChannelHandlerContext与ChannelHandler之间的关联绑定的关系但是永远都不会发生改变的,因此对其进行缓存是没有任何问题的。
  2. 对于与Channel的同名方法来说,ChannelHandlerContext的方法会产生更短的事件流,所以我们应该在可能的情况下利用这这个特性来提升应用性能。

NIO

使用NIO进行文件读取所涉及的步骤:
3. 从FileInputStream对象获取到Channel对象。
4. 创建Buffer。
5. 将数据从Channel中读取到Buffer对象中。
0<=mark<=position<=limit<=capacity

flip()方法

  1. 将limit值设为当前的position。
  2. 将position设为0。.

clear()方法

  1. 将limit值设为capacity
  2. 将position的值设为0.

compact()方法。

  1. 将所有未读的数据复制到buffer起始位置处。
  2. 将position设为最后一个未读元素的后面。
  3. 将limit设为capacity
  4. 现在buffer就准备好了,但是不会覆盖未读的数据。

注意
通过索引来访问Byte时并不会改变真实的读索引;我么可以通过ByteBuf的readerIndex()与writeIndex()方法分别直接修改读索引与写索引。

缓冲区

Netty所提供的3种缓冲区类型

  1. heap buffer
  2. direct buffer
  3. composite buffer

Heap Buffer(堆缓冲区)

这是最常用的类型,ByteBuf将数据储存到JVM的堆空间中,并且将实际的数据存放到byte array中来实现。

优点

由于数据是储存在JEM的堆中,因此可以快速的创建与快速地释放,并且它提供了直接访问字节数组地方法。

缺点

每次读写数据时,都需要先将数据复制到直接缓冲区中再进行网络传输。

Diect Buffer(直接缓冲区)

在堆之外直接分配内存空间,直接缓冲区并不会占用堆的容量空间,因为它是由操作系统在本地内存

优点

在使用Socket经行数据传递的时候,性能非常好,因为数据直接位于操作系统的本地内存中,所以不需要从JVM将数据复制到直接缓冲区中,性能很好。

缺点

因为Direct Buffer是直接在操作系统的内同中的,所以内存空间的分配与释放都要比堆内存更加复杂,而且速度要慢一些。
Netty通过提供内存池来解决这个问题。

重点

对于后端的业务消息的编解码来说,推荐使用HeapByteBuf;对于I/O通信线程在读写缓冲区时,推荐使用DirectByteBuf。

Composite Buffer(复合缓冲区)

JDK的ByteBuffer与Netty之间的差异对比

  1. Netty的ByteBuf采用了读写索引分离的策略(readerIndex与writeIndex),一个初始化(里面尚未由任何数据)的ByteBuf的readerIndex与writeIndex值都为0.
  2. 当读索引与写索引处同一个位置的时候,如果我们继续读取,那么就会抛出IndexOutOfBoundsException。
  3. 对于ByteBuf的任何读写操作都会分别单独维护读索引与写索引。maxCapacity最大的容量默认的限制就是Integer.MAX_VALUE。

JDK中的ByteBuffer的缺点

  1. final byte[] hb;这是JDK的ByteBuffer对象中用于储存数据的对象声明;可以看到,其字节数组是被声明为final的,也就是长度是固定不变的。一旦分配好后不能动态扩容与收缩;而且当待存储的数据字很大时很有可能出现IndexOutOfBoundsException,如果要预防这个异常,那就需要在存储之前完全确定好待存储的字节大小。如果ByteBuffer的空间不足,我们只有一种解决方案:创建一个全新的ByteBuffer对象,然后再将之前的ByteBuffer对象中的数据复制过去,这一切操作都需要由开发者自己来手动完成。
  2. ByteBuffer只使用了一个position指针来标识位置信息,在进行读写切换时就需要flip方法或是rewind方法,使用起来很方便。

Netty的ByteBuf的优点:

  1. 存储字节的数组是动态的,其最大默认值是Integer.MAX_VALUE。这里的动态性是体现write方法中的,write方法在执行时会判断buffer容量,如果不足则自动扩容。
  2. ByteBuf的读写索引是完全分开的,使用起来很方便。

自旋锁

AtomicIntegerFieldUpdater要点总结

  1. 更新器的类型必须是int类型的变量,不能是其包装类型。
  2. 更新器更新的必须是volatile类型的变量,确保线程之间的共享变量时的立即可见性。
  3. 变量不能是static的,必须要是实例变量。因为Unsafe.objectFieldOffset()方法不支持静态变量(CAS操作本质上是通过对象实例的偏移量来直接进行赋值)。
  4. 更新器只能修改它可见范围内的变量,因为更新器是通过放射来得到这个变量的,如果变量不可见就会报错。

如果要更新的变量是包装类型,那么可以使用AtomicReferenceFieldUpdater来进行更新。

Netty处理器重要概念

  1. Netty的处理器可以分为两类:入站处理器与出站处理器。
  2. 入战处理器的顶层是ChannelInboundHandler,出站处理器的顶层是ChannelOutBoundHandler。
  3. 数据处理时常用的各种编码器本质上时处理器。
  4. 编解码器:无论我们向网络中写入的数据是什么类型(int、char、String、二进制),数据在网络中传递时,其都是以字节流的形式呈现的;将数据由原本的形式转换为字节流的操作称为编码(encode),将数据由字节转换成它本来的格式或者其他格式操作称为解码(decode),编解码同意称为codec。
  5. 编码:本质上是一种出站处理器;因此,编码一定是一种ChannelOutBoundHandler。
  6. 解码:本质上是一种入站处理器;因此,解码一定是一种ChannelInBoundHandler.
  7. 在Netty中,编解码通常以XXXEncoder命名;编码器通常以XXXDecoder命名。

结论

  1. 无论是编码器还是解码器,其所接收的消息类型必须要与待处理的参数类型一致,否则该编码器并不会被执行。
  2. 在编码器进行数据解码时,一定要记着判断缓冲区(ByteBuf)中的数据是否充足,否则将会产生一些问题。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值