《Kafka技术内幕》读书笔记:Kafka入门

1. 介绍

1.1 Kafka流式处理平台

  一个流式数据平台,最重要的是要具备如下3个特点:

  • 类似消息系统,提供事件流的发布和订阅,既具备事件注入功能。
  • 存储事件流的数据节点具有故障容错的特点,即具备数据存储功能。
  • 能够对实时的事件流进行流式地处理和分析,即具备流处理功能。

作为一个流式数据平台,Kafka如何实现上面3个功能特点?

1.1.1 消息系统

  消息系统(也叫消息队列)主要有两种消息模型队列发布订阅

  • 队列模式:多个消费者读取队列,每条消息只发送给一个消费者
  • 发布-订阅模式:多个消费者订阅主题,主题的每条记录会发布给所有的消费者。

  Kafka使用消费组统一了两种消息模型

1.1.2 存储系统

  任何消息队列要做到发布消息和消费消息的解耦合,实际上都要扮演一个存储系统的角色,负责保存还没有被消费的消息。如果消息只是在内存中,一单机器宕机或重启,内存中的消息就会全部丢失。Kafka也不例外,数据写入到Kafka集群的服务器节点时,黑灰赋值多份来保障出现故障时仍能可用。为了保证消息的可靠存储,Kafka还允许生产者的生产请求在收到应答结果前,阻塞式地等待一条消息,直到它完全地复制到多个节点上,才认为这条消息写入成功。

1.1.3 流处理系统

  流式数据平台仅有消息的读取和写入、消息的存储是不够的,还需要流式数据处理能力。对于简单的处理,可直接使用Kafka提供的生产者API和消费者API来完成;但对于复杂的业务逻辑处理,Kafka提供了完整的流处理API,比如流的聚合、连接、各种转换操作。Kafka流处理框架内部解决很多流处理应用都会面临的问题:处理乱序或迟来的数据、重新处理输入数据、窗口和状态操作等。

1.2 Kafka将消息系统、存储系统。流处理系统组合在一起

  • 传统消息系统的流处理通常只会处理订阅动作发生后才到达的消息,无法处理订阅之前的历史数据
  • 分布式文件存储系统一般存储静态的历史数据,对历史数据的处理一般采用批处理方式

  Kafka将消息系统、存储系统、流处理系统都组合在一起,构成了以Kafka为中心的的流式处理数据处理平台。它既能处理最新的实时数据,也能处理过去的历史数据,其主要包括4种核心API:

  • 生产者API:应用程序发布事件流到一个或多个主题
  • 消费者API:应用程序订阅一个或多个主题,并处理事件流
  • 连接器API:将Kafka主题和已有的数据源进行连接,数据可以相互导入导出
  • 流处理API:从Kafka主题消费输入流,经过处理后,产生出输出流到输出主题
    《Kafka技术内幕》读书笔记:Kafka入门_第1张图片
      建立以Kafka为核心的流式数据管道,不仅要保证低延迟的消息处理,还需要保证存储的可靠性。在和离线系统集成时,将Kafka数据加载到批处理系统时,要保证数据不遗漏。

2. Kafka基本概念

  先抛出3个问题,在回答这些问题时需要引入很多概念:

  1. Kafka的主题与分区内部是如何存储的,它们有什么特点?
  2. 与传统的消息系统相比,Kafka的消费模型有什么特点?
  3. Kafka如何实现分布式的数据存储与数据读取?

2.1 分区模型

  Kafka集群为每个主题维护了分布式的分区( partition )日志文件,物理意义上可以把主题看作分区的日志文件( partitioned log)。 每个分区都是一个有序的、不可变的记录序列,新的消息会不断追加到提交日志( commit log)。 分区中的每条消息都会按照时间顺序分配到一个单调递增的顺序编号,作偏移量( offset ),这个偏移量能够唯一地定位当前分区中的每一条消息。每个分区的偏移量都从0开始,不同分区间的偏移量都是独立的,不会互相影响。
《Kafka技术内幕》读书笔记:Kafka入门_第2张图片
  如上图所示,主题有3个分区,每条消息包括键值和时间戳,消息到达后会按照规则到指定分区,得到一个分区内的自增偏移量,原始的消息内容和分配到的偏移量以及其他一些元数据信息会存储到分区日志文件中。

  • 传统消息系统在服务端保持消息的顺序,如果多个消费者消费同一个消息队列,服务端会以消息存储的的顺序依次发送给消费者。但由于消息是异步发送的,消息到达消费者的顺序可能是无序的,这样消息无法很好的保证消息会被顺序处理。
  • Kafka比传统的消息系统有更强的顺序性保证,以主题分区作为消息处理的并行单元。一个topic下的一个分区,在同一个消费组下,仅对应一个消费者(反之,同消费组下的多个消费者可以对应同一个分区),即这个消费者在消费组中就是这个分区的唯一读取线程,这样增强了顺序行,同时也做到了消费组内消费者的负载均衡。

2.2 消费模型

  基于推送模型的消息系统,由消息代理记录消费者的消费状态。消息代理在消息推送到消费者后,标记这条消息为已消费。但是,如果消息代理将消息发出后,消费进程挂掉或网络原因消费者没有收到消息时,就可能造成消息丢失。要保证消息的处理语义,消息代理发送完消息后,要设置状态为已发送,只有收到消费者的确认请求才更新为已消费,这需要在消息代理中记录所有消息的消费状态。
     Kafka采用拉取模型,由消费者自己记录消费状态,每个消费者独立地顺序读取每个分区的消息。

《Kafka技术内幕》读书笔记:Kafka入门_第3张图片
  如图所示,有不同消费组的两个消费者订阅了同一个主题,并且分到了同一个分区,消费者A的进度为3,消费者B的进度是6。消费者拉取的最大上限通过最高水位(watermark)控制,生产者最新写入的消息如果还没有到达备份数量,对消费者是不可见的。这种由消费者控制偏移量的优点是:消费者读取间不受影响,可以按照任意顺序消费消息,甚至消费者可以充值偏移量,重新读取之前已经消费过的消息
  在一些消息系统中,消息代理会在消息被消费后立即删除消息。如果有不同类型的消费者订阅同一个主题,消息代理可能需要冗余地存储同一条信息;或者等素有消费者都消费完才删除,这就需要消息消息代理跟踪每个消费者的消费状态,这种设计很大程度上限制了消息系统的整体吞吐量和处理延迟。Kafka的做法是生产者发布的消息会一直保存在Kafka集群中,不管消息有没有被消费。用户可以通过设置保留时间来清理过期数据。

2.3 分布式模型

  Kafka每个主题的多个分区日志分布式的存储在Kafka集群上,同时为了故障容错,每个分区都会以副本的方式复制到多个消息代理节点上,其中一个节点作为主副本(Leader),其他节点作为备份副本(Follower)。主副本会负责客户端的所有读写操作,备份副本仅仅从主副本同步数据。当主副本出现故障时,本分副本中的一个副本会被选择为新的主副本。即每个分区的副本中只有主副本负责接受读写,所以每个服务端都会作为某些主分区的副本,以及另外一些分区的本分副本。这样Kafka集群的所有服务端整体上对客户端是负责均衡的。
  Kafka的生产者和消费者和消费者对于服务端来说都是客户端,生产者客户端发布消息到服务端的指定主题,会指定消息所属的分区。根据消息是否有键采用不同的分区策略:有键则Hash,无键则轮询
《Kafka技术内幕》读书笔记:Kafka入门_第4张图片

  • Kafka的消费者通过订阅主题来消费消息,并且每个消费者都会设置一个消费组名称。因为生产者发布到主题的每一条消息都只会发送给消费组的的一个消费者(消息提交的分区对应的消费者)。所以如果要实现传统消息系统的"队列"模型,可以让每个消费者拥有相同的消费组名称,这样在这个消费组下,这些主题的消消息就会负载均衡到所有消费者;若果要实现发布-订阅模式,则可以消费者在不同的消费组,这样这写消息会广播给所有的消费者。
  • 同一个消费组下多个消费者互相协调消费工作,消费组成员列表由Kafka的消费组管理协议动态的维护,当一个消费者重新加入消费组,或者有消费者离开消费组时,主题下的分区会重新分配给消费组中的各个消费者。
  • Kafka的消费者在消费消息时,只保证在一个分区内消息的完全有序性,并不保证同一个主题中多个分区间的消息顺序。若果业务上要保证所以消息完全顺序一致,只能通过该主题设置一个分区来完成,这样吞吐量会降低。一般来说,只需要保证每个分区的有序性,在对消息加上键来保证相同键的所有消息落入同一个分区,就可以满足绝大多数应用。

3. Kafka的设计与实现

同样地先抛出3个问题:

  • 如何利用操作系统的优化技术来高效地持久化日志文件和加快数据传输效率?
  • Kafka的生产者如何批量地发送消息,消费者采用拉取模型带来哪些优点?
  • Kafka的副本机制如何工作,故障发生时怎么保证数据不丢失?

3.1 文件系统的持久化与数据传输效率

  人们普遍认为一旦涉及磁盘访问,读写的性能就严重下降。实际上,现代操作系统针对磁盘的读写已经做了一些优化方案来加快磁盘的访问速度。

  • 预读(read-ahead)提前将大的磁盘块读入内存。
  • 后写(write-behind)会将很多小的逻辑写操作合并起来组合成一个大的物理写操作。
  • 操作系统还会将主内存剩余的所有空闲的空间都用作磁盘缓存(dist cache/page cache),所有的磁盘读写操作都会经过统一的磁盘缓存(除了直接IO回绕过磁盘缓存)。
    《Kafka技术内幕》读书笔记:Kafka入门_第5张图片
    综合这几点优化特点,如果是针对磁盘的顺序访问,某些情况下它可能比随机的内存访问都要快,甚至可以和网络的速度相差无几。

  消息系统内的消息从生产者保存到服务端,再从服务端读取出来,数据的传出效率决定了生产者和消费者的性能。生产者如果每发送一条消息都直接通过网络发送到服务端,势必会造成过多的网络请求。如果我们能够将多条消息按照分区进行分组,并采用批量的方式一次发送一个消息集,并且对消息集进行压缩,就可以减少网络传输的带宽,进一步提高数据的传输效率。
  消费者要读取服务端的数据,需要将服务端的磁盘文件通过网络发送到消费者进程,而网络发送通常涉及不同的网络节点。传统的读取磁盘文件在每次发送网络时,都需要将页面缓存先保存到用户缓存,然后读取消息时再将其复制到内核空间,步骤如下:

  1. 操作系统将数据从磁盘中读取文件到内核空间里面的页面缓存。
  2. 应用程序将数据从内核空间读入用户空间缓冲区。
  3. 应用程序将读到的数据写会内核空间并放入socket缓冲区。
  4. 操作系统将数据从socket缓冲区复制到网卡接口,此时数据才能通过网络发送出去。

  结合Kafka的消息有多个订阅者的使用场景,生产者发布的消息一般会被不同的消费者消费多次,数据传输十分频繁,使用"零拷贝技术"只需将磁盘文件的数据复制到页面缓存一次,然后将数据聪明和页面缓冲直接发送到网络中(发送给不同的使用者可以重复使用同一个页面缓存),避免了重复的复制操作。这样,消息的使用速度基本上等同于网络连接的速度了。
《Kafka技术内幕》读书笔记:Kafka入门_第6张图片
  对比优化前后的两种方案。假设有10个消费者,传统复制方式的数据复制次数为4 x 10 = 40次,而"零拷贝技术"只需要将磁盘文件读入页面缓存1次加上10个消费者各读取页面缓存1次到网卡接口,共11次拷贝。显然减少了数据的复制次数,提高了消费性能。

3.2 消息的生产与消费

3.2.1 生产

  Kafka的生产者将消息直接发送给分区主副本的消息代理节点,并不需要经过中间路由层,为了做到这一点,所有消息代理节点在发送消息之前,会向任意一个代理节点请求元数据,并确定每条消息对应的目标节点(分区对应的主节点),然后发送出去。分区的选择规则如下:

  1. 生产者指定了分区;
  2. 键值存在,则可以使用"分区语义函数"将相同键的所有消息发布到同一分区。用户可通过Kafka暴露的分区语义接口指定键参与分区的规则。
  3. 采用轮询方式选定分区。

  前面说过Kafka会将生产者的消息按照分区分组,同一分区的消息批量压缩发送,减少了网络请求。对于缓冲的调节我们可以在生产者客户端设置消息大小上限延迟时间,达到消息大小上限或延迟时间,都会触发网络请求。

3.2.2 消费

  Kafka消息消费采用拉取模型,和生产者采用批量发送消息类似,消费者拉取消息可以一次拉取一批消息。拉取模型虽然不用消息代理记录消息的消费状态,但也会有一个缺点:消息代理没有数据或者数据量很少,消费者可能需要不断的轮询,并等待新数据。可以通过允许消费者拉取请求以阻塞式、长轮询的方式等待,直到有新的数据到来。我们可以在消费者客户端设置指定字节数量,表示在消息代理在还没有收集到足够的数据时,客户端的拉取请求不会立即返回。

3.3 副本机制和容错处理

  Kafka的每个Broker在分区的层面上互为备份。本分副本始终尽量保持与主副本的数据同步。备份副本的日志文件和主副本的日志总是相同的,它们都有相同的偏移量和相同顺序的消息。备份副本从主副本消费消息的方式和普通消费者一样,只不过备份副本会将处理写入到本地日志文件。
  分布式系统处理故障容错时,需要明确定义节点是否处于存活状态。Kafkaf对接点的存货定义有两个条件:

  • 节点必须和ZooKeeper保持会话;
  • 如果节点是某个分区的备份副本,它必须与主副本的写写操作进行复制,并且复制的进度不能太落后

  满足这两个条件,叫作"正在同步中"(in-sync)。每个分区的主副本会跟踪正在同步中的备份副本节点(In Sync Replicas,ISR)。如果一个备份副本挂掉、没有响应或者落后太多,主副本会将其从同步副本集合中移除。反之副本重新赶上主副本,它就会被重新加入集合中。
  在Kafka中,一条消息只有被ISR集合中的所有副本都运用到本地的日志文件,才会认为消息被成功提交了。任何时刻,只要ISR至少有一个副本是存活的,Kafka就可以保证消息被提交就不会丢失。

你可能感兴趣的:(kafka,kafka)