Apache kafka是消息中间件的一种,我发现有很多人不知道消息中间件是什么,那我们就先来简单地了解一下什么是消息中间件。
举个例子,生产者和消费者,生产者生产鸡蛋,消费者消费鸡蛋,生产者生产一个鸡蛋,消费者就消费一个鸡蛋,假设消费者消费鸡蛋的时候噎住了(系统宕机了),生产者还在生产鸡蛋,那新生产的鸡蛋可能就会因为无人及时消费而丢失。再比如生产者很强劲(大交易量的情况),生产者1秒钟生产100个鸡蛋,消费者1秒钟只能吃50个鸡蛋,那要不了一会,消费者就吃不消了(消息堵塞,最终导致系统超时),消费者拒绝再吃了,”鸡蛋“又丢失了,这个时候我们放个篮子在它们中间,生产出来的鸡蛋都放到篮子里,消费者去篮子里拿鸡蛋,这样鸡蛋就不会丢失了,都在篮子里,而这个篮子就是”kafka“。
鸡蛋其实就是“数据流”,系统之间的交互都是通过“数据流”来传输的(就是tcp、https什么的),也称为报文,也叫“消息”。
消息队列满了,其实就是篮子满了,”鸡蛋“ 放不下了,那赶紧多放几个篮子,其实就是kafka的扩容。
各位现在知道kafka是干什么的了吧,它就是那个"篮子"。
Kafka 是一个多分区、多副本且基于 ZooKeeper 协调的分布式消息系统,它以高吞吐、可持久化、可水平扩展、支持流数据处理等多种特性而被广泛使用。
Kafka的主要角色是一个分布式基于发布/订阅模式的消息队列,主要应用于大数据实时处理领域:
Kafka 和传统的消息系统(也称作消息中间件〉都具备系统解耦、冗余存储、流量削峰、缓冲、异步通信、扩展性、 可恢复性等功能。除此之外, Kafka还提供了大多数消息系统难以实现的消息顺序性保障及回溯消费的功能。
一个典型的Kafka体系架构包括若干Producer、若干 broker 、若干Consumer 以及ZooKeeper 集群,如下图所示。其中 ZooKeeper是Kafka用来负责集群元数据的管理、控制器的选举等操作的。Producer 将消息发送到 broker,broker负责将收到的消息存储到磁盘中,而Consumer负责从broker订阅并消费消息。
图1-1 Kafka 集群架构Producer:消息生产者,生产者负责创建消息然后将其投递到Kafka中。(就是它来生产“鸡蛋”的)
Consumer:消息消费者,消费者连接到 Kafka 上并接收消息,进而进行相应的业务逻辑处理。(生出的“鸡蛋”由它来消费)
broker :服务代理节点,broker可以简单地看作一个独立的Kafka服务节点或服务实例。一个或多个broker 组成了 一个 Kafka 集群。(多个装鸡蛋的篮子)
Topic:主题,Kafka中的消息以Topic为单位进行归类,生产者负责将消息发送到特定的主题(发送到 Kafka 集群中的每一条消息都要指定一个主题),而消费者负责订阅主题并进行消费。
Partition:分区,一个topic可以分为多个partition,每个partition是一个有序的队列。分区,在存储层面可以看作(是)一个可追加的日志( Log )文件,消息在被追加到分区日志和文件的时候都会分配一个特定的偏移量( offset )。offset 是消息在分区中的唯一标识,Kafka通过它来保证消息在分区内的顺序性。Kafka中的分区可以分布在不同的服务器 (broker )上,也就是说,一个主题可以横跨多个 broker。
Replica:副本,为保证集群中的某个节点发生故障时,该节点上的partition数据不丢失,且kafka仍然能够继续工作,会在同一分区的不同副本中保存相同的消息(在同一时刻,副本之间并非完全一样)。副本之间是“一主多从”的关系,即一个leader对应若干个follower),其中 leader 副本负责处理读写请求, follower 副本只负责与 leader 副本的消息同步。
leader:指每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是leader。
follower:指每个分区多个副本中的“从”,实时从leader中同步数据,保持和leader数据的同步。leader发生故障时,某个follower会成为新的leader。
步骤1:获取Kafka,并进行解压缩
# wget https://archive.apache.org/dist/kafka/2.8.0/kafka_2.13-3.2.0.tgz
# tar -zxvf kafka_2.12-2.8.0.tgz
步骤2:启动Kafka环境,进入kafka的bin目录启动zookeeper
# ./zookeeper-server-start.sh config/zookeeper.properties
打开另一个终端会话并运行,进入kafka的bin目录启动kafka
# ./kafka-server-start.sh config/server.properties
步骤3:创建topic以存储事件
主题类似于文件系统中的文件夹,事件是该文件夹中的文件。因此,在编写第一个事件之前,必须创建一个主题。打开另一个终端会话并运行:
# ./kafka-topics.sh --create -- topicA --bootstrap-server localhost:9092
查看主题描述
# ./kafka-topics.sh --describe -- topicA --bootstrap-server localhost:9092
步骤4:将事件写入topic
Kafka 客户端通过网络与 Kafka 代理进行通信,以写入(或读取)事件。收到事件后,代理将以持久和容错的方式存储事件。
运行控制台生产者客户端,将一些事件写入主题。默认情况下,您输入的每一行都将导致一个单独的事件被写入topic。
# ./kafka-console-producer.sh -- topicA --bootstrap-server localhost:9092
步骤5:读取事件
打开另一个终端会话并运行控制台使用者客户端以读取刚创建的事件:
# ./kafka-console-producer.sh -- topicA --from-beginning --bootstrap-server localhost:9092
Kafka中消息是以topic进行分类的,生产者生产消息,消费者消费消息,都是面向topic的。topic是逻辑上的概念,而partition是物理上的概念,每个partition对应一个log文件,该log文件中存储的就是producer生产的数据。Producer生产的数据会被不断追加到该log文件末端,且每条数据都有自己的offset。消费者组中的每个消费者,都会实时记录自己消费到了哪个offset,以便在出错后恢复时,可以从上次的位置继续消费。
图2-1 Kafka 工作流程为防止log文件过大导致数据定位效率低下,Kafka采取了分片和索引机制,将每个partition分为多个segment。每个segment对应两个文件——“.index”文件和“.log”文件,“.index”文件存储大量的索引信息,“.log”文件存储大量的数据,索引文件中的元数据指向对应数据文件中message的物理偏移地址。
图2-2 Kafka 分片和索引机制分区的好处:
方便在集群中扩展,每个Partition可以通过调整以适应它所在的机器,而一个topic又可以由多个Partition组成,因此整个集群就可以适应任意大小的数据了;
可以提高并发,因为可以以Partition为单位读写了。
分区的原则:
在指明了 partition 的情况下,可以直接将指明的值作为 partiton 值;
没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值;
既没有 partition 值又没有 key 值的情况下, kafka会采用Sticky Partition(黏性分区器)随机选择一个分区,并尽可能地一直使用该分区,待该分区的batch已满或者已完成,kafka再随机选择一个分区进行使用。
生产者会发送数据到topic主题里面的partition里面,那怎么样保证可靠性呢?为保证producer发送的数据,能可靠的发送到指定的topic,topic的每个partition收到producer发送的数据后,都需要向producer发送ack(acknowledgement确认收到),如果producer收到ack,就会进行下一轮的发送,否则重新发送数据。
确保有follower与leader同步完成,leader再发送ack,这样才能保证leader挂掉之后,能在follower中选举出新的leader。
全部的follower同步完成,才可以发送ack。
Leader维护了一个动态的in-sync replicas(ISR),意为和leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给producer发送ack。如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms参数设定。Leader发生故障之后,就会从ISR中选举新的leader。
分区中的所有副本统称为AR。
所有与leader副本保持一定程度同步的副本(包括leader副本在内)组成ISR。
与leader副本同步滞后过多的副本(不包 leader 副本)组成 OSR ,
AR=ISR+OSR
acks,这个参数用来指定分区中必须要有多少个副本收到这条消息,只有当指定的副本都收到消息后,生产者才会认为这条消息是成功写入的。 acks 是生产者客户端中一个非常重要的参数 ,它涉及消息的可靠性和吞吐量之间的权衡,acks 参数有3种类型的值(都是字符串类型)。
acks =1。默认值即为1 。生产者发送消息之后,只要分区的leader 副本成功写入消息,那么它就会收到来自服务端的成功响应。如果消息无法写入 leader 副本,比如写入消息时正处在leader副本崩溃、重新选举新的 leader 副本的过程中,那么生产者就会收到一个错误的响应,为了避免消息丢失,生产者可以选择重发消息 。如果消息写入 leader 副本并返回成功响应给生产者后,但在被其他 follower副本拉取之前leader 副本崩溃了,那么此时消息还是会丢失,因为新选举的 leader 副本中并没有这条对应的消息。acks 设置为1,是消息可靠性和吞吐量之间的折中方案,不过却会造成数据丢失。
acks =0。生产者发送消息之后不需要等待任何服务端的响应。如果在消息从发送到写入 Kafka 的过程中出现某些异常,导致 Kafka 并没有收到这条消息,那么生产者也无从得知,消息也就丢失了。在其他配置环境相同的情况下,acks 设置为0可以达到最大的吞吐量,但是也会造成数据丢失。
acks = -1 或 acks=all。生产者在消息发送之后,需要等待 ISR 中的所有副本都成功写入消息之后才能够收到来自服务端的成功响应。但是如果在follower同步完成后,broker发送ack之前,leader发生了故障,那么会造成数据重复。在其他配置环境相同的情况下,acks 设置为 (all )可以达到最强的可靠性。但这并不意味着消息就一定可靠,因ISR 中可能只有 leader 副本,这样就退化成了 acks= 1的情况。
HW是High Watermark 的缩写,俗称高水位,它标识了一个特定的消息偏移量( offset ),消费者只能拉取到这个 offset 前的消息。
LEO是Log End Offset 缩写,它标识当前日志文件中下一条待写入消息offset。
follower故障
follower发生故障后会被临时踢出ISR,待该follower恢复后,follower会读取本地磁盘记录的上次的HW,并将log文件中高于HW的部分截取掉,从HW开始向leader进行同步。等该follower的LEO大于等于该Partition的HW,即follower追上leader之后,就可以重新加入ISR了。
leader故障
leader发生故障之后,会从ISR中选出一个新的leader,之后,为保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于HW的部分截掉,然后从新的leader中同步数据。
注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。
kafka消费者组(Consumer Group)是kafka提供的可扩展且具有容错性的消费者机制。它是一个组,所以内部又可以有多个消费者,这些消费者共用一个ID(Group ID),一个组内的所有消费者共同协作,完成对订阅的主题的所有分区的消费。其中一个主题中的一个分区只能由一个消费者消费,消费者组之间互不影响。
我们知道的消息引擎模型有:点对点模型和发布/订阅模型。
点对点的模型,每消费一个消息之后,被消费的消息就会被删除。如果我们需要多个消费者消费同一个消息队列时,就不能使用点对点模型了。
发布订阅模型,支持多个消费者消费同一个消息队列,但是发布订阅模型中,消费者订阅了一个主题后,就要订阅主题的所有分区。这种方式既不灵活,也会影响消息的真实投递效果。
消费者组就避开了上述两种模型的缺陷,又兼容了他们的优点: 首先消费者之间彼此独立,互不影响,可以订阅同一个主题并且互不干扰,再加上Broker端的消息留存机制,使kafka的消费者组(Consumer Group)机制可以同时实现传统消息引擎系统的两大模型:如果所有的消费者实例都属于一个消费者组那就是点对点模型;如果所有消费者实例各自是独立的消费者那就是发布订阅模型。
consumer采用pull(拉)模式从broker中读取数据,可以根据consumer的消费能力以适当的速率消费消息。
pull模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据。解决办法是在消费数据时传入一个等待时间参数timeout,consumer会等待timeout之后再返回。
Kafka有三种分配策略,Range ,RoundRobin, Sticky。
Range 范围分区(默认的)
RangeAssignor策略的原理是按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,以保证分区尽可能均匀地分配给所有的消费者。
RoundRobin 轮询分区
RoundRobinAssignor策略的原理是将消费组内所有消费者以及消费者所订阅的所有topic的partition按照字典序排序,然后通过轮询方式逐个将分区依次分配给每个消费者。
StickyAssignor 分配策略
StickyAssignor策略的具体实现要比RangeAssignor和RoundRobinAssignor这两种分配策略复杂很多,但它可以使分区的分配尽可能的均匀,分区的分配尽可能的与上次的分配保持相同。
由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。
Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为_consumer_offsets。
总结:
本文是对kafka的基本概念进行介绍,相信你看完之后也对kafka有了些许了解,赶紧关注我们吧,后续会持续更新kafka的相关文章,敬请期待。
本期内容就到这里了,如果喜欢就点个关注吧,微信公众号搜索“数 新 网 络 科 技 号”可查看更多精彩内容~