Netty高性能编程备忘录(下)
估计很快就要被拍砖然后修改,因此转载请保持原文链接,否则视为侵权...
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高性能编程备忘录(下)的更多相关文章
- Netty高性能编程备忘录(上)
http://calvin1978.blogcn.com/articles/netty-performance.html 网上赞扬Netty高性能的文章不要太多,但如何利用Netty写出高性能网络应用 ...
- Netty 系列之 Netty 高性能之道
1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用 Netty4 + Thrift 压缩二进制编解码技术,他们实现了 10 W TPS(1 K 的复杂 POJO 对象)的跨 ...
- Netty系列之Netty高性能之道
转载自http://www.infoq.com/cn/articles/netty-high-performance 1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Ne ...
- Netty高性能之道
1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程服务调用.相比于 ...
- 转:Netty系列之Netty高性能之道
1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程服务调用 ...
- 新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析
1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件 ...
- 【读后感】Netty 系列之 Netty 高性能之道 - 相比 Mina 怎样 ?
[读后感]Netty 系列之 Netty 高性能之道 - 相比 Mina 怎样 ? 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商 ...
- Netty高性能原理和框架架构解析
1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件 ...
- Netty 系列之 Netty 高性能之道 高性能的三个主题 Netty使得开发者能够轻松地接受大量打开的套接字 Java 序列化
Netty系列之Netty高性能之道 https://www.infoq.cn/article/netty-high-performance 李林锋 2014 年 5 月 29 日 话题:性能调优语言 ...
随机推荐
- zabbix监控之自定义item
zabbix安装完成后,当需要使用自定义脚本构建自定义item必须注意以下几点: 1.首先使用zabbix_get手动在zabbix-server服务端获取监控的默认的item值,如下: [root@ ...
- JS 获取浏览器的宽和高
网页可见区域宽:document.body.clientWidth 网页可见区域高:document.body.clientHeight 网页可见区域宽:document.body.offsetWid ...
- 【javascript】数据结构-链表
// 创建一个链表 function LinkedList(){ // 创建一个Node辅助类,表示需要加入列表的项,它包含一个element属性,即表示需要加入到列表中的值,next属性表示指向下一 ...
- BIOS、MBR、UEFI和GPT关系
很多用户在新买电脑,或是给已有电脑重装系统时都出现过怎么都无法引导U盘安装的情况.究其原因,还是没能搞清楚BIOS.MBR.UEFI和GPT的复杂关系.所以,今天小编就和大家分享一下它们之间的爱恨情仇 ...
- Python 开发环境搭建
Python分别有两个大的版本,分别是2和3 下载地址:Python-3.6.2 Python-2.7.13 现在安装路径:D:\Program Files\Python 安装完成以后要安装 pi ...
- poj3352 Road Construction & poj3177 Redundant Paths (边双连通分量)题解
题意:有n个点,m条路,问你最少加几条边,让整个图变成边双连通分量. 思路:缩点后变成一颗树,最少加边 = (度为1的点 + 1)/ 2.3177有重边,如果出现重边,用并查集合并两个端点所在的缩点后 ...
- 【第二十五章】 springboot + hystrixdashboard
注意: hystrix基本使用:第十九章 springboot + hystrix(1) hystrix计数原理:附6 hystrix metrics and monitor 一.hystrixdas ...
- System.ConfigurationManager类用于对配置文件的读取
http://blog.csdn.net/ligenyingsr/article/details/54095986 System.ConfigurationManager类用于对配置文件的读取.其具有 ...
- LLDP协议、STP协议 笔记
参考: 数据链路层学习之LLDP 生成树协议 LLDP协议.STP协议 笔记 LLDP 提出背景: 随着网络技术的发展,接入网络的设备的种类越来越多,配置越来越复杂,来自不同设备厂商的设备也往往会增加 ...
- UVa 11093 环形跑道(模拟)
https://vjudge.net/problem/UVA-11093 题意:环形跑道上有n个加油站,编号为1~n.第i个加油站可以加油pi加仑,从加油站i开到下一站需要qi加仑汽油.输出最小的起点 ...