由浅到深学习Kafka(六):生产者压缩算法

说起压缩(compression),它秉承了用时间去换空间的经典trade-off思想 

具体来说就是用CPU时间去换磁盘空间或网络I/O传输量,希望以较小的CPU开销带来更少的磁盘占用或更少的网络I/O传输

在Kafka中,压缩也是用来做这件事的,今天就来学习一下Kafka中压缩

如何压缩?

Kafka是如何压缩消息的呢?

要弄清楚这个问题,就要从Kafka的消息格式说起

目前Kafka共有两大类消息格式,社区分别称之为V1版本和V2版本

V2版本是Kafka 0.11.0.0中正式引入的

不论是哪个版本,Kafka的消息层次都分为两层:消息集合(message set)以及消息(message)

一个消息集合中包含若干条日志项(record item),而日志项才是真正封装消息的地方

Kafka底层的消息日志由一系列消息集合日志项组成

Kafka通常不会直接操作具体的一条条消息,它总是在消息集合这个层面上进行写入操作

为什么要引入V2版本?

V2版本主要是针对V1版本的一些弊端做了修正

其中之一的改动就是:V2版本把消息的公共部分抽取出来放到外层消息集合里面,这样就不用每条消息都保存这些信息了

举个例子:

在V1版本中,每条消息都需要执行CRC校验,但有些情况下消息的CRC值是会发生变化的,比如在Broker端可能会对消息时间戳字段进行更新,那么重新计算之后的CRC值也会相应更新;

再比如Broker端在执行消息格式转换时(主要是为了兼容老版本客户端程序),也会带来CRC值的变化

鉴于这些情况,再对每条消息都执行CRC校验就有点没必要了,不仅浪费空间还耽误CPU时间,因此在V2版本中,息的CRC校验工作就被移到了消息集合这一层


V2版本还有一个和压缩息息相关的改进,就是保存压缩消息的方法发生了变化

之前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);

这里比较关键的代码行是props.put(“compression.type”, “gzip”),它表明该Producer的压缩算法使用的是GZIP

这样Producer启动后生产的每个消息集合都是经GZIP压缩过的,故而能很好地节省网络传输带宽以及Kafka Broker端的磁盘占用

在生产者端启用压缩是很自然的想法,那为什么说在Broker端也可能进行压缩呢?

其实大部分情况下 Broker从Producer端接收到消息后仅仅是原封不动地保存而不会对其进行任何修改,但有两种例外情况就可能让Broker重新压缩消息

(1)Broker端指定了和Producer端不同的压缩算法

举个例子:

Producer端指定了压缩算法为GZIP,Broker端指定了压缩算法为Snappy,在这种情况下Broker接收到GZIP压缩的消息后,只能先解压缩然后使用Snappy重新压缩一遍

在Kafka官网,你会发现Broker端的配置,也有一个参数叫compression.type


但是这个参数的默认值是producer,这表示Broker端会 “尊重” Producer端使用的压缩算法

可一旦在Broker端设置了不同的compression.type值,就要小心了,因为可能会发生预料之外的压缩/解压缩操作,通常表现为Broker端CPU使用率飙升

(2)Broker端发生了消息格式转换

消息格式转换主要是为了兼容老版本的消费者程序,在一个生产 环境中,Kafka集群中同时保存多种版本的消息格式非常常见,为了兼容老版本的格式,Broker端会对新版 本消息执行向老版本格式的转换,这个过程中会涉及消息的解压缩和重新压缩,一般情况下这种消息格式转换对性能是有很大影响的

什么时候解压缩?

有压缩必有解压缩,通常来说解压缩发生在Consumer程序中,也就是说Producer发送压缩消息到Broker后, Broker会将消息原样保存起来

当Consumer程序请求这部分消息时,Broker依然原样发送出去,当消息到达Consumer端后,由Consumer自行解压缩还原成之前的消息

那么有个问题,Consumer在解压缩的时候,它怎么知道这些消息是用何种压缩算法压缩的呢?

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

除了在Consumer端解压缩,Broker端也会进行解压缩,这和前面提到消息格式转换时发生的解压缩是不同的场景

每个压缩过的消息集合在Broker端写入时都要发生解压缩操作,目的就是为了对消息执行各种验证,这种解压缩对Broker端性能是有一定影响的,特别是对CPU的使用率而言

各种压缩算法对比

前面铺垫了这么多,就是为了对比一下各种压缩算法的优劣,这样我们能有针对性地配置适合我们业务的压缩策略

在Kafka 2.1.0版本之前,Kafka支持3种压缩算法:GZIP、Snappy和LZ4

从2.1.0开始,Kafka正式支持 Zstandard算法(简写为zstd)。它是Facebook开源的一个压缩算法,能够提供超高的压缩比 

看一个压缩算法的优劣,有两个重要的指标:

(1)压缩比,原先占100份空间的东西经压缩之 后变成了占20份空间,那么压缩比就是5,显然压缩比越高越好

(2)压缩/解压缩吞吐量,比如每秒能压缩或解压缩多少MB的数据,同样,吞吐量也是越高越好

下面这张表是Facebook Zstandard官网提供的一份压缩算法基准测试比较结果

从表中我们可以发现zstd算法有着最高的压缩比,而在吞吐量上的表现只能说中规中矩

反观LZ4算法,它在吞吐量方面则是名列前茅

当然,对于表格中数据的权威性,我不好做评论,只想用它来说明一下当前各种压缩算法的大致表现,可能大家在测试的过程中有不同的结果

在实际使用中,GZIP、Snappy、LZ4甚至是zstd的表现各有千秋

但对于Kafka而言,它们的性能测试结果却出奇得一致,即在吞吐量方面:LZ4 > Snappy > zstd / GZIP

而在压缩比方面,zstd > LZ4 > GZIP > Snappy

具体到物理资源,使用Snappy算法占用的网络带宽最多,zstd最少,这是合理的,毕竟zstd就是要提供超高的压缩比

在CPU使用率方面,各个算法表现得差不多,只是在压缩时Snappy算法使用的CPU较多一些,而在解压缩时GZIP算法则可能使用更多的CPU



关于Kafka压缩,就介绍到这里了,下篇文章见~

赫墨拉

我是一个喜爱大数据的小菜鸡,这里是我分享我的成长和经历的博客

You may also like...

发表评论

电子邮件地址不会被公开。