估计很快就要被拍砖然后修改,因此转载请保持原文链接,否则视为侵权...

http://calvin1978.blogcn.com/articles/netty-performance.html

前文再续,书接上一回:Netty高性能编程备忘录(上),想不到这次这么快就写了下集,把坑填了。

3. 内存篇

3.1 堆外内存池

堆外内存是Netty被说的最多的部分,网卡内核态与应用用户态之间零复制啊,无GC啊,不受堆内存大小限制啊,不重复。
内存池的算法也是Netty骄傲的地方(注意,在4.0的刚开始版本这也是经常改的)
Norman Maurer说,只有在输出时要需要编码对象直接操作bytes[]时,才有可能用回Heap Buffer。

3.1.1 ByteBuf释放

各种异常处理,一不留神,我的踩坑之作:Netty之有效规避内存泄漏

建议写足够的单元测试,在测试里将内存泄漏检查级别开到最高,然后每个用例执行完就System.gc()一次,同时加入一个测试用的appender监控Netty的logger有没有输出memory leak信息。
如果信心已足,在生产环境里,就可以加上"-Dio.netty.leakDetectionLevel=disabled”把检测关掉,提高那么点点理论上存在的性能。

3.1.2 Recycler

Netty的另一个得意设计是对象可以在线程内无锁的被回收重用。但有些场景里,某线程只创建对象,要到另一个线程里释放,一不小心,你就会发现应用缓缓变慢,heap dump时看到好多RecyleHandler对象。所以这块的设计其实在4.0.x的各个版本里变动了无数遍,貌似4.0.40版才终于在我的测试里不再泄漏了。

但有时觉得这么辛苦的重用一个对象(而不是ByteBuffer内存本身),不如干脆禁止掉这个功能,所以4.0.0.33里,我会在启动参数里加入 -Dio.netty.recycler.maxCapacity.default=0。无语的是,也几乎从这个版本开始,才能通过设为0禁止它。

3.2 避免复制:CompositeByteBuff, slice(), duplicate()

尽量,尽量不要进行ByteBuf内容复制。

场景1: 为了失败时重试,我要保留内容稍后使用,不想Netty在发送完毕后把内容释放了,最笨的方法是用copy()复制一个新的ByteBuf。

Bytebuf newBuf = oldBuf.duplicate().retain();

而上句只是复制出独立的读写Index, 而底下的ByteBuffer是共享的,同时将ByteBuffer的计数器+1.

场景2: 在Proxy型的应用里,输入输出的内容不变,只替换一些头信息。

聪明的做法是,用slice().retain()语句从旧的ByteBuf中切割出Header外的Body部分,同样是共享底层ByteBuffer。然后生成一个新的Header,然后用CompositeByteBuff,将新的Header 与 旧的Body拼接起来。

3.3 避免扩容: ByteBuf的大小预估与AdaptiveRecvByteBufAllocator

ByteBuf如果一开始申请的不足,到极限时会智能的扩容,但也和Java一样,需要重新申请两倍的内存,然后把旧的内容复制过去,一听就是个很消耗的动作,因此,反正是堆外内存池,一开始还是给多一点吧。

另一个有趣的思路是Netty的自适应算法。Netty收到一个请求时,什么都不知道啊,那会申请多大的内存来接收它呢?在Bootstrap里可以配置,默认是 AdaptiveRecvByteBufAllocator,根据每一次收到的请求动态变化。

那如果一个应用有几个不同接口,请求的大小变来变去,会不会玩死它呢?好像会的。不过服务化体系里的特征都是请求小,返回大,请求包的大小变化不会太剧烈。

3.4 烦人的rangeChecking

Norman Maurer说,如果你要搜索某个Byte是否存在,请用 byteBuf.forEachByte(ByteProcessor processor), 比循环的遍历地调用byteBuf.readByte()要快得多。原因无它,ByteBuf有Java其他集合同样的rangeChecking。

每次readByte()都不是读一个字节这么简单,首先要判断refCnt()>0,然后再做范围检查防止越界。getByte(i=int)更加一层又一层的检查函数,JVM没有帮你内联或者Profiler工具阻止了你的内联的话,够呛。

3.5 readInt(),不要readBytes(bytes[],0,4)

比如Thrift,它会做一层封装,先用byteBuf.readBytes(bytes,0,4)读取byte[],再自己转成int。

但实测证明,我将所有的读写short, int, long, boolean, byte的函数,改造为直接使用Netty的原生函数时,性能从7万QPS提升到7.4万QPS,而消耗CPU不变。

3.6 对String说不的 ASCIIString

Netty收到的bytes[],大部分时候最终都要变回String。但String的内部是char[]啊,出入都要经过CharsetEncoder进行转换成byte[],既浪费CPU,又浪费内存。

ByteBufUtils类提供了写入UTF-8和ASCII的优化,不需要从String创建并编码一个bytes[]再开始写入ByteBuf,而是遍历一个个char,当场编码当场写入。可惜此优化对于thrift这种需要先得到byte[]长度的编码器无效。

而Netty 4.1开始,提供了实现 CharSequence接口的ASCIIString。原理就是,String要存char[],是因为UTF-8这样的不定长Encoder,会把char转成1~3个byte。但如果我的Header的名称与某些值,肯定是ASCII字符时(比如服务名,服务版本),那一个char只对应一个byte啊,那你直接在构造函数里把byte[]交给我内部存起来就行了啊,不需要任何转换啊,不费CPU又不废内存了啊。

4. 工具类篇

Netty 为了高效编程,或写或借,搞了一些高效的工具类,在自己的应用里同样可以借用一下。

Netty自己有一篇Using as a generic library,介绍了其中的一些。本文主要介绍与性能相关的。

4.1 FastThreadLocal

Netty威武,居然太岁头上动土,搞出个比JDK的ThreadLocal还快的ThreadLocal。详见《Netty精粹之设计更快的ThreadLocal》

JDK的ThreadLocal,实现原理是Thread对象里有个HashMap性质的数组,每个ThreadLocal的id是个Hashcode,算法是currentValue+0x61c88647,hashCode取模数组大小得到threadLocal存的位置,如果桶里已有其他元素,key.nextHashCode()找下一个桶,小学学过的HashMap实现之一开放地址法就不啰嗦了。

而FastThreadLocal的id则是一个自增的int,FastThreadLocalThread里放一个数组,直接按下标获取,没有hash,没有比较,没有冲突。不过需要在Netty地界里用,业务线程池就要自己定义ThreadFactory,创建FastThreadLocalThread 而不是Thread。

4.2 移植JDK8的宝贝到JDK7

JDK8重写了ConcurrentHashMap,原来的Load Factor,Current Level都没有作用了。
ThreadLocalRandom就是把原来有全局锁的Random,通过ThreadLocal化取消了锁。
LongAdder则是把AtomicLong打散成几个,平时++的时候找其中一个执行,减少CMS冲突的概率,等get()的时候才把几个counter累计起来,适合increment()多,get()少的情况。

Netty把这些类都复制黏贴了一份,封装在 PlatformDependent里,根据JDK版本决定返回JDK原生的还是它复制的。

4.3 其他宝贝

4.3.1 ThreadLocal的StringBuilder

之前写过StringBuilder在高性能场景下的正确用法 ,才发现Netty和我做了同样的事,通过ThreadLocal的保存,重用StringBuilder对象,节约内存和分配内存的时间。当然,如果字符串只是很短就未必有必要。

4.3.2 IntHashMap

原始类型的map,比如key是int而不是Integer的Map,在某些次元里挺流行的,Trove,Koloboke, FastUtil等等,好处一是int比Integer省地方,int是4bytes,Integer是12+4 bytes,另外数据结构与解决冲突的方式也不同,详见高性能场景下,关于HashMap的补课

比起FastUtils.jar 穷举各种原始类型-原始类型/对象类型的组合,动不动10M大小。Netty只有IntHashMap一个类, 4.1又增加了LongHashMap等,够用了。

4.3.3 JCTools的Queue

针对Multiple Producer-Single Consumer,Single Producer-Multiple Consumer等不同场景专门设计,做到最少的锁。
不过并不提供阻塞等待的函数,所以不能拿来替换ArrayBlockingQueue。

4.3.4 RecyclableArrayList

如果你需要经常创建很长的ArrayList,不想浪费了,可以考虑用它来节约GC,不过到底哪边的代价大,一定要真正测试后决定。详见Netty精粹之轻量级内存池技术实现原理与应用

5. 其他零碎篇

主要来自Norman Maurer的文章:

1. ctx.writeAndFlush() 与 channel.writeAndFlush()的区别在于,channel要经过整条Pipeline,而ctx直接找下一个outboundHandler。

2. channel.writeAndFlush(buf, channel.voidPromise() )
writeAndFlush不管你用不用默认构造返回一个Promise(Future),有点浪费内存。没有用的话,用一个公共的 voidPromise ,减少大家花费。但低版本的Netty不能用。

3. 3. 空闲连接管理,因为刚才说的ctx.writeAndFlush()可能不经过IdleHander,所以只监控读空闲就够了。而且如果每次请求都要READ/WRITE/ALL IDEL三个值算一遍,也白白消耗性能。

4. writeAndFlush()不要太多,毕竟调用了系统调用。

5. Handler能共用就标上Shareable Annotation然后共用,不要每个Channel建一个。

暂时只想到这么多。其他想起来再写吧。 最后吐槽一句,Netty即使用的再溜,你的内核参数设定,你的业务代码,其实也有很大的影响,优化时不要光盯着Netty。

Netty高性能编程备忘录(下)的更多相关文章

  1. Netty高性能编程备忘录(上)

    http://calvin1978.blogcn.com/articles/netty-performance.html 网上赞扬Netty高性能的文章不要太多,但如何利用Netty写出高性能网络应用 ...

  2. Netty 系列之 Netty 高性能之道

    1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用 Netty4 + Thrift 压缩二进制编解码技术,他们实现了 10 W TPS(1 K 的复杂 POJO 对象)的跨 ...

  3. Netty系列之Netty高性能之道

    转载自http://www.infoq.com/cn/articles/netty-high-performance 1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Ne ...

  4. Netty高性能之道

    1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程服务调用.相比于 ...

  5. 转:Netty系列之Netty高性能之道

    1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程服务调用 ...

  6. 新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析

    1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件 ...

  7. 【读后感】Netty 系列之 Netty 高性能之道 - 相比 Mina 怎样 ?

    [读后感]Netty 系列之 Netty 高性能之道 - 相比 Mina 怎样 ? 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商 ...

  8. Netty高性能原理和框架架构解析

    1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件 ...

  9. Netty 系列之 Netty 高性能之道 高性能的三个主题 Netty使得开发者能够轻松地接受大量打开的套接字 Java 序列化

    Netty系列之Netty高性能之道 https://www.infoq.cn/article/netty-high-performance 李林锋 2014 年 5 月 29 日 话题:性能调优语言 ...

随机推荐

  1. 05: api认证

    1.1 api认证原理介绍 1.api认证原理:客户端生成秘钥 1) 客户端与服务器端有一个相同的字符串作为auth_key 2) 客户端使用encryption="auth_key|tim ...

  2. 常用maven命令总结

    常用Maven命令: mvn -v //查看版本 mvn archetype:create //创建 Maven 项目 mvn compile //编译源代码 mvn test-compile //编 ...

  3. 【第二十三章】 springboot + 全局异常处理

    一.单个controller范围的异常处理 package com.xxx.secondboot.web; import org.springframework.web.bind.annotation ...

  4. com.mysql.jdbc.PacketTooBigException,及mysql 设置 max_allow_packet

    本文为博主原创,未经允许不得转载: 在进行批量导入表格数据入库操作时,报了以下错误: 错误分析: mysql根据配置文件会限制server接受的数据包大小.有时候大的插入和更新会受max_allowe ...

  5. jquery.cookie.js中$.cookie() 使用方法

    定义:让网站服务器把少量数据储存到客户端的硬盘或内存,从客户端的硬盘读取数据的一种技术: 下载与引入:jquery.cookie.js基于jquery:先引入jquery,再引入:jquery.coo ...

  6. java的事务类型及定义

    转载: 什么是事务: 首先,说说什么事务.我认为事务,就是一组操作数据库的动作集合. 事务是现代数据库理论中的核心概念之一.如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务. ...

  7. c++ 容器元素填充(fill)

    #include <iostream> // cout #include <algorithm> // fill #include <vector> // vect ...

  8. 使用uWSGI和nginx来设置Django和你的web服务器

    本教程针对那些想要设置一个生产web服务器的Django用户.它介绍了设置Django以使得其与uWSGI和nginx工作良好的必要步骤.它涵盖了所有三个组成部分,提供了一个web应用和服务器软件的完 ...

  9. Unity2017烘焙参数设置

  10. java反射究竟消耗多少效率

    原文出处 一直以来都对Java反射究竟消耗了多少效率很感兴趣,今晚总算有空进行了一下测试 测试被调用的类和方法 package com.spring.scran; public class TestM ...