Kafka 设计思想

了解 Kafka 从《Kafka 权威指南》开始的,读着读着有点懵逼,所以我打算从 Kafka 设计上的一些点来总结一下。

不止于 Kafka 的各种细节,而是学习它的这种设计思想,如果能有十分之一用到自己设计的系统中,估计也会很牛逼了。

铭记于心:

  1. 要做什么;
  2. 这么做的设计思想是啥;
  3. 如何实现这种设计思想。

Kafka 主要目标

使用推送和拉去模型解耦生产者和消费者;

为消息传递系统中的消息提供数据持久化,以便支持多个消费者;

通过系统优化实现高吞吐量;

系统可以随着数据流的增长进行横向扩展。

发布与订阅系统:解耦

数据消息的发送者不会直接把消息发送给接收者。

发布者以某种方式对消息进行分类,接收者订阅它们,以便接受特定类型的消息。

发布与订阅系统一般会有一个 broker,也就是发布消息的中心点。

发布与订阅系统的演进

持续的数据流

Kafka 持续的数据流,是一个流平台。以数据流为中心。

一个主题的数据可以看成一个流,不管它有多少个分区。

流是一组从生产者移动到消费者的数据。

主题:对消息进行分类

Kafka 的消息通过主题进行分类。

分区:实现数据冗余和伸缩性

主题被分为若干个分区,一个分区就是一个提交日志。

日志以追加的方式写入分区,然后以先入先出的顺序读取。

一个主题一般包含几个分区,因此无法在整个主题范围保证消息的顺序,但可以保证单个分区的顺序。

Kafka 通过分区来实现数据冗余和伸缩性。

分区可以分布在不同的服务器上,也就是一个主题可以横跨多个服务器,以此提供比单个服务器更强大的能力。

ProducerRecord 对象包含了目标主题、键和值。

Kafka 的消息是一个个键值对,ProducerRecord 对象可以只包含目标主题和值,键可以设置为默认的 null,不过大多数应用会用到键。

键:控制消息写入不同的分区

消息可以有一个可选的元数据,也就是键。

键也是一个字节数组,和消息一样对 Kafka 也没有特别的含义。

消息以一种可控的方式写入不同的分区时,会用到键。

例如,可以为键生成一个一致性散列值,然后使用散列值对主题分区数进行取模,为消息选取分区。

这样可以保证具有相同键的消息总是被写到相同的分区上。

键的两个用途:可以作为消息的附加信息,也可以用来决定消息被写到主题的哪个分区。

拥有相同的键被写到同一分区。

如果键值为 null,并且使用了默认的分区器,那么记录将被随机地发送到主题内各个可用的分区上。

分区器使用轮询算法将消息均衡地分布到各个分区上。

如果键值不为空,并且使用了默认的分区器,那么 Kafka 会对键进行散列(使用 Kafka 自己的散列算法,即使升级 Java 版本,散列值也不会发生变化),然后根据散列值把消息映射到特定的分区上。

同一个键总是被映射到同一个分区上,所以进行映射时,会使用主题的所有分区,而不仅仅是可用的分区。

如果写入数据的分区不可用,那么就会发生错误。

当然你也可以实现自定义的分区策略。

分区再均衡:添加新分区后的操作

在主题发生改变时,如管理员添加了新的分区,会发生分区重分配。

分区的所有权从一个消费者转移到另一个消费者,这种行为被称为再平衡。

再平衡非常重要,它为消费者群组带来了高可用性和伸缩性(可以放心地添加或移除消费者)。

但是再均衡期间,消费者无法消费消息,造成整个群组一小段时间的不可用。

另外,当分区被重新分配给另一个消费者时,消费者当前的读取状态会丢失,它可能还需要去刷新缓存。

群组协调器:负责分区分配,为消费者分配分区

消费者的线程安全

批次:减少网络开销

批次就是一组消息,这些消息属于同一个主题和分区。

如果每一个消息都单独穿行于网络,会导致大量的网络开销,把消息分成批次可以减少网络开销。

不过这是在时间延迟和吞吐量之间作出权衡:批次越大,单位时间处理的消息就越多,单个消息处理的时间就越长。

压缩

批次数据会被压缩,这样可以提高数据的传输和存储能力,但是要做更多的计算处理。

模式:保证数据格式的一致性

JSON 和 XML 缺乏强类型的处理能力,不同版本之间的兼容性也不是很好。

Kafka 使用了 Avro(Hadoop 开发的一款序列化框架)。

Avro 提供了一种紧凑的序列化格式,模式和消息体是分开的,当模式发生变化时,不需要重新生成代码。

序列化器:推荐使用 Avro 序列化器作为备选方案

创建一个生产者必须指定序列化器,当然也可以使用默认的字符串序列化器,Kafka 还提供了整型和字节数组序列化器,当然也可以使用自定义序列化器。

不同版本的序列化器和反序列化器之间可能还存在兼容性问题。

所以不建议使用自定义的序列化器,而是使用已有的序列化器和反序列化器,如 JSON、Avro、Thrift 或 Protobuf。

生产者

Kafka 的客户端分类两种基本类型:生产者和消费者。

一般情况下,一个消息会被发布到一个特定的主题上。

生产者默认把消息均衡地分布到主题的所有分区上,而并不关心特定消息会被写到哪个分区。

在某些情况下,生产者会把消息直接写到指定的分区。

这通常是通过消息键和分区器来实现的,分区器为键生成一个散列值,并将其映射到指定的分区上。

这样可以保证同一个键的消息会被写到同一个分区上。

消费者

消费者订阅一个或者多个主题,并按消息生成的顺序读取它们。

Kafka 支持多个消费者从一个单独的消息流上读取数据,而且消费者之间互不影响。

这与其他消息队列不同,其他消息队列的消息一旦被一个客户端读取,其他客户端就无法再读取它。

如果生产者往主题写入的速度超过了应用程序消费数据的速度,只使用单个消费者处理消息,应用程序永远跟不上消息生成的速度。

我们可以使用多个消费者从同一个主题读取消息,对消息进行分流。

消费群组:多个消费者共同读取一个主题

群组保证每个分区只能被一个消费者使用。

下图群组中有 3 个消费者同时读取一个主题。其中两个消费者各自读取一个分区,另外一个消费者读取其他两个分区。

消费者与分区之间的映射通常被称为消费者对分区的所有权关系。

消费者群组从主题读取消息

每个消费者只处理部分分区的消息,这就是横向伸缩的主要手段。

我们有必要为主题创建大量的分区,在负载增长时可以加入更多的消费者。

不过要注意不要让消费者的数量超过主题分区的数量,多余的消费者只会被闲置。

多个消费者群组消费同一个主题:

一个新的消费者加入群组时,它读取的时原本由其他消费者读取的消息。

当一个消费者被关闭或者崩溃时,它就离开群组,原本由它读取的分区将由群组里的其他消费者来读取。

提交和偏移量

Kafka 不会像其他 JMS 队列那样需要得到消费者的确认,这是 Kafka 的一个独特之处。

相反,消费者可以使用 Kafka 来追踪消息在分区里的位置(偏移量)。

把更新分区当前位置的操作叫做提交。

消费者往一个叫做 _consumer_offset 的特殊主题发送消息,消息里面包含每个分区的偏移量。

如果消费者一直处于运行状态,那么偏移量就没什么用。

如果消费者发生崩溃或者有新的消费者加入群组,就会触发再平衡,完成再平衡后,每个消费者可能分配到新的分区,而不是之前处理的那个。

如果要继续完成工作,消费者需要读取每个分区最后一次提交的偏移量,然后从指定偏移量指定的地方继续处理。

如果提交的偏移量小于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息就会被重复处理。

如果提交的偏移量大于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息将会丢失。

再均衡监听器:为消费者分配新分区或者移除旧分区时,执行一些应用程序代码

比如失去分区的所有权之前,需要处理在缓冲区积累下来的记录,可能还要关闭文件句柄、数据库连接等。

偏移量:同步提交,异步提交,提交当前,提交特定

broker 和集群:复制、冗余和扩展

一个独立的 Kafka 服务器被称为 broker。

broker 接受来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。

broker 为消费者提供服务,对读取分区的请求作出响应,返回已经提交到磁盘的消息。

broker 是集群的组成部分。

集群控制器:负责管理工作

broker 是集群的组成部分。每个集群都有一个 broker 同时充当了集群控制器的角色(自动从集群的活跃成员中选举出来)。

控制器负责管理工作,包括将分区分配给 broker 和监控 broker。

在集群中,一个分区从属于一个 broker,该 broker 被称为分区的首领。

分区复制:为分区提供了消息冗余

一个分区可以分配给多个 broker,这个时候会发生分区复制。

这种复制机制提供了消息冗余,如果有一个 broker 失效,其他 broker 可以接管领导权。

不过相关的生产者和消费者都要重新连接到新的首领。

集群里的分区复制

保留消息:持久化

Kafka 可以按照要求保存数据,多久都可以。

Kafka 按照一定的顺序持久化保存的。

Kafka 的数据分布在整个系统里,具备数据故障保护能力和性能伸缩能力。

Kafka broker 默认的消息保留策略是这样的:要么保留一段时间(比如 7 天),要么保留到消息达到一定大小的字节数(比如 1GB)。

当消息达到这些上限时,旧消息就会过期并被删除,所以在任何时刻,可用消息的总量都不会超过配置参数所指定的大小。

主题可以配置自己的保留策略。

基于磁盘的存储:保证数据不会丢失

允许消费者非实时地读取消息。

消息被提交到磁盘,根据设置的保留规则进行保存。

每个主题可以设置单独的保留规则,以便满足不同的消费者的需求。

持久化数据可以保证数据不会丢失。

消费者可以被关闭,但是消息会继续保留在 Kafka 里,消费者可以继续从上次中断的地方继续处理消息。

多集群:数据更加安全

使用多个集群的原因:

  • 数据类型分离
  • 安全需求隔离
  • 多数据中心(灾难恢复)

偏移量:控制读取状态

消费者通过检查消息的偏移量来区分已经读取过的消息。

在给定的分区里,每个消息的偏移量都是唯一的。

消费者把每个分区最后读取的消息偏移量保存到 Zookeeper 或 Kafka 上,如果消费者关闭或者重启,它的读取状态不会丢失。

可复制

零复制

复制协议

控制器

存储层

伸缩性

开发阶段可以先使用单个 broker,再扩展到包含 3 个 broker 的小型开发集群,然后随着数据不断增长,部署到生产环境可能包含上百个 broker。

对在线集群进行扩展丝毫不影响整体系统的可用性。

要提高集群的容错能力,需要配置较高的复制系数。

高性能

通过横向扩展生产者、消费者和 broker,Kafka 可以轻松处理巨大的消息流。

在处理大量数据的同时,还能保证亚秒级的消息延迟。

消息发送:同步和异步

同步发送消息需要等待 Kafka 集群的回应。假设一个消息在应用程序和 Kafka 集群之间来回要 10ms,那么发送 100 个消息需要 1s。

如果只发送消息而不等待响应,那么发送 100 个消息的时间会少很多。

大多数时候,我们并不需要等待响应——尽管 Kafka 会把目标主题、分区信息和消息的偏移量发送回来,但对于应用程序来说并不是必须的。

猜你喜欢

转载自www.cnblogs.com/tuhooo/p/11311823.html
今日推荐