Kafka系列:基本概念、概述、设计、集群

Kafka是一种 消息中间件 。举个例子,一个网站在用户进行注册的时候,后台的操作包括发送邮箱、写入用户表、写入日志等等,但是一般都不会等这些完全处理完才告诉你注册成功。这一系列的操作通常是异步的。

Kafka系列:基本概念、概述、设计、集群

但是异步跟消息中间件有什么联系呢?我们试想一下,假如没有消息中间件,后台操作的顺序假设是

  • (1)写入用户表
  • (2)写入日志
  • (3)发送邮箱

如果系统在写入用户表之后,异常重启,或者需要更新(这个是很正常的,一般来说我们根本无法预估用户喜欢在什么时候在我们网站上注册用户,但是我们系统需要更新迭代)。那么后面两个步骤就因为系统重启内存被清空而无法完成了。

如果有了消息中间件,生产者(当前服务)将这三个操作进行消息通知,暂时存放在Kafka或者其他消息中间件中,等待消费者(下游服务)进行消费,然后异步操作写入用户表、日志以及发送邮箱。这么做的好处就是,不管当前服务、下游服务什么时候更新、或者出现了异常,都可以保证消息不会丢失,暂时保存在消息中间件中。

那么消息中间件就一定是万无一失的吗?不一定,但是通常来说,消息中间件具有稳定性高、处理逻辑简单、不需要更新迭代等特点,数据存在消息中间件中远比存在服务内存中要稳靠得多。

那么有了消息中间件,上下游之间的通信由原来的接口或者RPC调用改成了消息生产、消费,响应的性能会不会减弱呢?在一定程度上虽然会降低一些,但是目前Kafka等一些消息中间件,尽量保证消息的高吞吐、低延迟,每一秒可以处理几十万条消息。除去网络性能IO带来的开销无法避免之外,消息中间件几乎可达到毫秒级的延迟。

2、设计

消息中间件具有两种模式,一种是点对点(P2P),一种是发布/订阅(publish/subscribe),而Kafka属于第二种,如下图

Kafka系列:基本概念、概述、设计、集群

发布者publisher发布一条消息message,订阅了这个发布者的所有订阅者subscriber都可以同时消费这条消息。

在消息队列中,发布者通常叫做生产者producer,订阅名称叫做主题topic,订阅者叫做消费者consumer。

Kafka在发布/订阅模式之上,加入了组group的概念,每一个消费者consumer都属于一个消费组consumer group,每个组group可以有多个消费者consumer,每个消费者也可以加入多个消费组consumer group。发送到主题topic下的消息,会被订阅了这个主题topic的每个消费组consumer group消费。但是注意,一条消息只能被一个消费者组consumer group内的一个消费者consumer消费。 也就是说,假设所有的消费组consumer group都订阅了主题topic,如果所有的消费者consumer都在同一个消费组consumer group中, 那么是P2P模式,消息会在组内所有的消费者consumer之间负载均衡;相反,如果所有的消费者consumer都在自己单独的消费组consumer group中,那么每个消费者consumer都可以同时消费这个主题topic下的消息

Kafka系列:基本概念、概述、设计、集群

如上图所示,我们应该可以理解到,C4和C5能够单独消费消息message,但是同在消费组consumer group 1中的C1、C2、C3只能通过默认状态下(负载均衡)的形式进行消费。

其实这个分组是概念上的分组,我们只是用一些变量将这些消费者consumer进行约束。真正意义上的Kafka还引入一个物理上的分组概念

  • 分区 partition

分区partition是主题topic的下一级划分,一个主题topic至少有一个分区partition,一般都有多个分区(为了并发写入,提高吞吐量)。如下图所示

Kafka系列:基本概念、概述、设计、集群

我们看到,该主题topic下有三个分区(分别是分区0、分区1、分区2),假如目前有一条消息message从生产者producer产生,需要进行写入操作。那么我们可以有两种方式:

  • 指定分区进行写入
  • 默认写入

在代码中,生产者producer发送消息message的时候,除了必须要指定topic之外,还有一个可选的值key,这个key如果非空,那么是会通过Hash算法对分区数进行取模得到指定分区的。这个key通常应用于分布式中(以后会专门讲);如果key为空,那么就会负载均衡到每个分区,进行写入。

图中每个分区内的那些小框数字代表位移,它叫offset,在很多书上面的中文描述不尽相同,因此我这里只叫位移。它是不可更改的,一旦写入,无法修改,也无法单个删除。这种append追加的方式,有以下好处:

  • 因为不能修改和删除,所以即使在并发读写时也无需加锁;
  • 采用了末尾追加写入的方式,避免了磁盘随机读/写操作;

另外,每个分区partiton其实就是一个append log文件,每次写入都是进行 内存写入 ,在服务器中,会对消息进行缓存buffer起来,只有在消息的个数或者大小达到一定的阈值的时候,才会进行flush到磁盘中。

这种设计大大提高了性能,即使在大量的并发读写操作下,也不会被压垮。

在读取每一个message的时候,我们用以下的三元组进行定位

  • <topic>

我们可以随机消费每一个消息message,只要我们知道主题topic、分区partiton以及位移offset。

但是其实如果Kafka如果需要重启或者服务器突然宕机,那么写入在append log文件的信息如果没有达到一定的阈值,就还是保存在内存中,此时突发情况下的消息就会丢失。

为了保险起见,我们需要对Kafka进行数据备份冗余,也就是集群部署。

Kafka系列:基本概念、概述、设计、集群

3、集群

集群部署最常见的有两种,一种是mysql的 主备 (master-slave),另一种是zookeeper的 领导者追随者 (leader-follower)。传统的主备系统可以同时对外提供服务,只不过master提供读写而slave只提供读。但是像zookeeper这种的却不一样, 只有leader对外提供服务 ,通俗一点说,follower只是用来等leader挂了,然后重新选举,有机会就自己当leader,没有机会就一直等着。等leader挂了, 重新选举,leader挂了,重新选举,如此循环(有的时候,不是你的,注定不是你的).....

这么来说,其实主备系统可能命还好一点,虽然没有改动权,但是还能对外提供服务(至少能露露脸)。

在Kafka中,这些备份下来的日志被称为 副本replica

为什么目前会更新leader和follower模式,follower只用作备份而不对外提供服务呢。因为对于秒级几十万的写入读取并发量,主从一致是很难做到的。对于出现已经将消息写入在了leader的分区partition中,但是没有来得及备份到follower的情况,如果A和B分别访问leader和follower的该分区partition,得到的是A消费了该数据,而B没有消费该数据的不同的结果,这样做不到消息获取的

幂等性

因此,新的leader-follower模式中,只有leader对外提供读写服务。leader会对设好的副本因子数对每一份分区partition都进行冗余备份,假设副本因子数为3,那么就会分别在三个不同的服务器broker中进行备份。 Kafka会保证同一个分区partition的多个replica一定不会分配在同一台服务器broker上 。如下图

Kafka系列:基本概念、概述、设计、集群

在这里,我们打破一下我们前面的观点

  • 注意,并不是所有的follower都有权力参与选主

意思就是说,对于主题topic有四个分区,副本因子是3的情况,如果因为网络IO性能或者其他的原因,在同步的过程中与leader replica 5(假如是上图中的副本5)保持一致的只有另外一个副本follower replica 2(假如是副本2),而剩下的一个副本follower replica 3却因为服务时好时坏,始终无法与leader replica 5保持一致,那么假如leader replica 5突然挂掉了,follower replica 3是无法参与重新选主的(可能follower replica 2就自然而然成了“继承人”)。

说到这里大概可能就明白了,对于leader replica 5(老大)来说,它会在所有的其他follower replica中选取一些作为它的"马仔",这些follower replica(马仔)需要听这个leader replica 5老大的话,比如要及时处理leader replica 5(老大)发过来的信息同步的请求,并且做好备份,给出响应。这个“马仔圈”在Kafka中的术语叫做 ISR ,全称为in-sync replica。翻译过来,就是与 leader replica保持同步的集合

  • 也就是说,在ISR集合内的replica必须与leader replica保持同步状态(主要是消息日志),而在ISR集合之外的replica同样会接收到leader replica的信息同步的请求,但是不要求时限。如果ISR集合内的replica没有按时与leader replica保持消息日至的同步,会被“踢出”ISR集合的。相反的,如果这些replica重新“追上”了leader replica的进度,那么会被“加回”到ISR集合中。
Kafka系列:基本概念、概述、设计、集群

注意,哪些follower replica在ISR集合中,什么时候会被“踢出”,什么时候会被“加回”,都是Kafka自动维护的,不需要用户进行人工干预。

因此,我们引出一个概念

  • 消息的提交

什么是消息提交?在Kafka中,如果该主题topic下的leader replica以及ISR集合中的所有replica都已经备份了这条消息,那么Kafka就把这条消息置于“

已提交 ”的状态,即认为这条消息 发送成功 (在producer那一边没有问题)。

需要注意,所有的 ISR集合都是包括leader replica在内的 。也就是说,上面所说的例子中,ISR集合其实是包括replica 2(follower replica)和replica 5(leader replica)在内的。

Kafka能够保证,只要ISR集合中至少存在一个replica,那些“已提交”状态的消息就不会丢失(极端情况下就是老大没有马仔了,然后老大也不幸挂掉了,那么这个时候保存在老大机器里的append log可能就真的没了233333)

其实 只要ISR集合中只要有一个replica没有备份这条消息,Kafka都不会将这条消息置为“已提交” 。

end:如果你觉得本文对你有帮助的话,记得关注点赞转发,你的支持就是我更新动力。


分享到:


相關文章: