10 | 生产者压缩算法面面观


Kafka 核心技术与实战

客户端实践及原理剖析

10 | 生产者压缩算法面面观

怎么压缩?

Kafka 的消息层次都分为两层:消息集合(message set) 以及消息(message)。一个消息集合中包含若干条日志项(record item),而日志项才是真正封装消息的地方。Kafka 底层的消息日志由一系列消息集合日志项组成。Kafka 通常不会直接操作具体的一条条消息,它总是在消息集合这个层面上进行写入操作。

在这里插入图片描述

目前 Kafka 共有两大类消息格式,社区分别称之为 V1 版本和 V2 版本。V2 版本是 Kafka 0.11.0.0 中正式引入的。

V2 版本主要是针对 V1 版本的一些弊端做了修正,其一,就是把消息的公共部分抽取出来放到外层消息集合里面,这样就不用每条消息都保存这些信息了。

在这里插入图片描述

原来在 V1 版本中,每条消息都需要执行 CRC 校验,但有些情况下消息的 CRC 值是会发生变化的。比如在 Broker 端可能会对消息时间戳字段进行更新,那么重新计算之后的 CRC 值也会相应更新;再比如 Broker 端在执行消息格式转换时(主要是为了兼容老版本客户端程序),也会带来 CRC 值的变化。鉴于这些情况,再对每条消息都执行 CRC 校验就有点没必要了,不仅浪费空间还耽误 CPU 时间,因此在 V2 版本中,消息的 CRC 校验工作就被移到了消息集合这一层

其二,就是保存压缩消息的方法发生了变化。 之前 V1 版本中保存压缩消息的方法是把多条消息进行压缩然后保存到外层消息的消息体字段中;而 V2 版本的做法是对整个消息集合进行压缩。 显然后者应该比前者有更好的压缩效果。

在相同条件下,不论是否启用压缩,V2 版本都比 V1 版本节省磁盘空间。当启用压缩时,这种节省空间的效果更加明显,就像下面这两张图展示的那样:

在这里插入图片描述

何时压缩?

在 Kafka 中,压缩可能发生在两个地方:生产者端Broker 端

生产者程序中配置 compression.type 参数即表示启用指定类型的压缩算法。比如下面这段程序代码展示了如何构建一个开启 GZIP 的 Producer 对象:

 Properties props = new Properties();
 props.put("bootstrap.servers", "localhost:9092");
 props.put("acks", "all");
 props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
 props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
 // 开启GZIP压缩
 props.put("compression.type", "gzip");
 
 Producer<String, String> producer = new KafkaProducer<>(props);

大部分情况下 Broker 从 Producer 端接收到消息后仅仅是原封不动地保存而不会对其进行任何修改,但这里的“大部分情况”也是要满足一定条件的。有两种例外情况就可能让 Broker 重新压缩消息:

情况一:Broker 端指定了和 Producer 端不同的压缩算法。

Broker 端也有一个参数叫 compression.type。但是这个参数的默认值是 producer,这表示 Broker 端会“尊重” Producer 端使用的压缩算法。可一旦在 Broker 端设置了不同的 compression.type 值,就可能会发生预料之外的压缩 / 解压缩操作,通常表现为 Broker 端 CPU 使用率飙升

情况二:Broker 端发生了消息格式转换。

所谓的消息格式转换主要是为了兼容老版本的消费者程序。为了兼容老版本的格式,Broker 端会对新版本消息执行向老版本格式的转换,这个过程中会涉及消息的解压缩和重新压缩。一般情况下这种消息格式转换对性能是有很大影响的,除了这里的压缩之外,它还让 Kafka 丧失了引以为豪的 Zero Copy 特性

传统传输方式如下图所示:
在这里插入图片描述
从上图可以看出,传统传输方式经历了四次数据拷贝过程:

  • 数据从磁盘复制到内核缓冲区
  • 从内核缓冲区复制到用户空间缓冲区
  • 从用户缓冲区复制到内核的socket缓冲区
  • 从socket缓冲区复制到协议引擎(这里是网卡驱动)

零拷贝是指不需要 cpu 参与在内存之间复制数据的操作(内核态数据拷贝操作),对操做系统来讲就是零拷贝了。数据只需要从磁盘复制到内核,再从内核复制到协议引擎,减少了从内核到用户空间,从用户空间到 socket 缓冲两次复制。从磁盘复制到内核缓冲区是通过 DMA 引擎来做而不是 cpu,同样的,从 内核缓冲区到协议引擎也是由 DMA 引擎来做,这样就节省了 cpu 的工作。

零拷贝技术的演进:
mmap
在这里插入图片描述
sendfile
在这里插入图片描述
使用 DMA gather copy 的 sendfile
在这里插入图片描述

何时解压缩?

通常来说解压缩发生在消费者程序中,也就是说 Producer 发送压缩消息到 Broker 后,Broker 照单全收并原样保存起来。当 Consumer 程序请求这部分消息时,Broker 依然原样发送出去,当消息到达 Consumer 端后,由 Consumer 自行解压缩还原成之前的消息。

Kafka 会将启用了哪种压缩算法封装进消息集合中,这样当 Consumer 读取到消息集合时,它自然就知道了这些消息使用的是哪种压缩算法。

除了在 Consumer 端解压缩,Broker 端也会进行解压缩。每个压缩过的消息集合在 Broker 端写入时都要发生解压缩操作,目的就是为了对消息执行各种验证。另外,前面提到的消息格式转换和Broker 与 Producer 使用不同的压缩算法也会进行解压缩。

各种压缩算法对比

在 Kafka 2.1.0 版本之前,Kafka 支持 3 种压缩算法:GZIPSnappyLZ4。从 2.1.0 开始,Kafka 正式支持 Zstandard 算法(简写为 zstd),它是 Facebook 开源的一个压缩算法,能够提供超高的压缩比(compression ratio)。

Facebook Zstandard 官网提供的一份压缩算法 benchmark 比较结果:

在这里插入图片描述

从表中可以发现 zstd 算法有着最高的压缩比,而在吞吐量上的表现只能说中规中矩。反观 LZ4 算法,它在吞吐量方面则是毫无疑问的 NO.1

在实际使用中,GZIP、Snappy、LZ4 甚至是 zstd 的表现各有千秋。但对于 Kafka 而言,它们的性能测试结果却出奇得一致,即在吞吐量方面:LZ4 > Snappy > zstd 和 GZIP;而在压缩比方面,zstd > LZ4 > GZIP > Snappy。 具体到物理资源,使用 Snappy 算法占用的网络带宽最多,zstd 最少,这是合理的,毕竟 zstd 就是要提供超高的压缩比;在 CPU 使用率方面,各个算法表现得差不多,只是在压缩时 Snappy 算法使用的 CPU 较多一些,而在解压缩时 GZIP 算法则可能使用更多的 CPU。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

久违の欢喜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值