kafka是如何压缩消息的?要弄清楚这个问题,就要从kafka的消息格式说起。kafka的消息层次分为两层:消息集合(message set)以及消息(message)。一个消息集合包含若干条日志项(record item),而日志项才是真正封装消息的地方。kafka底层的消息日志由一系列消息集合日志项组成。kafka通常不会直接操作具体的一条条消息,它总是在消息集合这个层面上进行写入操作。
在kafka中,压缩可能会发生在两个地方:生产者端和broker端。
生产者程序中配置compression.type参数即表示启动指定类型的压缩算法。
public class KafkaProduce { public void kafkaProducer() throws Exception { Properties pro = new Properties(); ...//其他配置参数 pro.put("partitioner.class", "kafka.KafkaPartitioner"); // 启用压缩 pro.put("compression.type", "gzip"); KafkaProducer config = new KafkaProducer(pro); }
它表明producer的压缩算法使用的是gzip。这样producer启动后生产的每个消息集合都是经过gzip压缩过的,故而能很好地节省网络传输带宽已经broker端的磁盘占用。
何时压缩?
大部分情况下,broker从producer端接收到消息后仅仅是原封不动地保存,而不会对其进行任何修改,但这个“大部分情况”也是要满足条件。有两种例外的情况会让broker端重新压缩消息。
情况一:broker端指定了和producer端不同的压缩算法。
kafka broker端也有一个参数叫compression.type,和producer端的参数设置一样。但是这个参数的默认值是producer,表示broker端会尊重producer端使用的压缩算法,可一旦你在broker端设置了不同的compression.type值,就一定要小心了,因为可能会发生预料之外的压缩/解压缩操作,导致broker端CPU使用率飙升。比如broker端接收到gzip压缩消息后,broker端指定了snappy压缩算法,这样broker只能解压缩然后使用snappy重新压缩一遍。
情况二:broker端发送了消息格式变化。
所谓的消息格式变化主要是为了兼容老版本的消费者程序。在一个生产环境中,kafka集群中同时保存多种版本的消息格式非常常见。为了兼容老版本的格式,broker端会对新版本消息执行向老版本格式的转换。这个过程就会涉及到消息的解压和重新压缩。一般情况下这种消息格式的转换对性能是有很大的影响的,除了,这里讲的压缩外,还会让kafka丧失了引以为豪的zero copy特性。所以尽量保证消息格式的统一,这样不仅可以避免不必要的解压缩/重新压缩,对提升其他方面的性能也很有裨益。
何时解压缩?
通常来说解压缩发生在消费者程序中。kafka会将启用了哪种压缩算法封装进消息集合中,这样当consumer读取到集合时,它自然就知道了这些消息使用了哪种压缩算法。用一句话总结:producer端压缩,broker端保持,consumer端解压缩。
broker端也会进行解压缩,注意了,和前面提到的场景不一样。每个压缩过的消息集合在broker端写入时都要发生解压缩操作,目的就是为了对消息执行各种验证,这种解压缩对broker端性能是有一定的影响,特别是对CPU使用率而言。
各种压缩算法对比
在kafka2.1.0版本之前,kafka支持3种压缩算法:GZIP、Snappy和LZ4。从2.1.0开始,kafka正式支持Zstandard算法(简写zstd)。它是Facebook开源的一个压缩算法,能够提供超高的压缩比。对于kafka测试而言,在吞吐方面:LZ4>Snappy> zstd、GZIP;在压缩比方面:zstd>lz4>gzip>snappy。具体到物理资源,使用snappy算法占用的网络带宽资源最多,zstd最少,这是合理的,毕竟zstd就是要提供超高的压缩比;在CPU使用率方面,各个算法表现得差不多,只是在压缩时snappy使用的CPU较多一些,而在解压缩时gzip算法则可能使用更多的CPU。