前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >换个角度聊聊Netty

换个角度聊聊Netty

作者头像
春哥大魔王
发布2019-11-09 19:45:47
8210
发布2019-11-09 19:45:47
举报

Netty3

Netty3出现了太多的内存垃圾,创建了过多对象,在大的服务端压力下会表现比较糟糕,做了太多的内存拷贝,在堆上创建对象,堆缓冲区,当往socket写内容时就需要做内存拷贝,拷贝到直接内存,然后交给socket所以做了太多内存拷贝。

Netty3也没有一个好的内存池,写一个高性能的网络程序,大多时候会想使用本地内存,比如直接内存,创建和销毁直接内存是很耗性能的。

Netty3没有对linux进行优化,java对nio抽象接口这样可以在windows和osx上可以工作,但是在制定操作系统上做一些优化是比较难的,因为它是基于java的nio实现的。

Netty3的线程模型也不合理,每次要从socket中读取数据时,由于使用的是异步模式,所以有一个线程运行在一个eventloop中,当一些socket或文件描述符就绪时,我们从中读取数据然后传递到pipline,在pipline中可以进行数据处理和转换操作,当写数据时会从pipline再走一遍,直到走到socket,然后经过系统网络进入网络层,再到内核空间等。这里的问题是inbound数据处理是在一个eventloop里,一直是同一个线程。但outbound上写数据却不再这样,outbound处理数据始终处于调用线程里,这样难以理解到底是哪个线程在操作哪个数据和流程。

Netty4

  • Netty4产生更少的内存垃圾,意味着垃圾回收不必频繁工作
  • Netty4对linux传输层进行了优化使用了jni实现
  • Netty4还有一个高性能的Buffer Pool,用于直接内存

Netty4还有一个好用的线程池模型,inbound和outbound都发生在同一个线程内,这样就不用关心同步问题。

Channel

Channel是对socket的抽象,是双向操作的可以做一个写操作数据会走到socket,然后调用write系统操作把数据发送出去,如果使用TCP channel就相当于一个连接,每个channel对应一个channelpipline,channelpipline是一个包含不同channelhandler的双向链表,channelhandler可以处理inbound和outbound事件。

在Inbound每次读取一些数据,一个新的链接被接受,从pipline头部开始每个channelhandler可以对事件做一些处理,比如记日志,byte转pojo。

在做outbound时比如写操作,从pipline尾部开始,做系统调用之前你可以做一些拦截器模式实现。

Netty中使用链式过滤器,一个http编解码器其实是一个channelinboundhandler+channeloutboundhandler

Netty3中每个网络事件都是一个POJO对象,这样看起来比较简单,只需要传递POJO对象,通过instanceOf判断消息事件是连接事件还是读写事件,问题是在每次网络事件都建立一个POJO对象在加上一些数据转换操作,在更多连接建立时,这些都成为了负担。

同时也妨碍了JVM进行JIT优化,因为总是调用同一个方法来处理不同的POJO,做各种instanceof检查,这些对象可能有着复杂的结构,这是不友好的。

Netty4为了解决这个问题,引入了一个非常轻量级的对象池,除非真正需要,否则不要把所有对象都池化。但在Netty中有大量一直被重复使用的对象,这些对象被限制在同一个线程里使用,可以将他们缓存起来之后在重用他。

之前Netty3中进行POJO操作所调用的方法,都替换为直接方法调用减少了对象传递。

在从网络读取数据时 channelRead方法被调用,每次新连接建立时 channelActive方法被调用,当文件描述符被关闭时,channelInactive方法调用,这样可以节省一小部分对象。

有些事情只能靠使用JNI完成,比如NIO,在Netty中有很好的线程池模型,可以知道哪个线程在一个socket上会调用什么,可以直接获取内存地址,并进行传递,这样比传递对象更轻。

EventloopGroup类似一个线程池,有很多线程用来分配给处理不同的channel上的事件。

如果想让系统处理更多的socket,只使用一个线程会成为性能瓶颈,因为你不能快的接受连接,然后SO_BACKLOG队列会变长直到超时,我们可以起多个线程在同一端口处理不同连接。

Buffer

JDK的ByteBuffer不够友好,只有一个Index地址,如果想读取一个刚好填充好的Buffer,需要先flip一下,以便把index地址重新设置到0的位置,然后才能读取,这个操作很容易被忘记。

在Netty中,读写有着不同的index,不再需要flip操作,如果写则写index递增,如果读则读index递增。一旦两个index指向同一个地址,代表就不能再读取数据了。

JDK的ByteBuffer中,如果想把多个ByteBuffer组合起来,只能把多个ByteBuffer传递到一个ByteBuffer数组里,需要自己遍历。

Netty中提供了CompositeByteBuf类,允许把多个ByteBuffer组合到一起。

如果想要往Socket里写数据,需要先有一个非堆内存的Direct buffer,这个Direct buffer时需要释放的,否则就会造成内存不足,我们无法保证能够及时对直接内存进行回收。

在申请创建直接内存时,通过一个静态同步计数方法,在超出配置的最大大小的时候会抛出内存不足异常或错误。因为是静态同步方法,如果很多线程一起创建直接内存,就会产生大量阻塞,这就很糟糕。程序会暂停100毫秒在创建对象,调用system.gc告诉垃圾回收器该运行了,也就是说有了100毫秒的停顿,对于延迟敏感的程序很糟糕。

在Netty中创建一个bytebuffer,计数器就会加1,当结束的时候调用release方法,计数器就会减1。当计数器为0时,代表可以销毁它了。

解决这种问题Netty使用了内存池,在计数器为0的时候,将它放回到内存池,这样就不会再进入到静态同步方法了,但仍可以在内存池中保留它。创建池化的直接内存比创建非池化的直接内存快3倍。

Netty中提供了内存溢出检测器,比如在每次创建和释放内存的地方进行检测。

线程模型

Netty4中,一个channel被绑定到一个IO Thread上后绑定关系不再改变,这样的好处是所有操作会一直处于同一个线程内。

这样省去了类似volatile等线程可见性机制,可以不关心同步问题。IO线程驱动了inbound handler和outbound handler里的事件。如果一个handler是被共享的,需要保证线程安全了。如果handler只被一个channel使用就不需要关系同步问题了。

Eventloop线程其实就是java里的executor,netty会确保执行write/read线程是否是当前的eventloop线程。

在Netty3中,每次调用channelWrite都会调用socket write操作,但每次channel write的时候数据通常都比较小,这样可能调用多次write方法,系统调用是昂贵的,这样会很糟糕,因为java会调用JNI的C语言逻辑,C会进行系统调用,之后从用户空间进入到内核空间。

在Netty4对write和flus进行了拆分,对于outputstream每次调用write不会进入到socket,你需要调用flush才可以将outbound缓冲区所有数据写入到socket,当数据进来之后,调用channelWrite进行回应,在channel没有数据可读时调用channelReadComplete方法,这个方法触发channelFlush方法,之前写入缓存的数据会在一个系统调用里完成发送。

为解决每次调用Flush造成占用内存过多的情况,引入了背压机制对应的是channelWriteAblilityChange事件。

每次channel从可写变成不可写或反过来时,都会发送一个事件,这样可以检查channel是否可写了。可写则继续写,不可写了调用flush,这样可以对pipline连在一起的两个socket建立背压机制。

Netty3里,每次读事件来都会调用read,在Netty4中,每次有请求读取对象时,就调用channelRead方法,在订阅者无法在接受数据时停止读取。这也是基于Netty搞得Reactive Stream实现的方式。

IO线程

是用来做IO操作和事件处理的。他的抽象是eventloop。

有的时候我们需要调用一些不支持非阻塞API,比如JDBC或者文件系统等。在建立一个pipline之后里面会有多个handler处理不同任务。

内存伪共享

你有两个内存地址,他们处在同一个Cache Line里,而你通过不同线程访问他们。

因为不同线程在使用相同cache line,他们之间通过ping-pong机制来更新值,因为你需要刷新内存里的值,这对性能影响很大。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 春哥talk 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档