最近整体梳理之前用到的一些东西,回顾Kafka的时候好多东西都忘记了,把一些自己记的比较模糊并且感觉有用的东西整理一遍并且记忆一遍,仅用于记录以备后续回顾
生产者发送消息:在 Kafka 生产者发送消息时,使用零拷贝技术可以避免将数据从用户空间复制到内核空间,从而提高性能。具体来说,在发送消息之前,生产者将消息数据保存在内存缓冲区中,然后将指向缓冲区的指针传递给 Kafka 客户端库,客户端库再将指针传递给网络层,最终将数据发送到 Kafka 服务器。在这个过程中,数据在内存中只有一份副本,避免了数据的复制,从而提高了性能。
消费者接收消息:在 Kafka 消费者接收消息时,使用零拷贝技术可以避免将数据从内核空间复制到用户空间,从而提高性能。具体来说,在接收消息之前,消费者会注册一个内存映射文件(Memory-mapped file),然后 Kafka 客户端库会将消息数据写入到这个内存映射文件中。消费者只需要读取这个内存映射文件中的数据,就可以获取消息,避免了数据的复制,从而提高了性能。
消费者读取磁盘上的消息:Kafka 中的消息默认存储在磁盘上,消费者需要从磁盘上读取消息。使用零拷贝技术,可以将磁盘上的消息直接映射到内存中,而不需要将数据从磁盘复制到内存,从而提高了性能。
总之,Kafka 使用零拷贝技术来提高网络传输性能和磁盘读取性能,在发送消息和接收消息等场景中都得到了广泛应用。
在 Kafka 中,生产者写入消息、消费者读取消息的操作都是与 leader 副本进行交互的,从 而实现的是一种主写主读的生产消费模型。
Kafka 并不支持主写从读,因为主写从读有 2 个很明 显的缺点:
数据一致性问题。数据从主节点转到从节点必然会有一个延时的时间窗口,这个时间 窗口会导致主从节点之间的数据不一致。某一时刻,在主节点和从节点中 A 数据的值都为 X, 之后将主节点中 A 的值修改为 Y,那么在这个变更通知到从节点之前,应用读取从节点中的 A 数据的值并不为最新的 Y,由此便产生了数据不一致的问题。
延时问题。类似 Redis 这种组件,数据从写入主节点到同步至从节点中的过程需要经历网络→主节点内存→网络→从节点内存这几个阶段,整个过程会耗费一定的时间。而在 Kafka 中,主从同步会比 Redis 更加耗时,它需要经历网络→主节点内存→主节点磁盘→网络→从节点内存→从节点磁盘这几个阶段。对延时敏感的应用而言,主写从读的功能并不太适用。
Kafka
的基本架构组成是:由多个 broker
组成一个集群,每个 broker
是一个节点;当创建一个 topic
时,这个 topic
会被划分为多个 partition
,每个 partition
可以存在于不同的 broker
上,每个 partition
只存放一部分数据。
这就是天然的分布式消息队列,就是说一个 topic
的数据,是分散放在多个机器上的,每个机器就放一部分数据。
在 Kafka 0.8
版本之前,是没有 HA
机制的,当任何一个 broker
所在节点宕机了,这个 broker
上的 partition
就无法提供读写服务,所以这个版本之前,Kafka
没有什么高可用性可言。
在 Kafka 0.8
以后,提供了 HA
机制,就是 replica
副本机制。每个 partition
上的数据都会同步到其它机器,形成自己的多个 replica
副本。所有 replica
会选举一个 leader
出来,消息的生产者和消费者都跟这个 leader
打交道,其他 replica
作为 follower
。写的时候,leader
会负责把数据同步到所有 follower
上去,读的时候就直接读 leader
上的数据即可。Kafka
负责均匀的将一个 partition
的所有 replica
分布在不同的机器上,这样才可以提高容错性。
拥有了 replica
副本机制,如果某个 broker
宕机了,这个 broker
上的 partition
在其他机器上还存在副本。如果这个宕机的 broker
上面有某个 partition
的 leader
,那么此时会从其 follower
中重新选举一个新的 leader
出来,这个新的 leader
会继续提供读写服务,这就有达到了所谓的高可用性。
写数据的时候,生产者只将数据写入 leader
节点,leader
会将数据写入本地磁盘,接着其他 follower
会主动从 leader
来拉取数据,follower
同步好数据了,就会发送 ack
给 leader
,leader
收到所有 follower
的 ack
之后,就会返回写成功的消息给生产者。
消费数据的时候,消费者只会从 leader
节点去读取消息,但是只有当一个消息已经被所有 follower
都同步成功返回 ack
的时候,这个消息才会被消费者读到。
消费者组是Kafka独有的概念,即消费者组是Kafka提供的可扩展且具有容错性的消费者机制。
但实际上,消费者组(Consumer Group)其实包含两个概念,作为队列,消费者组允许你分割数据处理到一组进程集合上(即一个消费者组中可以包含多个消费者进程,他们共同消费该topic的数据),这有助于你的消费能力的动态调整;作为发布-订阅模型(publish-subscribe),Kafka允许你将同一份消息广播到多个消费者组里,以此来丰富多种数据使用场景。
需要注意的是:在消费者组中,多个实例共同订阅若干个主题,实现共同消费。同一个组下的每个实例都配置有相同的组ID,被分配不同的订阅分区。当某个实例挂掉的时候,其他实例会自动地承担起它负责消费的分区。 因此,消费者组在一定程度上也保证了消费者程序的高可用性。
顺序写
:由于现代的操作系统提供了预读和写技术,磁盘的顺序写大多数情况下比随机写内存还要快。Zero-copy
:零拷技术减少拷贝次数Batching of Messages
:批量量处理。合并小的请求,然后以流的方式进行交互,直顶网络上限。Pull 拉模式
:使用拉模式进行消息的获取消费,与消费端处理能力相符。首先需要弄明白消息为什么会丢失,对于一个消息队列,会有 生产者
、MQ
、消费者
这三个角色,在这三个角色数据处理和传输过程中,都有可能会出现消息丢失。
消息丢失的原因以及解决办法:
消费者可能导致数据丢失的情况是:消费者获取到了这条消息后,还未处理,Kafka
就自动提交了 offset
,这时 Kafka
就认为消费者已经处理完这条消息,其实消费者才刚准备处理这条消息,这时如果消费者宕机,那这条消息就丢失了。
消费者引起消息丢失的主要原因就是消息还未处理完 Kafka
会自动提交了 offset
,那么只要关闭自动提交 offset
,消费者在处理完之后手动提交 offset
,就可以保证消息不会丢失。但是此时需要注意重复消费问题,比如消费者刚处理完,还没提交 offset
,这时自己宕机了,此时这条消息肯定会被重复消费一次,这就需要消费者根据实际情况保证幂等性。
对于生产者数据传输导致的数据丢失主常见情况是生产者发送消息给 Kafka
,由于网络等原因导致消息丢失,对于这种情况也是通过在 producer 端设置 acks=all 来处理,这个参数是要求 leader
接收到消息后,需要等到所有的 follower
都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试。
Kafka
导致的数据丢失一个常见的场景就是 Kafka
某个 broker
宕机,,而这个节点正好是某个 partition
的 leader
节点,这时需要重新重新选举该 partition
的 leader
。如果该 partition
的 leader
在宕机时刚好还有些数据没有同步到 follower
,此时 leader
挂了,在选举某个 follower
成 leader
之后,就会丢失一部分数据。
对于这个问题,Kafka
可以设置如下 4 个参数,来尽量避免消息丢失:
topic
设置 replication.factor
参数:这个值必须大于 1
,要求每个 partition
必须有至少 2
个副本;Kafka
服务端设置 min.insync.replicas
参数:这个值必须大于 1
,这个参数的含义是一个 leader
至少感知到有至少一个 follower
还跟自己保持联系,没掉队,这样才能确保 leader
挂了还有一个 follower
节点。producer
端设置 acks=all
,这个是要求每条数据,必须是写入所有 replica
之后,才能认为是写成功了;producer
端设置 retries=MAX
(很大很大很大的一个值,无限次重试的意思):这个参数的含义是一旦写入失败,就无限重试,卡在这里了。在某些业务场景下,我们需要保证对于有逻辑关联的多条MQ消息被按顺序处理,比如对于某一条数据,正常处理顺序是新增-更新-删除
,最终结果是数据被删除;如果消息没有按序消费,处理顺序可能是删除-新增-更新
,最终数据没有被删掉,可能会产生一些逻辑错误。对于如何保证消息的顺序性,主要需要考虑如下两点:
Kafka
中顺序性;对于 Kafka
,如果我们创建了一个 topic
,默认有三个 partition
。生产者在写数据的时候,可以指定一个 key
,比如在订单 topic
中我们可以指定订单 id
作为 key
,那么相同订单 id
的数据,一定会被分发到同一个 partition
中去,而且这个 partition
中的数据一定是有顺序的。消费者从 partition
中取出来数据的时候,也一定是有顺序的。通过制定 key
的方式首先可以保证在 kafka
内部消息是有序的。
对于某个 topic
的一个 partition
,只能被同组内部的一个 consumer
消费,如果这个 consumer
内部还是单线程处理,那么其实只要保证消息在 MQ
内部是有顺序的就可以保证消费也是有顺序的。但是单线程吞吐量太低,在处理大量 MQ
消息时,我们一般会开启多线程消费机制,那么如何保证消息在多个线程之间是被顺序处理的呢?对于多线程消费我们可以预先设置 N
个内存 Queue
,具有相同 key
的数据都放到同一个内存 Queue
中;然后开启 N
个线程,每个线程分别消费一个内存 Queue
的数据即可,这样就能保证顺序性。当然,消息放到内存 Queue
中,有可能还未被处理,consumer
发生宕机,内存 Queue
中的数据会全部丢失,这就转变为上面提到的如何保证消息的可靠传输的问题了。
ISR
:In-Sync Replicas 副本同步队列AR
:Assigned Replicas 所有副本ISR是由leader维护,follower从leader同步数据有一些延迟(包括延迟时间replica.lag.time.max.ms
和延迟条数replica.lag.max.messages
两个维度,当前最新的版本0.10.x中只支持replica.lag.time.max.ms
这个维度),任意一个超过阈值都会把follower剔除出ISR,存入OSR(Outof-Sync Replicas)列表,新加入的follower也会先存放在OSR中。
AR=ISR+OSR。
分区的Leader副本选举对用户是完全透明的,它是由Controller独立完成的。你需要回答的是,在哪些场景下,需要执行分区Leader选举。每一种场景对应于一种选举策略。
这4类选举策略的大致思想是类似的,即从AR中挑选首个在ISR中的副本,作为新Leader。