kafka 高吞吐量介绍
-
零拷贝
将数据直接从磁盘文件复制到网卡设备中,而不需要经过应用程序,减少了内核和用户模式之间的上下文切换,依赖于底层的senffile()方法。
内核态是对系统硬件资源的控制,用户态是应用程序,上层应用。在系统调用的时候,会发生用户从用户态到内核态的上下文切换。
如果我们需要将数据展示给用户,我们就需要先将数据拷贝到内存,在从内存把数据放到socket,展示给用户。
通过
sendfile
系统调用,提供了零拷贝。数据通过DMA(Direct Memory Access)拷贝到内核态Buffer后,直接通过DMA拷贝到NIC Buffer,无需CPU拷贝。PlaintextTransportLayer.class @Override public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException { return fileChannel.transferTo(position, count, socketChannel); }
-
磁盘顺序写 和 页缓存
kafka设计采用文件追加的方式,只能在文件的尾部追加新的消息,不允许修改已经写入消息。当broker接收到producer发送过来的消息的时候,根据消息的主题和分区情况,将该消息写入到该分区的最后的一个segment文件中。采用这种追加的方式避免了磁盘随机写入磁盘寻道的开销。
如果每条消息都执行一次磁盘写入,也还是会造成很大的I/O开销,kafka使用了大量的页缓存,消息先被写入到页缓存,然后由操作系统负责将数据刷到磁盘,kafka提供了同步刷盘和间歇性刷盘的功能。除了页缓存批量刷盘消息,kafka服务重启,页缓存的数据还是保持有效。
-
消息批量写入 和 消息压缩
kafka消息压缩方式是将多条消息一起压缩,这样的压缩效果比较好;compression .type来配置压缩方式,有GZIP、SNAPPY、LS4,默认是不压缩的。
kafka 消息格式
public static int writeTo(DataOutputStream out, int offsetDelta, long timestampDelta, ByteBuffer key, ByteBuffer value, Header[] headers) throws IOException { int sizeInBytes = sizeOfBodyInBytes(offsetDelta, timestampDelta, key, value, headers); ByteUtils.writeVarint(sizeInBytes, out); byte attributes = 0; // there are no used record attributes at the moment out.write(attributes); //属性 ByteUtils.writeVarlong(timestampDelta, out); // 时间增量 ByteUtils.writeVarint(offsetDelta, out); // 偏移增量 if (key == null) { ByteUtils.writeVarint(-1, out); } else { int keySize = key.remaining(); ByteUtils.writeVarint(keySize, out); Utils.writeTo(out, key, keySize); //key keySize } if (value == null) { ByteUtils.writeVarint(-1, out); } else { int valueSize = value.remaining(); ByteUtils.writeVarint(valueSize, out); Utils.writeTo(out, value, valueSize); // value valueSize } if (headers == null) throw new IllegalArgumentException("Headers cannot be null"); ByteUtils.writeVarint(headers.length, out); for (Header header : headers) { String headerKey = header.key(); if (headerKey == null) throw new IllegalArgumentException("Invalid null header key found in headers"); byte[] utf8Bytes = Utils.utf8(headerKey); ByteUtils.writeVarint(utf8Bytes.length, out); out.write(utf8Bytes); byte[] headerValue = header.value(); if (headerValue == null) { ByteUtils.writeVarint(-1, out); } else { ByteUtils.writeVarint(headerValue.length, out); // headerValue.length out.write(headerValue); // headerValue } } return ByteUtils.sizeOfVarint(sizeInBytes) + sizeInBytes; }
上图是一条消息的所有数据信息,采用很多的varInt、varLong等变长整型和ZigZag编码。会把消息追加到batch里面去。
-
总结
零拷贝减少了上下文的切换;磁盘顺序写加快了写入时间 ,页缓存减缓I/O开销;消息批量写入减少网络I/O,消息压缩减少传输量。