背景
在上一篇文章《读kafka生产端源码,窥kafka设计之道(上)》 留下了kafka设计上比较优秀的一个点;内存的循环使用。本篇文章准备盘盘它。
好奇
为什么 kafka减少发送消息时向JVM频繁申请内存,就可以降低JVM GC的执行次数?
我们知道网络上传输的都是二进制数据;而在java中想通过socke网络套接字接口发送数据,底层都是用的ByteBuffer。在往网络上发送数据前,先申请块ByteBuffer的内存;然后把数据写入到此ByteBuffer内存中;调用底层socket的write接口,就OK了;大概伪代码流程
//伪代码
//申请内存
ByteBuffer buffer = ByteBuffer.allocate(size)
//内存里加入数据
buffer.put(XXX)
//发送数据
SocketChannel.write(ByteBuffer src)
ByteBuffer占用的内存,什么时候会被回收了?
答:在jvm进行GC时会被回收;
试想如果上面那段代码执行非常频繁,创建ByteBuffer就会很频繁;创建ByteBuffer很频繁,那么申请内存就会很频繁,申请内存越频繁,内存被占满的时间也就会越来越短,内存满了就只能靠不停的GC进行内存的回收,加以重复使用了。而现代JVM里GC的发展目标之一,就是减少GC的停顿时间。GC优化大师从PS,CMS,G1,到ZGC,都在朝这这个方向在努力。
kafka如何解决这个内存频繁申请和GC 频繁释放的问题了?
如果可以用一句简单的话来总结:那么我想应该是 对ByteBuffer的重复使用。 是的用完了不要丢,也不让jvm 给GC了。 即对进行了网络发送的ByteBuffer进行复用;如果有新的消息要发送,可以从缓存池里获取已有 ByteBuffer;然后往里面写入消息数据;当IO线程把ByteBuffer里的消息发往broker并收到对应的响应后,会把ByteBuffer放回缓存池供下一次需要发送的消息循环使用。
大概流程如下图:
核心参数和代码
有两个核心参数,可以控制缓存池BufferPool的行为
-
buffer.memory
缓存池大小,默认32M。如果IO thread发送消息的速率比业务线程生产消息的速度慢,则会引起业务线程的阻塞,可根据实际情况和jvm大小增大此参数 -
batch.size
控制每个缓存块ByteBuffer的大小,默认为16K。即一个 BatchRecord里可存的多条消息最大空间。 -
ByteBuffer的申请
-
ByteBuffer的回收
总结
如果要编写一款网络应用程序,或者网络框架的工具,我希望能向kafka一样,能考虑到内存的复用;并且减少对上层应用的影响。
假设一个应用通过kafka发送50个G的网络数据;那么kafka的缓存池,就节约了10个G内存的申请和回收;由此减少了多少次GC和GC暂停时间了。那么假设有个50个这样的应用了?总的收益又是多少了?
不是所有的工具都能号称是为应对大数据场景而产生的;kafka做为一款中间件,能比较好的融入大数据生态,kafka的研发人员有自己的独特设计和考虑在支撑这它。
原创不易,请 点赞,关注,留言,转载 4暴击^^