Kafka系列二

川泽纳污,山薮藏疾,瑾瑜匿瑕。(《左传·宣公十五年》)

分区

Kafka 的消息组织方 式实际上是三级结构:主题 - 分区 - 消息

问题一:为什么 Kafka 要做这样的设计?为什么使用分区的概念而不是直接使用多个主题呢?

  1. 分区的目的是:提供负载均衡,实现系统的高伸缩性【Scalability】
  2. 每条消息 只会 保存在某一个分区中。
  3. 不同的分区可以放在不同机器上;
  4. 数据的读写都是基于 分区 这个粒度;

生产者 分区策略

分区策略是决 定生产者将消息发送到哪个分区的算法

轮询策略

Round-robin 策略,即顺序分配,kafka默认分区策略。

随机策略

Randomness 策略, 随机的将消息放置到任意一个分区上。

按消息键保序策略

根据业务指定合适的key进行分区。

生产者 压缩

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

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

问题一:Producer、Broker压缩算法不一致

  1. Producer压缩一遍
  2. Broker解压,再压缩一遍。

但如果配置一致,则broker保持就好了。

问题二:版本不同,Broker端发生消息格式转化

消息格式转换主要是为了兼容老版本的消费者程序

  1. Broker解压,再压缩一遍, 从而支持老版本
  2. 丧失Zero Copy的特性,
Zero Copy”就是“零拷贝

当数据在磁盘和网 络进行传输时避免昂贵的内核态数据拷贝,从而实现快速的数据传输

压缩算法

压缩算法的指标

  1. 压缩比,原先占 100 份空 间的东西经压缩之后变成了占 20 份空间,那么压缩比就是 5,显然压缩比越高越好
  2. 压缩 / 解压缩吞吐量,比如每秒能压缩或解压缩多少 MB 的数据。同样地,吞 吐量也是越高越好。

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

最佳实践

CPU允许的情况下,开启压缩zstd,进而也可以节省带宽。

避免消息丢失

如何配置 Kafka 无消息丢失?

对一个问题的回答,先要确认问题是什么,概念的边界

Kafka 的世界里什么 才算是消息丢失,或者说 Kafka 在什么情况下能保证消息不丢失。这点非常关键,因为很多时候我们容易混淆责任的边界,如果搞不清楚事情由谁负责,自然也就不知道由谁来出解决方案了。

Kafka 只对“已提交”的消息(committed message)做有限度的持久化 保证。

  1. 已提交:只有broker确认收到才算是已提交;
  2. 有限度:凡事有例外,如果broker全部出现问题等。

生产者

问题: 生产者发送了消息,但是Kafka没有收到

回答问题可以: 概念+原因,概念是为了做铺垫,让原因更直白

  1. Kafka Producer 是异步发送消息的,也就是说如果你调用的是 producer.send(msg) 这个 API,那么它通常会立即返回,但此时你不能认为消息发送已成功完成。。 “fire and forget”,翻译一下就是“发射后不管“;
  2. 造成丢失的原因不限于:网络抖动、格式不符合broker<消息过大>等;
  3. 采用 回调+重试的方式,producer.send(msg, callback)

消费者

问题:消费者程序丢失数据

  1. 问题定位:消费者要消费的数据不见了。
  2. 涉及的概念:Consumer 程序有 个“位移”的概念,表示的是这个 Consumer 当前消费到的 Topic 分区的位置。

位移可以类比读书的书签,正常来说,你这次读到100页,下次继续读完全没有问题,问题是:你本计划读到100页,就把书签放到100的位置,实际上读到90页就跑出去完了,下次直接从100页开始,其中就少读了10页

解决的方法类似与生产者,维持先消费消息(阅读),再更新位移(书签)的顺序

最佳实践

Producer参数
  1. 使用 producer.send(msg, callback)。
  2. 设置 acks = all。表明所有副本 Broker 都要接收到消息,该消息才算是“已提交”。 这是最高等级的“已提交”定义。可能对某些业务有点浪费
  3. retries 为一个较大的值。重试次数;
Broker 端的参数
  1. unclean.leader.election.enable = false: 控制的是哪 些 Broker 有资格竞选分区的 Leader。如果一个 Broker 落后原先的 Leader 太多,那么 它一旦成为新的 Leader,必然会造成消息的丢失。故一般都要将该参数设置成 false, 即不允许这种情况的发生。
  2. replication.factor >= 3: 最好将 消息多保存几份,毕竟目前防止消息丢失的主要机制就是冗余。
  3. min.insync.replicas > 1: 控制的是消息至少要被写入到多少个副本才算是“已提交”。设置成大于 1 可以提升消息持久性。在实际环境中千万不要使用默认值 1。

确保 replication.factor > min.insync.replicas;
推荐设置成 replication.factor = min.insync.replicas + 1。

如果两者相等,那么只要有一个副本挂 机,整个分区就无法正常工作了。我们不仅要改善消息的持久性,防止数据丢失,还要 在不降低可用性的基础上完成

幂等与事务

消息交付可靠性保障: 是指 Kafka 对 Producer 和 Consumer 要处理的消息提供什么样的承诺

  1. 最多一次(at most once):消息可能会丢失,但绝不会 被重复发送。
  2. 至少一次(at least once)【默认】:消息不会丢失,但有可能被 重复发送。
  3. 精确一次(exactly once):消息不会丢失,也不会被重 复发送。

Kafka 是怎么做到精确一次的呢?简单来说,这是通过两种机制:幂等性(Idempotence)和事务 (Transaction)

幂等性 Producer

幂等性有很多好处,其最大的优势在于我们可以安全地重试 任何幂等性操作,反正它们也不会破坏我们的系统状态。

在 Kafka 中,Producer 默认不是幂等性的,仅需要设置一个 参数即可,即 props.put(“enable.idempotence”, ture),props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CO NFIG, true)。

作用范围

  1. 只能保证单分区上的幂等性,即一个幂等性 Producer 能够保证某个主题的一个分区上不出现重复消 息,它无法实现多个分区的幂等性。
  2. 它只能实现单会 话上的幂等性,不能实现跨会话的幂等性。这里的会话,你 可以理解为 Producer 进程的一次运行。当你重启了 Producer 进程之后,这种幂等性保证就丧失了

事务

实现多分区以及多会话上的消息无重复,应该怎么做呢?答案就是事务(transaction)或 者依赖事务型 Producer。

事务提供的安全性保障是经典的 ACID,即原 子性(Atomicity)、一致性 (Consistency)、隔离性 (Isolation) 和持久性 (Durability)。

事务型 Producer 能够保证将消息原子性地写入到多个分区 中。这批消息要么全部写入成功,要么全部失败。和幂等性 Producer 一样,开启 enable.idempotence = true。

Consumer 端,读取事务型 Producer 发送的消息也是需要一些变更的。修改起来也很简单,设置 isolation.level 参数的值即可。当前这个参数有两个取值:

  1. read_uncommitted:这是默认值,表明 Consumer 能 够读取到 Kafka 写入的任何消息,
  2. read_committed:表明 Consumer 只会读取事务型 Producer 成功提交事务写入的消息。当然了,它也能看 到非事务型 Producer 写入的所有消息。

小结

天下没有免费的午餐。比起幂等性 Producer, 事务型 Producer 的性能要更差,在实际使用过程中,我们 需要仔细评估引入事务的开销,

参考

极客时间—Kafka核心技术与实战
胡夕 - 博客园 –《Apache Kafka实战》