一、Kafka是什么?

Kafka 一个分布式的、多分区、多副本、多订阅者,流式数据处理的平台。它具有消息系统(发布、订阅)的能力,也有实时流式数据处理和分析的能力,我们更多偏向于把它当做消息队列系统来使用。

  • 以时间复杂度O(1)的方式提供持久化能力,即使应对TB以上的数据也能保证常数时间复杂度的访问性能
  • 高吞吐率,廉价商用机器也能作做到单机每秒100K条以上的消息传输
  • 支持消息分区、及分布式消费,同时保证每个Partition内的消息顺序传输
  • 同时支持离线数据处理和实时数据处理
  • 支持在线水平拓展

二、相关名词解释

(1)Broker 代理服务器

一个kafka代理服务器也被称之为Broker,它接收生产者发送的消息并存入磁盘。

Broker同时服务消费者拉取分区消息的请求,返回目前已经提交的消息。

(2)Topic 主题

在kafka中消息以主题(topic)来分类,每一个主题都对应一个消息队列。

(3)Partition 分区

topic物理上的分组,一个topic可以分为多个partition,每一个partition是一个有序的队列。

(4)Segment 段

partition物理上由多个segment组成。

(5)offset 偏移量

每个partition都由一系列有序的、不可变的消息组成,这些消息被连续的追加到partition中。partition中每个消息都有一个连续的序号叫做offset,用于partition唯一标记一条信息。

(6)Leader/Follower 主副本/从副本

分区的副本。为了保障数据的高可用,分区都会有一些副本,每个分区都会有一个leader主副本负责读写数据,follower从副本只负责同步leader主副本数据,不对外提供服务。

(7)Consumer group 消费者组

消费者组,由多个消费者组成,一个组内只会有一个消费者去消费一个分区的消息。

(8)Coordinator 协调者

协调者,主要是为消费者组分配分区以及重平衡操作(Rebalance)。

(9)Producer 生产者

生产者,负责发送消息

(10)Consumer 消费者

消费者,负责消费消息

三、消息队列模型

对于传统的消息队列系统支持两个模型:点对点、发布订阅;

  • 点对点:消息只能被一个消费者消费,消费完后就删除
  • 发布订阅:相当于广播模式,消息可以被所有的消费者消费

Kafka通过消费者组实现了同时支持上述2个模型,如果说消费者都属于一个消费者组,那么消息只能被一个消费者消费,即点对点的模型;如果每个消费者都是一个单独的消费者组,那么便是发布订阅模型。

四、kafka 通信过程原理

  1. 生产者启动的时候会指定bootstrap.servers,通过指定的broker地址,kafka就会和这些broker创建tcp连接。
  2. 连接到任意一台broker之后,然后发送获取元数据请求(有哪些主题、主题有哪些分区、分区有哪些副本、分区的主从副本等信息)
  3. 接着创建和所有的broker的tcp连接
  4. 发送消息
  5. 消费者和生产者一样,也会指定bootstrap.servers属性,然后选择一台broker创建tcp连接,发送请求到找到协调者所在的broker
  6. 再和协调者broker创建tcp连接,获取元数据
  7. 根据分区leader节点所在的broker节点,和这些broker分别创建连接
  8. 消费消息

image-20240304235511766

五、发送消息时如何选择分区?

主要有两种方式:

  1. 轮询,安装顺序消息依次发送到不同的分区
  2. 随机,随机发送到某个分区

如果消息指定key,那么会根据消息的key进行hash,然后对partition数量取模,绝对落在哪个分区上。所以,对于相同key的消息来说,总会发送到一个分区上,也就是我们常常说的消息分区有序性。

六、分区的意义及优势?

若不分区的话,消息只能落在一个节点上,这样就算再好的服务器,性能也是承受不住的。

实际上,分布式系统都面临这个问题,要么收到消息时,进行消息切分,要么提前切分。kafka选择了前者,通过分区可以把数据均匀的分布到不同节点。

分区带来负载均衡和横向扩展的能力。

发送消息时可以根据分区的数量落在不同的kafka服务器节点上,提升了并发写消息的性能,消费消息的时候有和消费者绑定了关系,可以从不同节点的不同分区消费消息,提高的读取消息的能力。

另外一个就是分区引入了副本,冗余的副本保证了kafka的高可用和高持久性。

七、消费者组和消费者重平衡

kafka中消费者组订阅topic主题的消息,一般来说消费者的数量最好和所有主题分区的数量保持一一致。

  • 消费者数量<分区数量,必然会有一个消费者消费多个分区
  • 消费者数量>分区数量,必然有一个消费者没有分区可以消费

image-20240305001717005

消费者消费的分区是怎么分配的,有先加入的消费者时候怎么办?

由协调器来组织完成,每一次新的消费者加入消费者组时,都会先向协调器发送请求,从而获取分区的分配,这个分区分配的算法逻辑由协调者来完成。

重平衡就是指有新消费者加入的情况。例如起初我们只有消费者A在消费数据,后来加入了消费者B和C,这时候分区就需要被重新分配了,这就是重平衡,也叫做再平衡,这个期间会导致整个消费者停止工作,重平衡期间都无法消费消息。

发送重平衡的决定因素:消费者数量、主题数量(用正则订阅的主题)、分区数量,其中任何一个改变都会触发重平衡

重平衡的过程:

重平衡的机制依赖于消费者和协调器直接的心跳来维持,消费者会有一个独立的线程会定时去发送心跳给协调者,可以通过heartbeat.interval.ms来控制发送心跳的间隔时间。

  1. 每个消费者第一次加入组的时候都会向协调者发送join group请求,第一个发送请求的消费者会成为“群主”,协调者会返回群成员列表给群主
  2. 群主执行分区分配策略,然后把分配结果通过sync group请求发送给协调者,协调者收到分区分配结果
  3. 其他成员想协调者发送 sync group,协调者把每个消费者的分区分别响应给他们

image-20240305003002497

八、分区分配策略

主要有3种分配策略:

range,默认策略,对分区进行排序,越靠前的消费者能够分配到的分区数越多。

image-20240305003219550

默认策略的弊端(根据主题进行分配的)在于如果消费者组订阅了多个主题,就可能会导致分区分配不均衡。

image-20240305003639102

RoundRobin

这个就是我们常说的轮询,会根据所有的主题进行轮询分配,不会出现range那种主题越多可能导致分区分配不均衡的问题。

image-20240305003908189

Sticky

粘性策略:在分配均衡的前提下,让分区的分配更小的改动。

比如P0\P1分配给消费者A,那么下一次尽量还是分配给A。这样做的好处是连接可以复用,要消费消息总是要和broker去连接的,如果能保持上一次分配分区的话,那就不用频繁的销毁创建连接了。

九、如何保证消息可靠?

什么是消息可靠?就是如何确保消息一定能发送到服务器并进行存储,并且发生宕机等异常场景,能够从备份数据中恢复。

消息的可靠性需要从3方面来保证:

  • 第一:发送端能否保证发送的消息是可靠的
  • 第二:kafka broker 自身保证不丢数据,安全落盘
  • 第三:接收端能够可靠的消费消息

发送端:通过ack机制,定义不同策略。

发送端如何保证高可用?源于kafka健壮的副本(replication)策略。通过调节其副本相关参数,可以使得kafka在性能和可靠性之间运转的游刃有余。replication的数量可以在server.properties中配置。

kafka中的消息是以topic进行分类的,生产者通过topic向kafka broker发送消息,消费者通过topic读取数据。然而topic在物料层面又能以partition为分组,一个topic可以分成若干个partition,kafka中的消息又以顺序的方式存储在文件中。

kafka中的topic的partition有N个副本(replicas)。N个replicas中,其中一个replicas为leader,其他都是follower,leader处理partition中的读写请求,其余follower定期去复制leader上的数据。

如果leader发生故障或者挂掉,一个新的leader被选举并接收客户端的消息成功写入。kafka确保从同步副本列表中选举一个副本为leader。

当生产者向leader发生数据时,可以通过request.required.acks参数来设置可靠性的级别:

1:默认级别,意味着生产者在ISR中的leader已成功收到数据并确认后发送下一条信息。如果leader宕机了,则会丢失数据。

0:这个意味着生产者无需等待来着broker的确认而继续发送下一批消息,这种情况下消息的传输效率是最高的,但数据可靠性是最低的。此时retires参数失效,因为客户端无法判断是否失败,也就无法重试。

all/-1:生产者需要等待ISR中所有的follower都确认接收到数据后才算完成一次发送,可靠性最高,但这样也不能保证数据不丢失,比如ISR中只有一个leader时,就会变成acks=1的情况。

retries = N,设置一个非常大的值,让生产者发送消息失败后不断重试。

kafka自身:消息的写入是通过page cache异步写入磁盘的,因此仍然存在丢失消息的可能。针对kafka自身丢消息可能设置的参数:

  • replication.factor=N,设置一个较大的值,保证至少有2个或以上的副本;
  • min.insync.replicas=N,代表消息如何才能被认为是写入成功,设置大于1的数,保证至少写一个或者以上的副本才算写入成功
  • unclear.leader.election.enable=false,这个设置意味着没有完全同步的分区副本不能成为leader副本,如果true的话,那些没有完全同步的副本成为leader副本后,就会有消息丢失的风险。

接收到:若配置了自动提交,万一消费的数据没有处理完,就自动提交了offset,然后consumer直接宕机了,未处理完的数据丢失了,下次也消费不到了。故而消费端是靠offset来保证的。

消费者丢失数据,通过关闭自动提交即可,改为业务处理成功后手动提交。

因为重平衡发送的时候,消费者会去读上一次的偏移量,自动提交默认是5秒一次,这个会导致重复消费或者丢失消息。

enable.auto.commit=false,设置为手动提交。

auto.offset.reset=earliest,这个参数代表没有偏移量可以提交或者broker上不存在偏移量的时候,消费者如何处理。earliest代表从分区的开始位置读取,可能会重复读取消息,但不会丢失。另外一种latest表示从末尾读取,有概率丢失消息。

十、副本同步原理

Kafka的副本分为leader主副本和follower从副本。其中只有leader主副本会对外提供辅助,follower从副本只负责与leader保持数据同步,作为数据冗余容灾的作用。

在Kafka中所有的副本集合统称为AR(assigned replicas),和leader主副本保持同步的副本集合称之为ISR(InSyncReplicas)

ISR是一个动态集合,维持这个集合通过replica.lag.time.max.ms参数来控制,这个代表落后leader副本的最长时间,默认为10秒,所以只要follower副本没有落后leader副本10秒以上,就认为是和leader是同步的

HW(high watermark):高水位,也叫做复制点,表示副本间同步的位置。

LEO(log end offset):下一条待写入消息的位移

如下图所示,04绿色表示已经提交的消息,这些消息已经在副本之间进行同步,消费者可以看见这些消息并且进行消费,46黄色的则是表示未提交的消息,可能还没有在副本间同步,这些消息对于消费者是不可见的。

图片

副本间同步的过程依赖的就是HW和LEO的更新,以他们的值变化来演示副本同步消息的过程,绿色表示Leader副本,黄色表示Follower副本。

首先,生产者不停地向Leader写入数据,这时候Leader的LEO可能已经达到了10,但是HW依然是0,两个Follower向Leader请求同步数据,他们的值都是0。

image-20240305012659056

然后,消息还在继续写入,Leader的LEO值又发生了变化,两个Follower也各自拉取到了自己的消息,于是更新自己的LEO值,但是这时候Leader的HW依然没有改变。

image-20240305012712392

此时,Follower再次向Leader拉取数据,这时候Leader会更新自己的HW值,取Follower中的最小的LEO值来更新。

image-20240305012730661

之后,Leader响应自己的HW给Follower,Follower更新自己的HW值,因为又拉取到了消息,所以再次更新LEO,流程以此类推。

image-20240305012740752

十一、Kafka为什么快?

  • 顺序IO:kafka写消息到分区采用顺序追加的方式,也就是顺序写入磁盘,不是随机写入,这个速度比普通的随机IO快非常多,几乎可以和网络IO相媲美。
  • Page Cache 和零拷贝:kafka在写入消息数据的时候通过mmap内存映射的方式,不是真正立刻写入磁盘,而是利用操作系统的文件缓存page cache异步写入,提高写入消息的性能,另外消费消息的时候又通过sendfile实现了零拷贝。
  • 批处理和压缩:kafka发送消息时,不是一条条的发送的,而是会把多条消息合并为一个批次进行处理发送,消费也是一个道理一次拉取一批次的消息进行消费。并且producer、broker、consumer都使用了优化后的压缩算法,发送和消费消息使用压缩节省了网络传输的开销,broker存储使用压缩降低了磁盘存储空间。

十二、CAP原理

CAP是“一致性(Consistency)、可用性(Availability)以及分区容忍性(Partition Tolerance)”的缩写。

1)C 即一致性(Consistency):要求分布式系统要保障,一旦数据写入到分布式存储系统之后,所有访问数据的请求不管是访问分布式存储的那个节点上,查到到该写入的数据都是一致的,不能出现3个副本中有的副本有该条数据,有的副本没有该条数据(插入问题),更不能是有的副本该条数据和另外一个副本该条数据是不一样的(更新问题)。

2)A 即可用性(Availability):可用性就是要求分布式系统要保障,一旦数据写入到分布式存储系统之后,所有访问该数据的请求都可以正常响应,不管该数据能不能查到,又或者该条数据查出来的一不一致,不能出现查询该数据时出现长期等待或者报错的发生。

3)P 即分区容忍性 (Partition Tolerance):分区容忍性时要求分布式系统要保障,一旦数据写入到分布式存储系统的主本文件后,因为网络的的问题无法同步到副本的时候,系统依然能够对外提供服务,网络在分布式系统来讲是不敢绝对保障的,如果因为网络问题,导致写入数据无法向副本同步,这时候就是分区的情况出现,但网络的绝对的可靠从科学角度上来讲是无法做到的,因此,所有分布式系统必须是满足“P”的存在,不然就只能使用单机系统来解决,那就不是分布式系统了。

综上所述,分布式系统基本上所有的都必须满足“P”,在“A”和“C”之间来选择,要么是AP,要么是CP。

CAP原理定义的就是3个原则在分布式存储系统中只能满足其中两个,无法全部都满足,因为要求网络绝对的可靠是不可能的,因此,所有的分布式系统都必须满足P,然后AP和CP之间做出抉择,是保性能牺牲一致(AP),或者是保一致牺牲性能(CP)要根据实际的应用场景来确定。

Kafka提供了一些配置,用户可以根据具体的业务需求,进行不同的配置,使得Kafka满足AP或者CP,或者它们之间的一种平衡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
比如下面这种配置,就保证强一致性,使得Kafka满足CP。任意写入一条数据,都需要等到replicate到所有节点之后才返回ack;接下来,在任意节点都可以消费到这条数据,即是在有节点宕机的情况下,包括主节点。

replication.factor = 3
min.insync.replicas = 3
acks = all

而下面的配置,就主要保证可用性,使得Kafka满足AP。对于任意写入一条数据,当主节点commmit了之后就返回ack;如果主节点在数据被replicate到从节点之前就宕机,这时,重新选举之后,消费端就读不到这条数据。这种配置,保证了availability,但是损失了consistency。

replication.factor = 3
min.insync.replicas = 3
acks = 1

还有一种配置是公认比较推荐的一种配置,基于这种配置,损失了一定的consistency和availability,使得Kafka满足的是一种介于AP和CP之间的一种平衡状态。因为,在这种配置下,可以在容忍一个节点(包括主节点)宕机的情况下,任然保证数据强一致性和整体可用性;但是,有两个节点宕机的情况,就整体不可用了。

replication.factor = 3
min.insync.replicas = 2
acks = all

对于这种配置,其实Kafka不光可以容忍一个节点宕机,同时也可以容忍这个节点和其它节点产生网络分区,它们都可以看成是Kafka的容错(Fault tolerance)机制。

除了上面的几个常用配置项,下面这个配置项也跟consistency和availability相关。这个配置项的作用是控制,在所有节点宕机之后,如果有一个节点之前不是在ISR列表里面,启动起来之后是否可以成为leader。当设置成默认值false时,表示不可以,因为这个节点的数据很可能不是最新的,如果它成为了主节点,那么就可能导致一些数据丢失,从而损失consistency,但是却可以保证availability。如果设置成true,则相反。这个配置项让用户可以基于自己的业务需要,在consistency和availability之间做一个选择。

unclean.leader.election.enable=false