kafka在消息指定好分区后,并没有直接把消息发出去,而是把消息存放在内存中,再分批发出去,这样就减少了网络传输的次数,减少了网络的开销,整体的效率就会得到了很大的提升。管理消息的内存叫做RecordAccumulator,即消息累加器。
batches
上一章我们已经知道了消息是往哪个partition发的,所以topic和partition就会组成一个TopicPartition对象。
如果一个topic有3个partition,那就会有3个TopicPartition,这3个TopicPartition在消息累加器里,就有对应的3个队列Deque,这个信息,存放在数据结构为CopyOnWriteMap的batches中。
当然RecordAccumulator一开始的时候,肯定是没有对应的Deque,比如topic1+0的TopicPartition刚准备往Deque扔数据的时候,就会看看是否有这个TopicPartition对应的Deque,如果有,就拿出来用,如果没有,就会创建一个。
Deque和是TopicPartition在RecordAccumulator是以map来做映射关系的,由于get的时候比较多,put的时候时候比较少,是一个写少读多的 场景,所以这个map是CopyOnWriteMap的结构,在put的时候会先复制再写,导致内存有一定的消耗,但是对于get来说,由于没有加锁,而且是线程安全的,性能就大大的提升。
RecordBatch
Deque队列里存放的并不是消息本身,而是一个个封装消息的RecordBatch,每个RecordBatch默认大小为16k,由batch.size
进行设置。
如果第一个消息封装后有4k,第二个消息封装后有10k,那这两个消息是放在同一个RecordBatch的。
如果消息的大小超过了默认的16k,那这个RecordBatch的大小就是以实际的大小为准。
BufferPool
上面的内存空间并不是可以一直申请的,他有一个总的空间,默认大小为32M,由buffer.memory
进行设置。
内存空间通过BufferPool进行管理,每次申请一个新的RecordBatch,都要看看当前已用的空间+当前申请的RecordBatch是不是小于32M,如果小,那就可以一直申请。
BufferPool里面也有一个Deque队列,当大小为16k的RecordBatch用完的时候,就会把这个空间存放在BufferPool的Deque里,当申请内存的时候,就可以直接从Deque里拿出来。这样做的目的是为了防止重复的创建对象,进而引起垃圾回收。
如果申请的时候,32M的内存都被消耗完了,此时就会阻塞在这里,等有空间归还了,会被唤醒继续申请内存了。
如果RecordBatch的大小并不等于16k,那他此时是没有办法回收到BufferPool的Deque里,所以只能等着垃圾回收,所以我们使用kafka的时候,batch.size
的大小要根据业务需要进行扩大,比如我们基本都是发送500k的数据,BufferPool的Deque实际上并没有起到什么作用,JVM还会一直进行垃圾回收。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。