文档章节

Netty、t-io、Voovan 框架浅谈

愚民日记
 愚民日记
发布于 2017/06/12 15:35
字数 3699
阅读 5921
收藏 137

声明: 欢迎本着技术讨论为主的讨论者一起探讨。让我们一起共同维护好技术圈的和谐氛围。以下内容均为个人理解,如果不妥的地方请大家指出。如果发现错字,只能麻烦你暂时脑补一下。

    我作为 Voovan 的设计及主创人员,针对目前流行的几款框架在设计和功能方面做一个分析,分析的内容我选取了一些我个人比较关注的性能和开发的便利性有影响的点,当然一定会不够全面的,就像大家关注的点总是不同的,有人喜欢大长腿,有人喜欢大XX。这篇文章中不涉及性能的好坏评价(因为没有对三个框架做全面的横向评测),也不做框架设计优劣的评论,存在及合理。

    首先目的是要在熟悉和了解其他框架的过程中,学习他们好的设计思想和编码技巧,这里我不做结论性的陈述。目的是在于帮助开发者更好的选择适合自己的框架。

    在编写这篇文章时与 t-io 的作者进行了沟通,当然就我们两个人的理解也极有可能有错误的地方,欢迎大家指出错误,我们会尽快修复的。

 

以下是我对三个框架在设计或者说是编码特点中选取的几个我比较关注的点的对比图:

首先我们对几个关键的概念进行一些解析,方便大家更好的理解上面表中的概念:

  • NIO、AIO 的区别?

    在这里我们来看一下两者最明显的区别,NIO 是由 JDK 来处理异步事件的,就是说由 JDK 来探测系统缓冲区及Socket 的连接状态并通知用户事件被触发,最明显的就是编写 NIO 的时候我们需要对 Selector 进行处理,然后自己对事件进行处理,那么这个时候如果不使用线程来处理的话,就是一个同步的通信模型。而 AIO 这是由 JDK 所在的操作系统通知 JDK 事件被触发,这是基于 Linux 的 Epoll 或者 window 的 Iocp 来完成,且事件在被触发时就是在一个独立的线程中处理的。 理论上 AIO 的性能更好,如果传言是真的不知道为什么 Netty 的作者停止了对 Netty5 AIO 版本的维护。

 

  • 事件驱动

   大家都知道异步程序的编写必然伴随着不断的回调,而事件驱动就是将这些回调分类整理统一成不同的事件并出发,暴露给使用者,举个简单的例子,断开连接这个事件,广义的讲有两种情况:服务端主动断开,客户端主动断开,但是对于使用者来说都是断开,都需要出发 close 这个事件,所以框架就需要对这两种事件进行统一,在被触发时给用户一个 close,或者说调用用户的 close 回调。

   模拟事件驱动就是框架中没有建立事件处理模型,在整个框架的编码过程中在代码的不同位置统一事件的处理。

 

  • TCP/SSL

    首先说明一下只有 TCP 协议才能支持 SSL 通信。而 SSL 通信是通过使用非对称的密钥保证通信的内容不会被中间人进行攻击,因此现在众多的支付功能都采用 SSL 通信的形式,而不是很多朋友理解的加密,因为密钥是公开,所以服务端返回给客户端的信息是完全可以被解密。

 

  • 非堆内存及零拷贝
  1. 非堆内存: 首先JVM在管理对象的时候可以使用的有堆内存和非堆内存,堆内存由 JVM 自动管理,大家常见到的 OutOfMemroy 则由于堆内存被完全占用而导致,那么非堆内存不是 JVM 自动管理的,所以理论上可以申请到目前物理内存的最大值,而为什么要使用非对内存呢? 答案是应为 GC, 在高并发的情况下堆内存被不断被申请,当达到 GC 条件时 JVM 会停止响应进行无关联对象的回收,而使用非对内存的时候,由于对象的申请和释放都由开发人员手工实现并完成,所以在临时的某个对象或者缓冲区使用完后就可以进行针对性的释放,同时不占用堆内存,从而减少 GC 的次数,总提上减少 JVM 因 GC 导致的停顿。当然这对开发者也提出了更高的要求。因此理论上使用非堆内存都可以做到并发时的极低的内存消耗。

  2. 零拷贝:首先零拷贝的作用也是在于减少JVM堆内存消耗,主要目的是在从缓冲区接收到数据后对数据的解析或处理后在到达开发者提供的回调函数之前不对其进行 copy 操作 或者 降低 copy 操作,那么什么是 copy 操作呢,比如: HeapByteBuffer.get(byte[]) 方法会对数据进行一次拷贝(使用的是System.arraycopy),而DirectByteBuffer虽然分配的是非堆内存单在做 get 方法时也会将数据 copy 到堆内存中,而这种操作在大多数开发者中是会被频繁使用,所以不知道是哪位大神(具我的了解应当的 Netty 的开发者)提出了零拷贝,拯救了我们。

 

  • 粘包处理与业务分离

    这个可能就是在框架设计时如何给用户提供更好的体验的问题了(当然并不会取得每个人的欢心,有人爱有人恨,世间万物除了 money 皆是如此),目的是在于能够统一的封装粘包处理代码达到清晰且模块化复用的目的,就我个人而言,我非常厌恶将粘包处理代码编写到解包过滤器或者解码器,这样会导致我在开发新的系统或者某些功能是需要复制并不断的粘贴代码或者某个类,所以 voovan 提出了粘包处理与业务分离的方式,将粘包处理部分的代码作为一个独立的可插入的功能提供给开发者,当然如果用户喜欢在协议解析部分处理,只要在构造服务的时候不要注册粘包处理类,就可以在协议解析的部分自己处理了。

     2017-07-01: 关于Netty粘包处理大家有疑问,我在这里补充说明一下 Voovan 的粘包处理的不同:

      Voovan 的粘包处理是个独立的一个实现,和解包处理是分开的,这也就是我们在进行解包处理的时候不用考虑包是否完整,同时也方便丢弃那一部分非法的探测包(不需要读取再丢弃,仅仅操作一下指针即可:直接在canSplite方法中清空并重置bytebuffer就可以做到,无须内存操作)从而提升安全性. Voovan 在包不完整的时候会尝试等待一个完整的包。至于为什么拆分粘包处理为一个独立的逻辑? 个人认为判断包是否完整是一套独立的逻辑,可以写的很简单,独立出来对于相当多的一部分把解包处理和粘包处理用一套逻辑来处理的开发者而言,独立的粘包可以提示他们写一个很简单的逻辑来处理粘包,从而有效的提高判断完整报文运行效率。Voovan也提供透传(默认粘包处理器,即不设置粘包处理), 方便开发者保留自己的习惯在解包时处理粘包。当然最终这一切都取决开发者的选择。最后,并不是说 Netty 不支持粘包处理,粘包处理是无论什么语言所有涉及导到Socket 通信都必须要解决的问题,Netty 是否支持粘包处理,仅仅凭借常识推断就可以知道一定是有的。

  • 异步开发与同步开发

    异步开发与同步开发的本质区别在编码上就是是否使用回调,异步开发需要开发者注册回调的函数,供框架在需要时调用,不会对当前线程产生阻塞,没有阻塞也就意味着Socket 通信的缓冲区中的内容不会因为阻塞而长时间的等待造成并发性能的下降。同步开发则是用户调用 send 或者 recive 时线程是阻塞的必须等到有合规的内容时才会继续接收缓冲区内未处理的数据。

   那么是否是异步就会一定比同步好呢?答案是否定的,异步虽然性能高,但也同时加大了编码和调试的难度,所以我个人推荐仅在提供Socket服务的时候使用,因为我们无法预测并发情况,所以还是做万全的准备比较好。那么同步使用在作为Socket客户端时就有了他得天独后的优势,方便开发且方便调试。

 

  • 心跳及重连

    关于心跳及重连相信我就不做过多的描述了,简单介绍一下:

  1. 心跳:Socket服务端和客户端之间定时发送和应答的内容,用于判断连接是否断开以便通知开发者进行响应的处理。

  2. 重连:主要是指在Socket客户端通过心跳或者 Socket 事件发现连接被断开后自动的重新发起到服务端的连接。

 

  • 什么是 TCP 长连接和短连接?

        个人认为长连接和短连接没有实际本质的区别只是开发者使用场景的不同。

        TCP长连接: 长连接就是一个持续不断的 TCP 连接 ,  主要作用是在一次 connet 后,不断的进行无数组业务单元的信息传递,直到关闭,长连接长时间不断开最少会霸占一个线程进行事件监听,所以过多的客户端容易降低性能。典型场景: 部分IM通信软件,以及数据同步系统。

        TCP短连接:短连接就是在和服务端通信的过程中是一次connect 交互完一组业务单元后连接关闭,下次交互的过程中再建立连接。短连接虽然不会长期霸占一个线程用作监听,但他每次的连接和断开会消耗一定 IO 资源,但好在Socket 通信往往不像磁盘或者内存 IO 操作需要纳秒级的响应。

        就我个人的理解 HTTP1.0、HTTP1.1、HTTP2、网游服务端都是短连接的形式,一组相关的业务单元传输完毕后,等待超时后,就会主动关闭连接。一般 web 服务器的 keepalive 的超时时间都不会设置的太长,而且会根据当前线程的情况自动调整,线程越多超时时间越短,线程越少超时时间越长。

 

关于性能测试:

     并发性能的概念

        QPS(query per second)平均每秒请求数, 如每秒处理请求 10k 。

        BPS (bytes per second)平均每秒传输字节数, 如每秒传输80mb 。

        首先要说的是 QPS,这个是对异步通信框架线程和竞争锁的管理,线程和竞争锁管理的不好会数出现锁阻塞导致系统停止响应,测试 QPS 会导致不断的接受连接申请线程处理业务关闭连接释放线程等操作。调大线程池有助提高 QPS 的测试结果。

        个人对异步框架的测试QPS时的理解是连接->发送->响应->关闭为一个 QPS.因为这样才能相对准确的测试出其基线性能.

        例如:voovan 在 [连接->发送->响应->关闭 为 1 个QPS]  的测试情况下是10000+ 的QPS.

       而增加 keepalive 方式后 voovan 在 [连接->发送->响应->发送->响应->关闭 为 2 个QPS]  的测试情况下是18000+ 的QPS.

        可见不同的QPS 的定义对测试结果有相当大的影响.

        BPS 测试最大吞吐量是对内存和 IO 性能管理的考验,管理的不好的话就会出现频繁的 GC 停顿直到系统无响应或者 OutOfMemroy,测试BPS会不断在发送过程中充满整个Socket 缓冲区,充分启用网卡的能力发送数据。如果是从文件读数据,还要考虑你的磁盘性能。调大缓冲区设置有助于提高 BPS 的测试结果。

        QPS 实测得到的结果可能更适用于多数场景,因为无论是游戏服务端,APP 服务端还是日常的 Web 服务都是客户端数量无法估量,而每次交互过程中的数据量又是非常有限的,多则上百k,少则几 k,几十k,而且大多数应该是几 k,几十 k 的场景,所以网络上很多的测试案例更关注的是 QPS。

        而 BPS 的应用场景多处于有限个节点的大量数据同步,这个场景更关注的是吞吐性能,而非响应情况。

    大家如果自己进行性能测试的时候如果想要测试出某个框架的极限性能需要关注上面两个黑体字所提到的优化内容.因为每个框架对于出厂时的设置是由倾向的,最起码要站在一个相对公平的环境下进行测试。

    OK,写到这里我想要介绍的内容已经介绍完毕了,希望能够帮助大家更好的根据自己需要选择合适的异步通信框架为自己服务。

    欢迎一起探讨学习,如果有错误欢迎指正.

最后请允许我无耻的推广一下 Voovan,试一下说不定正是你想要的呢?

讨论请加入一下 QQ 群。

交流QQ群:454201740

开源协议:Apache v2 License

Voovan开源项目源代码主要托管于 Git@OSC.

Issues地址: Git@OSC

© 著作权归作者所有

共有 人打赏支持
愚民日记
粉丝 69
博文 9
码字总数 14581
作品 4
乌鲁木齐
后端工程师
私信 提问
加载中

评论(43)

发几个动弹也能被禁言-有什么规定吗
看了文章,准备研究下jvm先
乌龟壳
乌龟壳

引用来自“乌龟壳”的评论

感觉你的零拷贝的描述和netty的概念不一致。我试着说下netty的零拷贝。

Netty通过自定义的Buffer那一套类,实现在不进行任何Array.copy操作的前提下,实现创建新的Buffer
1. 多个Buffer零拷贝合并成一个
2. 零拷贝裁剪Buffer
3. 复合场景,零拷贝裁剪多个Buffer零拷贝合并成的Buffer

所以这个零拷贝主要是尽可能减少拷贝操作,和gc没有直接关系,new一个零拷贝的类实际就是个普通对象,还是要gc的,但是gc的只是这个零拷贝对象本身而已。

引用来自“愚民日记”的评论

关于 GC 你的理解可能有偏差, JVM 的 GC 不仅仅是收回对象,更重要的目的是收回给对象分配的堆内存,这也是 JVM GC存在的意义, JVM 判断是否 GC 的时候有一个很重要的条件就是堆内存的水位,如果水位过高是一定会出发 GC 的, 而且多数情况下还是full GC, 而 fullgc 的停顿往往是可以被明显感知到的。
而零拷贝更大的意义在于一份有效数据在内存中只保留一份,这样做的意义就是当传输很大的数据报文或者频繁的交互小报文时不会因为处理粘包或者在解包过程中产生数据的冗余拷贝,导致内存被无效的消耗,从而比零拷贝更快的触碰到堆内存的那根红线,而导致 full GC .而冗余内存的消耗又会导致下次 GC 相比零拷贝提前发生,这样单位时间内没有零拷贝设计的代码触发 GC 的次数就会比采用零拷贝设计代码多,最终导致并发能力下降, 最终 jvm 的 GC 我们是无法避免的。其次对新生代的对象 gc 是经常发生的,并不会造成特别明显的影响.他有一个策略,会将被频繁使用的对象转入导老生代.你可以通过增加 -XX:+PrintGCDetails 这个 JVM 参数观察 gc 的情况。
jvm的gc算法确实还不够好,最终的期待肯定是完全避免这种手工内存管理。之前和t-io讨论的时候也说了,这种做法是基于当前gc算法还不够好的无奈选择。其实编码的时候很不喜欢这种模式,手工管理内存需要人工去操心这些。
愚民日记
愚民日记

引用来自“乌龟壳”的评论

感觉你的零拷贝的描述和netty的概念不一致。我试着说下netty的零拷贝。

Netty通过自定义的Buffer那一套类,实现在不进行任何Array.copy操作的前提下,实现创建新的Buffer
1. 多个Buffer零拷贝合并成一个
2. 零拷贝裁剪Buffer
3. 复合场景,零拷贝裁剪多个Buffer零拷贝合并成的Buffer

所以这个零拷贝主要是尽可能减少拷贝操作,和gc没有直接关系,new一个零拷贝的类实际就是个普通对象,还是要gc的,但是gc的只是这个零拷贝对象本身而已。
关于 GC 你的理解可能有偏差, JVM 的 GC 不仅仅是收回对象,更重要的目的是收回给对象分配的堆内存,这也是 JVM GC存在的意义, JVM 判断是否 GC 的时候有一个很重要的条件就是堆内存的水位,如果水位过高是一定会出发 GC 的, 而且多数情况下还是full GC, 而 fullgc 的停顿往往是可以被明显感知到的。
而零拷贝更大的意义在于一份有效数据在内存中只保留一份,这样做的意义就是当传输很大的数据报文或者频繁的交互小报文时不会因为处理粘包或者在解包过程中产生数据的冗余拷贝,导致内存被无效的消耗,从而比零拷贝更快的触碰到堆内存的那根红线,而导致 full GC .而冗余内存的消耗又会导致下次 GC 相比零拷贝提前发生,这样单位时间内没有零拷贝设计的代码触发 GC 的次数就会比采用零拷贝设计代码多,最终导致并发能力下降, 最终 jvm 的 GC 我们是无法避免的。其次对新生代的对象 gc 是经常发生的,并不会造成特别明显的影响.他有一个策略,会将被频繁使用的对象转入导老生代.你可以通过增加 -XX:+PrintGCDetails 这个 JVM 参数观察 gc 的情况。
愚民日记
愚民日记

引用来自“beykery”的评论

引用来自“愚民日记”的评论

引用来自“文敦复”的评论

引用来自“愚民日记”的评论

引用来自“文敦复”的评论

netty不支持粘包处理,解释下?????

没说netty不支持粘包处理啊,谁说的?

你看你的图!打了x
是粘包处理和业务分离,和是否支持粘包处理是两个概念

请查看netty的encoder和decoder
关于粘包处理这个之前已经做过解释了,你的留言也说明了 Netty 的粘包处理实在 decoder 中实现的和解包的逻辑在一起,Voovan 的粘包处理是个独立的一个实现,和解包处理是分开的,这也就是我们在进行解包处理的时候不用考虑包是否完整,同时也方便丢弃那一部分非法的探测包,提升安全性,Voovan 在包不完整的时候会尝试等待一个完整的包,至于为什么拆分粘包处理为一个独立的逻辑? 个人认为判断包是否完整是一套独立的逻辑,可以写的很简单,独立出来对于相当多的一部分把解包处理和粘包处理用一套逻辑来处理的开发者而言,独立的粘包可以提示他们写一个很简单的逻辑来处理粘包,从而有效的提高运行效率,当然最终这一切都取决开发者的选择。最后,并不是说 Netty 不支持粘包处理,粘包处理是无论什么语言所有涉及导 Socket 通信都必须要解决的问题,Netty 是否支持粘包处理,仅仅凭借常识推断就可以知道一定是有的
沧海一刀
沧海一刀
愚民日记
愚民日记

引用来自“styleman”的评论

c/c++ 后端 笑而不语,虽然我也封装过java的网络模块。总结的还是ok,自己编写必须解决的坑。

是啊,Voovan整个开发过程确实是伴随着坑连坑的节奏~
大賢者
大賢者
c/c++ 后端 笑而不语,虽然我也封装过java的网络模块。总结的还是ok,自己编写必须解决的坑。
情郎i
情郎i
支持,学习了
beykery
beykery

引用来自“愚民日记”的评论

引用来自“文敦复”的评论

引用来自“愚民日记”的评论

引用来自“文敦复”的评论

netty不支持粘包处理,解释下?????

没说netty不支持粘包处理啊,谁说的?

你看你的图!打了x
是粘包处理和业务分离,和是否支持粘包处理是两个概念

请查看netty的encoder和decoder
愚民日记
愚民日记

引用来自“安西都护府首席程序员”的评论

1. NIO是非阻塞IO,Selector和NIO的关系并不是很紧密。离开Selector Socket一样可以读写,只是不知道能不能成功读写。Selector要做的只是把注册了事件的链路在有事件触发之后 加入到一个集合里面。当Selector.select()的时候就会返回这个集合。
2. epoll并不是AIO,netty里面同样可以使用epoll。只要在Epoll.isAvailable 返回true的时候就可以使用EpollEventLoopGroup。

3.零拷贝另外一个发生是在文件传输的时候,文件可以取得Filechannel 直接transto 到socketchannel ,不经过JVM进程。

epoll和aio本质上是没有关系的,epoll是Linux下多路复用IO接口select/poll的增强版本。其实现和使用方式与select/poll有很多不同,epoll通过一组函数来完成有关任务,而不是一个函数。,零拷贝我确实讲的有些片面了,他应该有更多的适用场景~
Voovan v2.0.2,高性能异步通信框架和工具库

VOOVAN 开源项目介绍 Voovan 是一个高性能异步网络框架和 HTTP 服务器框架,同时支持 HTTP 采集、动态编译支持、数据库访问封装以及 DateTime、String、Log、反射、对象工具、流操作、文件操...

愚民日记
2017/06/30
1K
12
t-io 1.7.1 发布:不仅仅是百万级 TCP 长连接框架

t-io 目标提升 不仅仅是百万级TCP长连接框架,这是t-io的第三个目标了,前两个都已实现。 目标先定下来,逐步实现,当年吹过的牛,大都含着泪完成了 要往短连接方面扩展 支持更多的传输层协议...

talent-tan
2017/07/03
6.6K
67
Voovan 参照 Jetty 的性能测试,中国框架一样很优秀

Voovan是一个综合框架,当然也有他的主打内容,今天,对就是今天,在这里给各位看官汇报一下他的并发测试能力. 测试对象:Voovan WebServer / Jetty9 测试工具: Apache bench (ab) Voovan WebSer...

愚民日记
2017/01/04
1K
2
Voovan V1.0 (Beta.3.2) ,增加 UDP 异步通信

Voovan V1.0 (Beta.3.2) 发布了,增加 UDP 异步通信。 主要更新内容: 增加 UDP 异步通信支持 HttpServer 增加 命令参数,以便在 Docker 环境中部署可以灵活配置 反射工具类优化,mapToObject ...

愚民日记
2016/12/01
1K
4
Voovan v1.0.beta.1 文档丰富/性能优异

Voovan 开源框架V1.0.beta.1发布 Voovan 框架和 Netty 以及 Mina 框架类似,同样是提供了异步通信的支持,但相比的不同是 Voovan 框架采用 Aio 和 Nio 模型,框架内部解决了日常开发一些常遇...

愚民日记
2016/09/07
828
0

没有更多内容

加载失败,请刷新页面

加载更多

《碎玉投珠》的读后感想法心得范文3800字

《碎玉投珠》的读后感想法心得范文3800字: 《碎玉投珠》是晋江作者北南2018年的作品,内容主要讲述了其17年的《两小无嫌猜》中副cp师父师叔的爱情故事。 个人并没有看过北南其他的作品,这篇...

原创小博客
28分钟前
0
0
Confluence 6 文档主题合并问答

在 Confluence 官方 前期发布的消息 中,文档主题在 Confluence 6.0 及其后续版本中已经不可用。我们知道你可能对这个有很多好好奇的问题,因此我们在这里设置了一个问答用于帮助你将这个主题...

honeymose
40分钟前
2
0
java框架学习日志-2

上篇文章(java框架学习日志-1)虽然跟着写了例子,也理解为什么这么写,但是有个疑问,为什么叫控制反转?控制的是什么?反转又是什么? 控制其实就是控制对象的创建。 反转与正转对应,正转...

白话
今天
6
0
Integer使用双等号比较会发生什么

话不多说,根据以下程序运行,打印的结果为什么不同? Integer a = 100;Integer b = 100;System.out.println(a == b);//print : trueInteger a = 200;Integer b = 200;System.out.pr...

兜兜毛毛
昨天
11
0
CockroachDB

百度云上的CockroachDB 云数据库 帮助文档 > 产品文档 > CockroachDB 云数据库 > 产品描述 开源NewSQL – CockroachDB在百度内部的应用与实践 嘉宾演讲视频及PPT回顾:http://suo.im/5bnORh ...

miaojiangmin
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部