【kafka学习记录】3 - 生产者消息分区和压缩

在使用Apache Kafka生产和消费消息的时候,肯定是希望能够将数据均匀地分配到所有服务器上。比如很多公司使用Kafka收集应用服务器的日志数据,这种数据都是很多的,特别是对于那种大批量机器组成的集群环境,每分钟产生的日志量都能以GB数,因此如何将这么大的数据量均匀地分配到Kafka的各个Broker上,就成为一个非常重要的问题。

 

分区

消息组织方式:主题 - 分区- 消息

主题下的每条消息只会保存在某一个分区中,而不会在多个分区中被保存多份。

分区是为了提高负载均衡的能力,为了实现系统的高伸缩性。(也可以实现业务级别的消息顺序)

不同的分区能够被放置到不同节点的机器上,而数据的读写操作也都是针对分区这个粒度而进行的,这样每个节点的机器都能独立地执行各自分区的读写请求处理。并且,我们还可以通过添加新的节点机器来增加整体系统的吞吐量。

分区应该是实现 org.apache.kafka.clients.producer.Partitioner 接口

 

 

分区策略

决定生产者将消息发送到哪个分区的算法,kafka提供了默认的分区策略,也可以自定义。

Kafka默认分区策略实际上同时实现了两种策略:如果指定了Key,那么默认实现按消息键保序策略;如果没有指定Key,则使用轮询策略。

 

1、轮询策略

即顺序分配,依次放在分区中,是默认的分区策略。

轮询策略有非常优秀的负载均衡表现,它总是能保证消息最大限度地被平均分配到所有分区上,故默认情况下它是最合理的分区策略,也是我们最常用的分区策略之一。

 

 

2、随机策略

是随意地将消息放置到任意一个分区上。

不如轮询策略好,需要实现 partition方法:先计算出该主题总的分区数,然后随机地返回一个小于它的正整数。

List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); 

return ThreadLocalRandom.current().nextInt(partitions.size());

 

3、 Key-ordering策略

Kafka允许为每条消息定义消息键,简称为Key。这个Key的作用非常大,它可以是一个有着明确业务含义的字符串,比如客户代码、部门编号或是业务ID等;也可以用来表征消息元数据。特别是在Kafka不支持时间戳的年代,在一些场景中,工程师们都是直接将消息创建时间封装进Key里面的。一旦消息被定义了Key,那么你就可以保证同一个Key的所有消息都进入到相同的分区里面,由于每个分区下的消息处理都是有顺序的,故这个策略被称为按消息键保序策略,如下图所示。

List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);

return Math.abs(key.hashCode()) % partitions.size();

 

 

如何实现消息的顺序问题?

kafka同一个topic是无法保证数据的顺序性的,但是同一个partition中的数据是有顺序的。

可以修改为根据某些特定的key、地理ip、汽车key自定义分区策略

List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);

 return partitions.stream().filter(p -> isSouth(p.leader().host())).map(PartitionInfo::partition).findAny().get();

 

 

 

如何确定Kafka的分区数、key和consumer线程数

如何通过水平扩展甚至是线性扩展来进一步提升吞吐量呢? Kafka就是使用了分区(partition),通过将topic的消息打散到多个分区并分布保存在不同的broker上实现了消息处理(不管是producer还是consumer)的高吞吐量。

 

 

其他知识点:

  • Java客户端默认的生产者分区策略的实现类为org.apache.kafka.clients.producer.internals.DefaultPartitioner。

  • 默认策略为:如果指定了partition就直接发送到该分区;如果没有指定分区但是指定了key,就按照key的hash值选择分区;如果partition和key都没有指定就使用轮询策略。而且如果key不为null,那么计算得到的分区号会是所有分区中的任意一个;如果key为null并且有可用分区时,那么计算得到的分区号仅为可用分区中的任意一个

  • 消息重试只是简单地将消息重新发送到之前的分区

  • 我们公司之前也有一个业务是单分区,要保证全局顺序。后来发现其实使用key+多分区也可以实现。反正保证同一批因果依赖的消息分到一个分区就可以

  • consumer.assign()直接消息指定分区

  • 总结:首先判断ProducerRecord中的partition字段是否有值,即是否在创建消息记录的时候直接指定了分区;如果指定了分区,则直接将该消息发送到指定的分区,否则调用分区器的partition方法,执行分区策略;如果用户配置了自己写的分区器,且在生产者配置是指定了,则使用用户指定的分区器,否则使用默认的分区器,即DefaultPartitioner;如果指定了key,则使用该key进行hash操作,并转为正数,然后对topic对应的分区数量进行取模操作并返回一个分区;如果没有指定key,则通过先产生随机数,之后在该数上自增的方式产生一个数,并转为正数之后进行取余操作。

 

1. 防止乱序可以通过设置max.in.flight.requests.per.connection=1来保证

2. 两个生产者生产的消息无法保证顺序,因为它们本身就没有前后之分,它们是并发的关系。

 


 

生产者压缩算法

Kafka的消息层次都分为两层:消息集合和消息。

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

这样消息层次有两层:外层是消息批次(或消息集合);里层是消息(或日志项)。

 

V2版本是Kafka 0.11.0.0中正式引入的,V2版本对V1版本进行了修正。

1、就是把消息的公共部分抽取出来放到外层消息集合里面,这样就不用每条消息都保存这些信息了。比如,在V2版本中,消息的CRC校验工作就被移到了消息集合这一层。

2、保存压缩消息的方法不同: V1版本是把多条消息进行压缩然后保存到外层消息的消息体字段中;V2版本是对整个消息集合进行压缩。

 

 

压缩

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

compression.type =  gzip

在生产者端启用压缩是很自然的想法,那为什么我说在Broker端也可能进行压缩呢?其实大部分情况下Broker从Producer端接收到消息后仅仅是原封不动地保存而不会对其进行任何修改,但这里的“大部分情况”也是要满足一定条件的。有两种例外情况就可能让Broker重新压缩消息。

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

情况二:Broker端发生了消息格式转换 为了兼容老版本的格式,Broker端会对新版本消息执行向老版本格式的转换。这个过程中会涉及消息的解压缩和重新压缩。

 

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

 

 

总结:

Producer端压缩、Broker端保持、Consumer端解压缩。

但针对于broker的操作,在两种特殊情况下需要重新压缩(1.两端是不同的压缩算法;2.兼容新老版本进行消息格式转换,即需要解压再压缩)

 

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

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

一个压缩算法的优劣,有两个重要的指标:一个指标是压缩比,原先占100份空间的东西经压缩之后变成了占20份空间,那么压缩比就是5,显然压缩比越高越好;另一个指标就是压缩/解压缩吞吐量,比如每秒能压缩或解压缩多少MB的数据。同样地,吞吐量也是越高越好。

 

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

 

 

解压缩:

除了在Consumer端解压缩,Broker端也会进行解压缩。

注意了,这和前面提到消息格式转换时发生的解压缩是不同的场景。每个压缩过的消息集合在Broker端写入时都要发生解压缩操作,目的就是为了对消息执行各种验证。

我们必须承认这种解压缩对Broker端性能是有一定影响的,特别是对CPU的使用率而言。

 

 

其他解答:

  • 前面我们提到了Broker要对压缩消息集合执行解压缩操作,然后逐条对消息进行校验,有人提出了一个方案:把这种消息校验移到Producer端来做,Broker直接读取校验结果即可,这样就可以避免在Broker端执行解压缩操作。你认同这种方案吗?

  • 刚刚看到4天前京东提的那个jira已经修复了,看来规避了broker端为执行校验而做的解压缩操作,代码也merge进了2.4版本。有兴趣的同学可以看一下:https://issues.apache.org/jira/browse/KAFKA-8106

  • 不认同,因为网络传输也会造成丢失,但是我建议可以在消息里面使用一种消耗较小的签名方式sign,比如多使用位移等方式,broke端也这么操纵,如果签名不一致证明有数据丢失,同时签名的方式可以避免CPU大量消耗

 

  • 总结:

怎么压缩:

1、新版本改进将每个消息公共部分取出放在外层消息集合,例如消息的 CRC 值

2、新老版本的保存压缩消息的方法变化,新版本是对整个消息集合进行压缩

何时压缩:

1、正常情况下都是producer压缩,节省带宽,磁盘存储

2、例外情况 a、broker端和producer端使用的压缩方法不同 b、broker与client交互,消息版本不同

何时解压缩:

1、consumer端解压缩

2、broker端解压缩,用来对消息执行验证

优化:选择适合自己的压缩算法,是更看重吞吐量还是压缩率。其次尽量server和client保持一致,这样不会损失kafka的zero copy优势

 

如果多条消息组成消息集合发送,那是什么条件控制消息发送,如果是一条又是什么条件控制触发发送的

主要是这两个参数:batch.size和linger.ms。如果是生产了一条消息且linger.ms=0,通常producer就会立即发送者一条消息了。

 

把Kafka官网通读几遍然后再实现一个实时日志收集系统(比如把服务器日志实时放入Kafka)

 

Kafka 用户日志上报实时统计之分析与设计

http://www.jikexueyuan.com/course/1945.html?spm=a2c4e.10696291.0.0.5bc119a4UE4VoX&hmsr=teacher_dengjie_c1945   视频课程 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值