在kafka的世界中有很多概念年和术语是需要我们提前理解并且熟练掌握的,下面来盘点下。
之前我们提到过,kafka属于分布式的消息引擎系统,主要功能是提供一套完善的消息发布与订阅方案。在kafka中,发布订阅的对象是主题(topic),可以为每个业务、每个应用、甚至是每一类数据都创建专属的主题。
向主题发布消息的客户端应用程序称为生产者(producer),生产者通常持续不断地向一个或多个主题发送消息,而订阅这些主题获取消息的客户端应用程序就被称之为消费者(consumer)。和生产者类似,消费者也能同时订阅多个主题。我们把生产者和消费者统称为客户端(clients)。你可以同时运行多个生产者和消费者实例,这些实例不断地向kafka集群中的多个主题生产和消费消息。
有客户端自然也有服务端。kafka的服务端由被称为broker的服务进程构成,即一个kafka集群由多个broker组成,broker负责接收和处理客户端发来的请求,以及对消息进行持久化。虽然多个broker进程能够运行在同一台机器上,但更常见的做法是将不同的broker分散运行在不同的机器上。这与即便集群中的某一台机器宕机,运行在其上的broker进程挂掉了,其他机器上的broker也依旧能对外提供服务。这是就是kafka提供高可用的手段之一。
实现高可用的另一个手段就是备份机制(replication)。备份的思想很简单,就是把相同的数据拷贝到多台机器上,而这些相同的数据拷贝就叫做副本(replica)。副本的数量是可以配置的,这些副本保存着相同的数据看,但却有不同的角色和作用。kafka定义了两种副本,领导者副本(leader replica)和追随者副本(follow replica)。前者对外提供服务,这里的对外指的是与客户端进行交互;而后者只是被动地追随领导者副本而已,不与外界进行交互。当然了,很多其他系统中追随者副本是可以对外提供服务的,比如mysql,从库是可以处理读操作的,也就是所谓的"主写从读",但是在kafka中追随者副本不会对外提供服务,至于为什么我们作为思考题解答。对了,关于领导者–追随者,之前其实是叫做主(master)–从(slave),但是不建议使用了,因为slave有奴隶的意思,政治上有点不合适,所以目前大部分的系统都改成leader–follower了。
副本的工作机制很简单:生产者向主题写的消息总是往领导者那里写的,消费者向主题获取消息也都是来自于领导者。也就是无论是读还是写,针对的都是领导者副本,至于追随者副本,它只做一件事情,那就是向领导者副本发送请求,请求领导者副本把最新生产的消息发送给它,这样便能够保持和领导者的同步。
虽然有了副本机制可以保证数据的持久化或者数据不丢失,但没有办法解决伸缩性的问题。伸缩性即所谓的scalability,是分布式系统中非常重要且必须谨慎对待的问题。什么是伸缩性呢?我们拿副本来说,虽然现在有了领导者副本和追随者副本,但倘若领导者副本累计了太多的数据以至于单台broker都无法容纳了,此时应该怎么办?有个很自然的想法就是,能否把数据分割成多份保存在不同的broker上?没错,kafka就是这么设计的。
这种机制就是所谓的分区(partition)。如果了解其他的分布式系统,那么可能听说过分片、分区域等提法,比如MongoDB和ElasticSearch找那个的sharding、Hbase中的region,其实它们都是相同的原理,只是partition是最标准的名称。
kafka中的分区机制指的是将每个主题划分为多个分区,每个分区都是一组有序的消息日志。生产者生产的每一条消息只会被发到一个分区中,也就是说如果向有两个分区的主题发送那个一条消息,那么这条消息要么在第一个分区中,要么在第二个分区中。而kafka的分区编号是从0开始的,如果某个topic有100个分区,那么他们的分区编号就是从0到99。
到这里可能会有疑问,那就是刚才提到的副本如何与这里的分区联系在一起呢?实际上,副本是在分区这个层级定义的。每个分区下可以配置若干个副本,其中只能有1个领导者副本和N-1个追随者副本。生产者向分区写入消息,每条消息在分区中的位置由一个叫做位移(offset)的数据来表征。分区唯一总是从0开始,假设一个生产者向一个空分区写入10条消息,那么这10条消息的位移依次是0、1、2、…、9
至此我们能完整地串联起kafka的三层消息架构
- 第一层是主题层,每个主题可以配置M个分区,每个分区又可以配置N个副本。
- 第二层是分区层,每个分区的N个副本中只能有一个副本来充当领导者角色,对外提供服务;其他的N-1个副本只是追随者副本,用来提供数据冗余之用。
- 第三层是消息层,分区中包含若干消息,每条消息的位移从0开始,依次递增。
- 最后客户端程序只能与分区的领导者副本进行交互。
那么kafka是如何持久化数据的呢?总的来说,kafka使用消息日志(log)来保存数据,一个日志就是磁盘上一个只能追加(append-only)消息的物理文件。因为只能追加写入,故避免了缓慢地随机I/O操作,这也是实现kafka高吞吐量特征的一个重要手段。不过如果不停地向一个日志写入消息,最终也会耗尽所有的磁盘空间,因此kafka必然要定期的删除消息以回收磁盘。怎么删除?简单来说就是通过日志段(logsegment)机制。在kafka底层,一个日志文又进一步细分成多个日志段,消息被追加写到当前最新的日志段中,当写满了一个日志段后,kafka会自动分出一个新的日志段,并将老的日志段封存起来。kafka在后台还有定时任务会定期的检查老的日志段是否能够被删除,从而实现回收磁盘的目的。
这里再重点说一下消费者,之前说过有两种消费模型,即点对点模型(peer to peer,p2p)和分布订阅模型。这里面的点对点指的是同一条消息只能被下游的一个消费者消费,而其他消费者不能染指。在kafka中实现这种p2p模型的方法就是引入了消费者组(consumer group)。所谓的消费者组,指的是多个消费者实例共同组建一个组来消费一个主题。这个主题中的每个分区都只会被消费者组里面的一个消费者实例消费,其他消费者实例不能消费它。为什么要引入消费者组呢?主要是为了提升消费者端的吞吐量,多个消费者实例同时消费,加速了整个消费端的吞吐量(TPS)。关于消费者组的机制,后面会详细介绍,现在只需要知道消费者组就是多个消费者组成的一个组来消费主题里面的消息、并且消息只会被组里面的一个消费者消费即可。此外,这里的消费者实例可以是运行消费者应用的进程,也可以是一个线程,它们都称为一个消费者实例(consumer instance)。
消费者组里面的消费者不仅瓜分订阅主题的数据,而且更酷的是它们还能彼此协助。假设组内某个实例挂掉了,kafka能够自动检测,然后把这个Failed实例之前负责的分区转移给其他或者的消费者。这个过程就是大名鼎鼎的"重平衡(rebalance)"。嗯,其实即是大名鼎鼎,也是臭名昭著,因为由重平衡引发的消费者问题比比皆是。事实上,目前有很过重平衡的bug,整个社区都无力解决。
每个消费者在消费消息的过程中,必然需要有个字段记录它当前消费到了分区的哪个位置上,这个字段就是消费者位移(consumer offset)。注意,我们之前说一个主题可以有多个分区、每个分区也是用位移来表示消息的位置。但是这两个位移完全不是一个概念,分区位移表示的是分区内的消息位置,它是不变的,一旦消息被成功写入一个分区上,那么它的位置就是固定了的。而消费者位移则不同,它可能是随时变化的,毕竟它是消费者消费进度的指示器嘛。另外每个消费者都有着自己的消费者位移,因此一定要区分这两类位移的区别。一个是分区的唯一,另一个是消费者位移。
小结:
- 生产者,producer:向主题发布新消息的应用程序。
- 消费者,consumer:从主题订阅新消息的应用程序。
- 消息,record:kafka是消息引擎,这里的消息就是指kafka处理的主要对象。
- 主题,topic:主题是承载消息的逻辑容器,在实际使用中多用来区分具体的业务,即不同的业务对应不同的主题。
- 分区,partition:一个有序不变的消息序列,每个主题下可以有多个分区。分区编号从0开始,分布在不同的broker上,实现发布与订阅的负载均衡。生产者将消息发送到主题下的某个分区中,以分区偏移(offset)来标识一条消息在一个分区当中的位置(唯一性)
- 分区位移,offset:表示分区中每条消息的位置信息,是一个单调递增的值。
- 副本,replica:kafka中同一条数据能够被拷贝到多个地方以提供数据冗余,这便是所谓的副本。副本还可以分为领导者副本和追随者副本,各自有各自的功能跟那个职责。读写都是针对领导者副本来的,追随者副本只是用来和领导者副本进行数据同步、保证数据冗余、实现高可用。
- 消费者位移,consumer offset:表示消费者消费进度,每个消费者都有自己的消费者位移。
- 消费者组,consumer group:多个消费者实例共同组成一个组,同时消费多个分区以实现高吞吐。
- 重平衡,rebalance:消费者组内某个消费者实例挂掉之后,其他消费者实例自动重新分配订阅主题分区的过程。重平衡是kafka消费者端实现高可用的重要手段。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-paJvc7WP-1576467087158)(E:\笔记整理\Kafka\kafka专业术语\assets\kafka.png)]
思考:为什么kafka不像mysql那样支持主写从读呢?
因为kafka的主题已经被分为多个分区,分布在不同的broker上,而不同的broker又分布在不同的机器上,因此从某种角度来说,kafka已经实现了负载均衡的效果。不像mysql,有压力都在主上面,所以才要从读;另外,kafka保存的数据和数据库的数据有着实质性的差别,kafka保存的数据是流数据,具有消费的概念,而且需要消费者位移。所以如果支持从读,那么消费端控制offset会更复杂,而且领导者副本同步到追随者副本需要时间,会造成数据不一致的问题;另外对于生产者来说,kafka是可以通过配置来控制是否等待follower对消息确认的,如果支持从读的话,那么也需要所有的follower都确认了才可以回复生产者,造成从能下降,而且follower出现了问题也不好处理。