一文带你深入了解kafka并提供52道企业常见面试题
1
Kafka基本概念
1
什么是kafka
Kafka是一个分布式消息中间件,支持分区,多副本,多订阅者,基于zookeeper协调的消息系统,它提供了类似于JMS的特性,但是在设计实现上完全不同,此外它并不是JMS规范的实现。Kafka对消息保存时根据Topic进行归类,发送消息者称为Producer,消息接受者称为Consumer,此外kafka集群有多个kafka实例组成,每个实例(server)成为broker。无论是kafka集群,还是producer和consumer都依赖于zookeeper集群保存一些meta信息,来保证系统可用性
kafka特点
高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒,每个topic可以分多个partition, 由多个consumer group 对partition进行consume操作。
可扩展性:kafka集群支持热扩展
持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
高并发:支持数千个客户端同时读写
kafka架构
整体架构
名称 | 解释 |
Broker | Kafka集群包含一个或多个服务器,这种服务器被称为broker |
Topic | 每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处) |
Partition | Partition是物理上的概念,每个Topic包含一个或多个Partition.kafka只保证一个分区内的数据是有序的,不保证一个topic下的所有partition全局有序 |
Producer | 消息生产者,负责发布消息到Kafka broker |
Consumer | 消息消费者,向Kafka broker读取消息的客户端 |
Consumer Group | 每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group) |
replica | partition 的副本,保障 partition 的高可用 |
leader | replica 中的一个角色, producer 和 consumer 只跟 leader 交互 |
follower | replica 中的一个角色,从 leader 中复制数据 |
controller | Kafka 集群中的其中一个服务器,用来进行 leader election 以及各种 failover |
AR(Assigned Replicas) | 分区中所有副本 |
ISR(In-Sync-Replicas) | 所有与leader副本保持一定程度同步的副本(包含leader副本在内) |
OSR(Out-of-Sync-Replicas) | 与leader副本同步滞后过多的副本(不包括leader副本) |
HW(Hight Watermark) | 高水位,它标识了一个特定的消息偏移量,消费者只能拉取到这个offset之前的消息 |
LEO(log End Offset) | 标识当前日志文件下下一条待写入消息的offset |
每个group中可以有多个consumer,每个consumer属于一个consumer group,通常情况下,一个group中会包含多个consumer,这样不仅可以提高topic中消息的并发消费能力,而且还能提高"故障容错"性,如果group中的某个consumer失效那么其消费的partitions将会有其他consumer自动接管。
对于Topic中的一条特定的消息,只会被订阅此Topic的每个group中的其中一个consumer消费,此消息不会发送给一个group的多个consumer,不过一个consumer可以同时消费多个partitions中的消息。
consumer负载均衡
当一个group中,有consumer加入或者离开时,会触发partitions均衡.均衡的最终目的,是提升topic的并发消费能力,步骤如下:
假如topic1,具有如下partitions: P0,P1,P2,P3 ;假如group中,有如下consumer: C1,C2
首先根据partition索引号对partitions排序: P0,P1,P2,P3 ; 根据consumer.id排序: C0,C1
计算倍数: M = [P0,P1,P2,P3].size / [C0,C1].size,本例值M=2(向上取整)
然后依次分配partitions: C0 = [P0,P1],C1=[P2,P3],即Ci = [P(i * M),P((i + 1) * M -1)]
kafka监控
KafkaOffsetMonitor
KafkaManager
Kafka Web Console
Kafka Eagle
kafka存储机制
Kafka文件存储基本结构
a.在Kafka文件存储中,同一个topic下有多个不同partition,每个partition为一个目录,partiton命名规则为topic名称+有序序号,第一个partiton序号从0开始,序号最大值为partitions数量减1。
如下图所示
b.每个partion(目录)相当于一个巨型文件被平均分配到多个大小相等segment(段)数据文件中;
每段segment file消息数量不一定相等,这种特性方便old segment file被高速删除(默认情况下每一个文件大小为1G);
每一个partiton仅仅须要支持顺序读写即可了。segment文件生命周期由服务端配置参数决定。默认保留7天的数据。
Kafka Partition Segment
Segment file组成:由2大部分组成,分别为index file和data file,此2个文件一一对应,成对出现,后缀".index"和“.log”分别表示为segment索引文件、数据文件。
Segment文件命名规则:partion全局的第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值。数值最大为64位long大小,19位数字字符长度,没有数字用0填充。
3.索引文件存储大量元数据,数据文件存储大量消息,索引文件中元数据指向对应数据文件中message的物理偏移地址。
上述图中索引文件存储大量元数据,数据文件存储大量消息,索引文件中元数据指向对应数据文件中message的物理偏移地址。
其中以索引文件中元数据3,497为例,依次在数据文件中表示第3个message(在全局partiton表示第368772个message)、以及该消息的物理偏移地址为497。
segment data file由许多message组成, 物理结构如下
关键字 | 解释说明 |
8 byte offset | 在parition(分区)内的每条消息都有一个有序的id号,这个id号被称为偏移(offset),它可以唯一确定每条消息在parition(分区)内的位置。即offset表示partiion的第多少message |
4 byte message size | message大小 |
4 byte CRC32 | 用crc32校验message |
1 byte “magic" | 表示本次发布Kafka服务程序协议版本号 |
1 byte “attributes" | 表示为独立版本、或标识压缩类型、或编码类型。 |
4 byte key length | 表示key的长度,当key为-1时,K byte key字段不填 |
K byte key | 可选 |
value bytes payload | 表示实际消息数据。 |
Kafka为什么查询速度快
分段
Kafka解决查询效率的手段之一是将数据文件分片,数据文件以该段中最小的offset命名。这样在查找指定offset的Message的时候,用二分查找就可以定位到该Message在哪个段(segment)中。
稀疏索引
为了进一步提高查找的效率,Kafka为每个分段后的数据文件建立了索引文件,文件名与数据文件的名字是一样的,只是文件扩展名为.index。
索引包含两个部分(均为4个字节的数字),分别为相对offset和position。
index文件中并没有为数据文件中的每条Message建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。
这样避免了索引文件占用过多空间,从而可以将索引文件保留在内存中。
但缺点是没有建立索引的Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。
示例:Kafka 查找message
比如:要查找绝对offset为7的Message:
首先是用二分查找确定它是在哪个LogSegment中,自然是在第一个Segment中。
打开这个Segment的index文件,也是用二分查找找到offset小于或者等于指定offset的索引条目中最大的那个offset。自然offset为6的那个索引是我们要找的,通过索引文件我们知道offset为6的Message在数据文件中的位置为9807。
打开数据文件,从位置为9807的那个地方开始顺序扫描直到找到offset为7的那条Message。
这套机制是建立在offset是有序的。索引文件被映射到内存中,所以查找的速度还是很快的。
一句话,Kafka的Message存储采用了分区(partition),分段(LogSegment)和稀疏索引这几个手段来达到了高效性。
顺序读写
零拷贝
所谓的零拷贝是指将数据直接从磁盘文件复制到网卡设备中,而不需要经由应用程序之
手。零拷贝大大提高了应用程序的性能,减少了内核和用户模式之间的上下文切换 ,对linux
操作系统而言,零拷贝技术依赖于底层的 se ndfile () 方法实现 对应于 Java 语言,
Fi eChannal.transferTo ()方法的底层实现就是 sendfile ()方法
零拷贝技术通过 DMA (Direct Memory Access )技术将文件内容复制到内核模式下的 Read
Buffer 。不过没有数据被复制到 Socke Buffer ,相反只有包含数据的位置和长度的信息的文
件描述符被加到 Socket Buffer DMA 引擎直接将数据从内核模式中传递到网卡设备(协议
引擎)。这里数据只经历了 2次复制就从磁盘中传送出去了 并且上下文切换也变成了2 次。
零拷贝是针对内 核模式而言的 数据在内核模式下实现了零拷贝
批量发送
生产者发送多个消息到同一个分区的时候,为了减少网络带来的系能开销,kafka会对消息进行批量发送
batch.size
通过这个参数来设置批量提交的数据大小,默认是16k,当积压的消息达到这个值的时候就会统一发送(发往同一分区的消息)
数据压缩。
Producer 端压缩、Broker 端保持、Consumer 端解压缩。
kafka基本命令
查看分区情况
bin/kafka-topics.sh --zookeeper zk01:2181 --describe --topic lzc_test |
修改分区数
bin/kafka-topics.sh --zookeeper zk01:2181 --alter --topic lzc_test --partition 3 |
查询所有的topic
bin/kafka-topics.sh --zookeeper zk01:2181 --list |
创建topic
bin/kafka-topics.sh --zookeeper zk01:2181 --create --replication-factor 2 --partitions 3 --topic tt_t |
删除topic
bin/kafka-topics.sh --zookeeper zk01:2181 --delete --topic lzc_test |
命令行发送消息
bin/kafka-console-producer.sh --broker-list kafka01:9092 --topic lzc_test |
命令行消费消息
bin/kafka-console-consumer.sh --bootstrap-server kafka01:9092 --from-beginning --topic lzc_test |
指定要消费的分区,和要消费的起始offset
bin/kafka-console-consumer.sh --bootstrap-server kafka01:9092 --topic lzc_test --offset 10 --partition 1 |
编程
<dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.11</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>2.0.1</version> </dependency> |
producer生产者
package com.wedoctor; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; import java.util.Properties; public class MyProducer { public static void main(String[] args) throws InterruptedException { Properties props = new Properties(); props.put("bootstrap.servers", "kafka01:9092"); props.put("acks", "all"); props.put("batch.size", 10); props.put("linger.ms", 10000); props.put("buffer.memory", 10240); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = new KafkaProducer<>(props); for (int i = 0; i < 200; i++) producer.send(new ProducerRecord<String, String>("lzc_test", Integer.toString(i), "kafka_producer_test:"+i)); producer.close(); } } |
Consumer消费者
package com.wedoctor; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; import java.util.Arrays; import java.util.Properties; public class MyConsumer { public static void main(String[] args) { Properties props = new Properties(); props.put("bootstrap.servers", "kafka01:9092"); props.put("group.id", "lzc"); props.put("enable.auto.commit", "true"); props.put("auto.commit.interval.ms", "1000"); props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("auto.offset.reset","earliest"); KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props); consumer.subscribe(Arrays.asList("lzc_test")); while (true) { ConsumerRecords<String, String> records = consumer.poll(100); for (ConsumerRecord<String, String> record : records) System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); } } } |
深入了解kafka
数据可靠性
Topic分区副本角度
Kafka 可以保证单个分区里的事件是有序的,分区可以在线(可用),也可以离线(不可用)。在众多的分区副本里面有一个副本是 Leader,其余的副本是 follower,所有的读写操作都是经过 Leader 进行的,同时 follower 会定期地去 leader 上的复制数据。当 Leader 挂了的时候,其中一个 follower 会重新成为新的 Leader。通过分区副本,引入了数据冗余,同时也提供了 Kafka 的数据可靠性。
Kafka 的分区多副本架构是 Kafka 可靠性保证的核心,把消息写入多个副本可以使 Kafka 在发生崩溃时仍能保证消息的持久性。
ACK机制
如果我们要往 Kafka 对应的topic发送消息,我们需要通过 Producer 完成。Kafka 在 Producer 里面提供了消息确认机制。也就是说我们可以通过配置来决定消息发送到对应分区的几个副本才算消息发送成功。可以在定义 Producer 时通过 acks 参数指定(在 0.8.2.X 版本之前是通过 request.required.acks 参数设置的)。这个参数支持以下三种值:
acks = 0:意味着如果生产者能够通过网络把消息发送出去,那么就认为消息已成功写入 Kafka 。在这种情况下还是有可能发生错误,比如发送的对象无能被序列化或者网卡发生故障,但如果是分区离线或整个集群长时间不可用,那就不会收到任何错误。在 acks=0 模式下的运行速度是非常快的,你可以得到惊人的吞吐量和带宽利用率,不过如果选择了这种模式, 一定会丢失一些消息。
acks = 1:意味若 Leader 在收到消息并把它写入到分区数据文件(不一定同步到磁盘上)时会返回确认或错误响应。在这个模式下,如果发生正常的 Leader 选举,生产者会在选举时收到一个 LeaderNotAvailableException 异常,如果生产者能恰当地处理这个错误,它会重试发送悄息,最终消息会安全到达新的 Leader 那里。不过在这个模式下仍然有可能丢失数据,比如消息已经成功写入 Leader,但在消息被复制到 follower 副本之前 Leader发生崩溃。
acks = all(这个和 request.required.acks = -1 含义一样):意味着 Leader 在返回确认或错误响应之前,会等待所有同步副本都收到悄息。如果和 min.insync.replicas 参数结合起来,就可以决定在返回确认前至少有多少个副本能够收到悄息,生产者会一直重试直到消息被成功提交。不过这也是最慢的做法,因为生产者在继续发送其他消息之前需要等待所有副本都收到当前的消息。
根据实际的应用场景,我们设置不同的 acks,以此保证数据的可靠性。
另外,Producer 发送消息还可以选择同步(默认,通过 producer.type=sync 配置) 或者异步(producer.type=async)模式。如果设置成异步,虽然会极大的提高消息发送的性能,但是这样会增加丢失数据的风险。如果需要确保消息的可靠性,必须将 producer.type 设置为 sync。
数据一致性
ISR同步副本列表及Leader 选举
Kafka中,一个topic的某个partition的leader如果失效,可以在该partition的另几个副本中动态选举出一个新的leader
ISR概念:在介绍 Leader 选举之前,让我们先来了解一下 ISR(in-sync replicas)列表(同步副本)。每个分区的 leader 会维护一个 ISR 列表,ISR 列表里面就是 follower 副本的 Borker 编号,只有跟得上 Leader 的 follower 副本才能加入到 ISR 里面,这个是通过 replica.lag.time.max.ms 参数配置的,只有 ISR 里的成员才有被选为 leader 的可能。
所以当Leader挂掉了,而且 unclean.leader.election.enable=false 的情况下,Kafka 会从 ISR 列表中选择第一个follower作为新的Leader,因为这个分区拥有最新的已经committed的消息。通过这个可以保证已经committed的消息的数据可靠性。
高水位线HIGH WATER MARK
这里介绍的数据一致性主要是说不论是老的 Leader 还是新选举的 Leader,Consumer 都能读到一样的数据。那么 Kafka 是如何实现的呢?
假设分区的副本为3,其中副本0是 Leader,副本1和副本2是 follower,并且在 ISR 列表里面。虽然副本0已经写入了 Message4,但是 Consumer 只能读取到 Message2。因为所有的 ISR 都同步了 Message2,只有 High Water Mark 以上的消息才支持 Consumer 读取,而 High Water Mark 取决于 ISR 列表里面偏移量最小的分区,对应于上图的副本2,这个很类似于木桶原理。
这样做的原因是还没有被足够多副本复制的消息被认为是“不安全”的,如果 Leader 发生崩溃,另一个副本成为新 Leader,那么这些消息很可能丢失了。如果我们允许消费者读取这些消息,可能就会破坏一致性。试想,一个消费者从当前 Leader(副本0) 读取并处理了 Message4,这个时候 Leader 挂掉了,选举了副本1为新的 Leader,这时候另一个消费者再去从新的 Leader 读取消息,发现这个消息其实并不存在,这就导致了数据不一致性问题。
当然,引入了 High Water Mark 机制,会导致 Broker 间的消息复制因为某些原因变慢,那么消息到达消费者的时间也会随之变长(因为我们会先等待消息复制完毕)。延迟时间可以通过参数 replica.lag.time.max.ms 参数配置,它指定了副本在复制消息时可被允许的最大延迟时间。
不清洁选举unclean.leader.election.enable
从Kafka 0.11.0.0版本开始unclean.leader.election.enable参数的默认值由原来的true改为false,这个参数背后到底意味着什么,Kafka的设计者处于什么原因要修改这个默认值?
参考上图,某种状态下,follower2副本落后leader副本很多,并且也不在leader副本和follower1副本所在的ISR(In-Sync Replicas)集合之中。follower2副本正在努力的追赶leader副本以求迅速同步,并且能够加入到ISR中。但是很不幸的是,此时ISR中的所有副本都突然下线,情形如下图所示:
此时follower2副本还在,就会进行新的选举,不过在选举之前首先要判断unclean.leader.election.enable参数的值。如果unclean.leader.election.enable参数的值为false,那么就意味着非ISR中的副本不能够参与选举,此时无法进行新的选举,此时整个分区处于不可用状态。如果unclean.leader.election.enable参数的值为true,那么可以从非ISR集合中选举follower副本称为新的leader。
我们进一步考虑unclean.leader.election.enable参数为true的情况,在上面的这种情形中follower2副本就顺其自然的称为了新的leader。随着时间的推进,新的leader副本从客户端收到了新的消息,如上图所示。
此时,原来的leader副本恢复,成为了新的follower副本,准备向新的leader副本同步消息,但是它发现自身的LEO比leader副本的LEO还要大。Kafka中有一个准则,follower副本的LEO是不能够大于leader副本的,所以新的follower副本就需要截断日志至leader副本的LEO处。
如上图所示,新的follower副本需要删除消息4和消息5,之后才能与新的leader副本进行同步。之后新的follower副本和新的leader副本组成了新的ISR集合,参考下图。
原本客户端已经成功的写入了消息4和消息5,而在发生日志截断之后就意味着这2条消息就丢失了,并且新的follower副本和新的leader副本之间的消息也不一致。也就是说如果unclean.leader.election.enable参数设置为true,就有可能发生数据丢失和数据不一致的情况,Kafka的可靠性就会降低;而如果unclean.leader.election.enable参数设置为false,Kafka的可用性就会降低。具体怎么选择需要读者更具实际的业务逻辑进行权衡,可靠性优先还是可用性优先。从Kafka 0.11.0.0版本开始将此参数从true设置为false,可以看出Kafka的设计者偏向于可靠性,如果能够容忍uncleanLeaderElection场景带来的消息丢失和不一致,可以将此参数设置为之前的老值——true。
消息丢失与重复
数据丢失
提交了偏移量,但是消费的时候发生了异常
数据重复
数据消费正常,但是提交偏移量失败
kafka面试实战题
Kafka名词解释和工作方式
Producer :消息生产者,就是向kafka broker发消息的客户端。
Consumer :消息消费者,向kafka broker取消息的客户端
Topic :咋们可以理解为一个队列。
Consumer Group (CG):这是kafka用来实现一个topic消息的广播(发给所有的consumer)和单播(发给任意一个consumer)的手段。一个topic可以有多个CG。topic的消息会复制(不是真的复制,是概念上的)到所有的CG,但每个partion只会把消息发给该CG中的一个consumer。如果需要实现广播,只要每个consumer有一个独立的CG就可以了。要实现单播只要所有的consumer在同一个CG。用CG还可以将consumer进行自由的分组而不需要多次发送消息到不同的topic。
Broker :一台kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic。
Partition:为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,一个topic可以分为多个partition,每个partition是一个有序的队列。partition中的每条消息都会被分配一个有序的id(offset)。kafka只保证按一个partition中的顺序将消息发给consumer,不保证一个topic的整体(多个partition间)的顺序。
Offset:kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找。例如你想找位于2049的位置,只要找到2048.kafka的文件即可。当然the first offset就是00000000000.kafka
Consumer与topic关系
本质上kafka只支持Topic;
每个group中可以有多个consumer,每个consumer属于一个consumer group;
通常情况下,一个group中会包含多个consumer,这样不仅可以提高topic中消息的并发消费能力,而且还能提高"故障容错"性,如果group中的某个consumer失效那么其消费的partitions将会有其他consumer自动接管。
对于Topic中的一条特定的消息,只会被订阅此Topic的每个group中的其中一个consumer消费,此消息不会发送给一个group的多个consumer;
那么一个group中所有的consumer将会交错的消费整个Topic,每个group中consumer消息消费互相独立,我们可以认为一个group是一个"订阅"者。
在kafka中,一个partition中的消息只会被group中的一个consumer消费(同一时刻);
一个Topic中的每个partions,只会被一个"订阅者"中的一个consumer消费,不过一个consumer可以同时消费多个partitions中的消息。
kafka的设计原理决定,对于一个topic,同一个group中不能有多于partitions个数的consumer同时消费,否则将意味着某些consumer将无法得到消息。
kafka只能保证一个partition中的消息被某个consumer消费时是顺序的;事实上,从Topic角度来说,当有多个partitions时,消息仍不是全局有序的。
kafka中生产数据的时候,如何保证写入的容错性?
设置发送数据是否需要服务端的反馈,有三个值0,1,-1
0: producer不会等待broker发送ack
1: 当leader接收到消息之后发送ack
-1: 当所有的follower都同步消息成功后发送ack
request.required.acks=0
如何保证kafka消费者消费数据是全局有序的
伪命题
每个分区内,每条消息都有一个offset,故只能保证分区内有序。
如果要全局有序的,必须保证生产有序,存储有序,消费有序。
由于生产可以做集群,存储可以分片,消费可以设置为一个consumerGroup,要保证全局有序,就需要保证每个环节都有序。
只有一个可能,就是一个生产者,一个partition,一个消费者。这种场景和大数据应用场景相悖。
消息丢失和消息重复
同步:这个生产者写一条消息的时候,它就立马发送到某个分区去。
异步:这个生产者写一条消息的时候,先是写到某个缓冲区,这个缓冲区里的数据还没写到 broker集群里的某个分区的时候,它就返回到 client去了
针对消息丢失:同步模式下,确认机制设置为-1,即让消息写入 Leader和 Fol lower之后再确认消息发送成功:
异步模式下,为防止缓冲区满,可以在配置文件设置不限制阻塞超时时间,当缓冲区满时让生产者一直处于阻塞状态
针对消息重复,将消息的唯一标识保存到外部介质中,每次消费时判断是否处理过即可
Kafka和SparkStreaing如何集成?
package com.wedoctor.spark.dstream import java.sql.{Connection, PreparedStatement} import com.wedoctor.spark.utils.{DruidConnectionPool, OffsetUtils} import org.apache.kafka.clients.consumer.ConsumerRecord import org.apache.kafka.common.TopicPartition import org.apache.kafka.common.serialization.StringDeserializer import org.apache.spark.SparkConf import org.apache.spark.rdd.RDD import org.apache.spark.streaming.dstream.InputDStream import org.apache.spark.streaming.kafka010._ import org.apache.spark.streaming.{Seconds, StreamingContext} //这是一个聚合类型的运算,将计算好的结果和偏移量在一个事务中都保存到MySQL object KafkaToMySQL { def main(args: Array[String]): Unit = { val appId = args(0) val groupId = args(1) //实时计算创建StreamingContext,(StreamingContext是对SparkContext的增强包装,里面持有者SparkContext的引用) val conf = new SparkConf().setAppName(appId).setMaster("local[*]") val ssc: StreamingContext = new StreamingContext(conf, Seconds(5)) ssc.sparkContext.setLogLevel("WARN") //Kafka相关的参数 val kafkaParams = Map[String, Object]( "bootstrap.servers" -> "node-1.com.wedoctor:9092,node-2.com.wedoctor:9092,node-3.com.wedoctor:9092", "key.deserializer" -> classOf[StringDeserializer], "value.deserializer" -> classOf[StringDeserializer], "group.id" -> groupId, "auto.offset.reset" -> "earliest", "enable.auto.commit" -> (false: java.lang.Boolean) //让消费者不用自动提交偏移量 ) val topics = Array("wordcount") //读取历史偏移量(在Driver端查询历史偏移量) val offset: Map[TopicPartition, Long] = OffsetUtils.queryHistoryOffsetFromMySQL(appId, groupId) //sparkStreaming跟Kafka整合,使用的是官方推荐的直连方式,使用Kafka底层的消费API,效率更高 val kafkaDStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream( ssc, //传入StreamingContext LocationStrategies.PreferConsistent, //位置策略 ConsumerStrategies.Subscribe[String, String](topics, kafkaParams, offset) //消费策略(订阅的topic,kafka参数,历史偏移量) ) kafkaDStream.foreachRDD(rdd => { if(!rdd.isEmpty()) { //获取偏移量信息 val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges //实现聚合的功能 val lines = rdd.map(_.value()) val reduced: RDD[(String, Int)] = lines.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _) //将聚合后的数据收集到Driver端 val results: Array[(String, Int)] = reduced.collect() //只适用聚合类的运算 //获取一个数据库连接(适用数据库连接池) var connection: Connection = null var pstm1: PreparedStatement = null var pstm2: PreparedStatement = null try { connection = DruidConnectionPool.getConnection //开启事务 connection.setAutoCommit(false) //将计算好的聚合数据写入到MySQL, t_wordcount的表,有两个字段,word 主键,counts long pstm1 = connection.prepareStatement("INSERT INTO t_wordcount (word, counts) VALUES (?, ?) ON DUPLICATE KEY UPDATE counts = counts + ?") //设置参数 for (t <- results) { pstm1.setString(1, t._1) //设置单词 pstm1.setLong(2, t._2) pstm1.setLong(3, t._2) pstm1.executeUpdate() //pstm1.addBatch() } //pstm1.executeBatch() pstm2 = connection.prepareStatement("INSERT INTO t_kafka_offset (app_gid, topic_partition, offset) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE offset = ?") //将偏移量写入到数据库中 t_kafka_offset : 分析(topic、分区、组ID、结束偏移量) for (range <- offsetRanges) { val topic = range.topic val partition = range.partition val offset = range.untilOffset //设置参数 pstm2.setString(1, appId + "_" + groupId) pstm2.setString(2, topic + "_" + partition) pstm2.setLong(3, offset) pstm2.setLong(4, offset) //执行update pstm2.executeUpdate() } //提交事务 connection.commit() } catch { case e: Exception => { e.printStackTrace() //回滚 connection.rollback() //停掉sparkstreaming ssc.stop(true) } } finally { //释放资源 if(pstm2 != null) { pstm2.close() } if(pstm1 != null) { pstm1.close() } if(connection != null) { connection.close() } } } }) //开启 ssc.start() //让程序一直运行,将Driver挂起 ssc.awaitTermination() } } |
列举Kafka的优点,简述Kafka为什么可以做到每秒数十万甚至上百万消息的高效分发?
详情可参考4.3
为什么离线分析要用kafka?
Kafka的作用是解耦,如果直接从日志服务器上采集的话,实时离线都要采集,等于要采集两份数据,而使用了kafka的话,只需要从日志服务器上采集一份数据,然后在kafka中使用不同的两个组读取就行了
相比较于传统消息队列,kafka的区别
1、分区性:存储不会受单一服务器存储空间的限制
2、高可用性:副本及Leader选举
3、消息有序性:一个分区内是有序的。
4、负载均衡性:分区内的一条消息,只会被消费组中的一个消费者消费,主题中的消息,会均衡的发送给消费者组中的所有消费者进行消费。
kafka消息的存储机制
kafka通过 topic来分主题存放数据,主题内有分区,分区可以有多个副本,分区的内部还细分为若干个 segment。都是持久化到磁盘,采用零拷贝技术。
1、高效检索
分区下面,会进行分段操作,每个分段都会有对应的素引,这样就可以根据 offset二分查找定位到消息在哪一段,根据段的索引文件,定位具体的 mle ssage
2、分区副本可用性(1 eader选举,zk来协调
如果1eader宕机,选出了新的1eader,而新的 leader并不能保证已经完全同步了之前1eader的所有数据,只能保证HW(高水位设置)之前的数据是同步过的,此时所有的 follower都要将数据截断到W的位置,再和新的 leader同步数据,来保证数据一致。
当宕机的 leader恢复,发现新的1eader中的数据和自己持有的数据不一致,此时宕机的1 eader会将自己的数据截断到宕机之前的hw位置,然后同步新1 eader的数据。宕机的1eader活过来也像 follower一样同步数据,来保证数据的一致性。
Kafka api low-level与high-level有什么区别,使用low-level需要处理哪些细节
Kafka的ISR副本同步队列
ISR(In-Sync Replicas),副本同步队列。ISR中包括Leader和Follower。如果Leader进程挂掉,会在ISR队列中选择一个服务作为新的Leader。有replica.lag.max.messages(延迟条数)和replica.lag.time.max.ms(延迟时间)两个参数决定一台服务是否可以加入ISR副本队列,在0.10版本移除了replica.lag.max.messages参数,防止服务频繁的进去队列。
任意一个维度超过阈值都会把Follower剔除出ISR,存入OSR(Outof-Sync Replicas)列表,新加入的Follower也会先存放在OSR中。
Kafka消息数据积压,Kafka消费能力不足怎么处理?
1)如果是Kafka消费能力不足,则可以考虑增加Topic的分区数,并且同时提升消费组的消费者数量,消费者数=分区数。(两者缺一不可)
2)如果是下游的数据处理不及时:提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间<生产速度),使处理的数据小于生产的数据,也会造成数据积压。
Kafka中的ISR、AR又代表什么?
ISR:in-sync replicas set (ISR),与leader保持同步的follower集合
AR:分区的所有副本
Kafka中的HW、LEO等分别代表什么?
LEO:是 LogEndOffset 的简称,代表当前日志文件中下一条
HW:水位或水印(watermark)一词,也可称为高水位(high watermark),通常被用在流式处理领域(比如Apache Flink、Apache Spark等),以表征元素或事件在基于时间层面上的进度。在Kafka中,水位的概念反而与时间无关,而是与位置信息相关。严格来说,它表示的就是位置信息,即位移(offset)。取 partition 对应的 ISR中 最小的 LEO 作为 HW,consumer 最多只能消费到 HW 所在的位置上一条信息。
哪些情景会造成消息漏消费?
先提交offset,后消费,有可能造成数据的重复
当你使用kafka-topics.sh创建了一个topic之后,Kafka背后会执行什么逻辑?
1)会在zookeeper中的/brokers/topics节点下创建一个新的topic节点,如:/brokers/topics/first
2)触发Controller的监听程序
3)kafka Controller 负责topic的创建工作,并更新metadata cache
topic的分区数可不可以增加?如果可以怎么增加?如果不可以,那又是为什么?
可以增加
bin/kafka-topics.sh --zookeeper localhost:2181/kafka --alter --topic topic-config --partitions 3
topic的分区数可不可以减少?如果可以怎么减少?如果不可以,那又是为什么?
不可以减少,被删除的分区数据难以处理。
Kafka有内部的topic吗?如果有是什么?有什么所用?
__consumer_offsets,保存消费者offset
聊一聊Kafka Controller的作用?
负责管理集群broker的上下线,所有topic的分区副本分配和leader选举等工作。
失效副本是指什么?有那些应对措施?
不能及时与leader同步,暂时踢出ISR,等其追上leader之后再重新加入
Kafka 都有哪些特点?
高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒,每个topic可以分多个partition, consumer group 对partition进行consume操作。
可扩展性:kafka集群支持热扩展
持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
高并发:支持数千个客户端同时读写
请简述下你在哪些场景下会选择 Kafka?
日志收集:一个公司可以用Kafka可以收集各种服务的log,通过kafka以统一接口服务的方式开放给各种consumer,例如hadoop、HBase、Solr等。
消息系统:解耦和生产者和消费者、缓存消息等。
用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到hadoop、数据仓库中做离线分析和挖掘。
运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。
流式处理:比如spark streaming和 Flink
Kafka 的设计架构你知道吗?
架构图如下
详细如下
Kafka 架构分为以下几个部分
Producer :消息生产者,就是向 kafka broker 发消息的客户端。
Consumer :消息消费者,向 kafka broker 取消息的客户端。
Topic :可以理解为一个队列,一个 Topic 又分为一个或多个分区。
Consumer Group:这是 kafka 用来实现一个 topic 消息的广播(发给所有的 consumer)和单播(发给任意一个 consumer)的手段。一个 topic 可以有多个 Consumer Group。
Broker :一台 kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker 可以容纳多个 topic。
Partition:为了实现扩展性,一个非常大的 topic 可以分布到多个 broker上,每个 partition 是一个有序的队列。partition 中的每条消息都会被分配一个有序的id(offset)。将消息发给 consumer,kafka 只保证按一个 partition 中的消息的顺序,不保证一个 topic 的整体(多个 partition 间)的顺序。
Offset:kafka 的存储文件都是按照 offset.kafka 来命名,用 offset 做名字的好处是方便查找。例如你想找位于 2049 的位置,只要找到 2048.kafka 的文件即可。当然 the first offset 就是 00000000000.kafka。
Kafka 分区的目的?
分区对于 Kafka 集群的好处是:实现负载均衡。分区对于消费者来说,可以提高并发度,提高效率。
你知道 Kafka 是如何做到消息的有序性?
kafka 中的每个 partition 中的消息在写入时都是有序的,而且消息带有offset偏移量,消费者按偏移量的顺序从前往后消费,从而保证了消息的顺序性。但是分区之间的消息是不保证有序的。即无法保证全局有序
Kafka 的高可靠性是怎么实现的?
分区副本及ACK机制
kafka通过分区的多副本机制来保证消息的可靠性。
1. 每个分区可以设置多个副本,这些副本分布在不同的broker上;
2. 相同partition的多个副本能动态选举leader来对外服务和管理内部数据同步。这样,即使有broker出现故障,受影响的partition也会在其他broker上重新选举出新的leader来继续服务
如果我们要往 Kafka 对应的topic发送消息,我们需要通过 Producer 完成。Kafka 在 Producer 里面提供了消息确认机制。也就是说我们可以通过配置来决定消息发送到对应分区的几个副本才算消息发送成功。可以在定义 Producer 时通过 acks 参数指定(在 0.8.2.X 版本之前是通过 request.required.acks 参数设置的)。这个参数支持以下三种值:
acks = 0:意味着如果生产者能够通过网络把消息发送出去,那么就认为消息已成功写入 Kafka 。在这种情况下还是有可能发生错误,比如发送的对象无能被序列化或者网卡发生故障,但如果是分区离线或整个集群长时间不可用,那就不会收到任何错误。在 acks=0 模式下的运行速度是非常快的(这就是为什么很多基准测试都是基于这个模式),你可以得到惊人的吞吐量和带宽利用率,不过如果选择了这种模式, 一定会丢失一些消息。
acks = 1:意味若 Leader 在收到消息并把它写入到分区数据文件(不一定同步到磁盘上)时会返回确认或错误响应。在这个模式下,如果发生正常的 Leader 选举,生产者会在选举时收到一个 LeaderNotAvailableException 异常,如果生产者能恰当地处理这个错误,它会重试发送悄息,最终消息会安全到达新的 Leader 那里。不过在这个模式下仍然有可能丢失数据,比如消息已经成功写入 Leader,但在消息被复制到 follower 副本之前 Leader发生崩溃。
acks = all(这个和 request.required.acks = -1 含义一样):意味着 Leader 在返回确认或错误响应之前,会等待所有同步副本都收到悄息。如果和 min.insync.replicas 参数结合起来,就可以决定在返回确认前至少有多少个副本能够收到悄息,生产者会一直重试直到消息被成功提交。不过这也是最慢的做法,因为生产者在继续发送其他消息之前需要等待所有副本都收到当前的消息。
根据实际的应用场景,我们设置不同的 acks,以此保证数据的可靠性。
另外,Producer 发送消息还可以选择同步(默认,通过 producer.type=sync 配置) 或者异步(producer.type=async)模式。如果设置成异步,虽然会极大的提高消息发送的性能,但是这样会增加丢失数据的风险。如果需要确保消息的可靠性,必须将 producer.type 设置为 sync。
请谈一谈 Kafka 数据一致性原理
一致性就是说不论是老的 Leader 还是新选举的 Leader,Consumer 都能读到一样的数据。
假设分区的副本为3,其中副本0是 Leader,副本1和副本2是 follower,并且在 ISR 列表里面。虽然副本0已经写入了 Message4,但是 Consumer 只能读取到 Message2。因为所有的 ISR 都同步了 Message2,只有 High Water Mark 以上的消息才支持 Consumer 读取,而 High Water Mark 取决于 ISR 列表里面偏移量最小的分区,对应于上图的副本2,这个很类似于木桶原理。
这样做的原因是还没有被足够多副本复制的消息被认为是“不安全”的,如果 Leader 发生崩溃,另一个副本成为新 Leader,那么这些消息很可能丢失了。如果我们允许消费者读取这些消息,可能就会破坏一致性。试想,一个消费者从当前 Leader(副本0) 读取并处理了 Message4,这个时候 Leader 挂掉了,选举了副本1为新的 Leader,这时候另一个消费者再去从新的 Leader 读取消息,发现这个消息其实并不存在,这就导致了数据不一致性问题。
当然,引入了 High Water Mark 机制,会导致 Broker 间的消息复制因为某些原因变慢,那么消息到达消费者的时间也会随之变长(因为我们会先等待消息复制完毕)。延迟时间可以通过参数 replica.lag.time.max.ms 参数配置,它指定了副本在复制消息时可被允许的最大延迟时间。
ISR、OSR、AR 是什么?
ISR:In-Sync Replicas 副本同步队列
OSR:Out-of-Sync Replicas
AR:Assigned Replicas 所有副本
ISR是由leader维护,follower从leader同步数据有一些延迟(具体可以参见 图文了解 Kafka 的副本复制机制),超过相应的阈值会把 follower 剔除出 ISR, 存入OSR(Out-of-Sync Replicas )列表,新加入的follower也会先存放在OSR中。AR=ISR+OSR。
LEO、HW、LSO、LW等分别代表什么
LEO:是 LogEndOffset 的简称,代表当前日志文件中下一条
HW:水位或水印(watermark)一词,也可称为高水位(high watermark),通常被用在流式处理领域(比如Apache Flink、Apache Spark等),以表征元素或事件在基于时间层面上的进度。在Kafka中,水位的概念反而与时间无关,而是与位置信息相关。严格来说,它表示的就是位置信息,即位移(offset)。取 partition 对应的 ISR中 最小的 LEO 作为 HW,consumer 最多只能消费到 HW 所在的位置上一条信息。
LSO:是 LastStableOffset 的简称,对未完成的事务而言,LSO 的值等于事务中第一条消息的位置(firstUnstableOffset),对已完成的事务而言,它的值同 HW 相同
LW:Low Watermark 低水位, 代表 AR 集合中最小的 logStartOffset 值。
Kafka 在什么情况下会出现消息丢失?
topic的副本如果只有1个,那么一旦这个副本所在broker服务器宕机,则有可能丢失
producer往kafka写入数据时,如果确认机制参数acsk!=all,也可能会造成数据丢失;
不清洁选举机制如果开启,也可能造成数据丢失(不清洁选举就是说在所有ISR副本全部宕机的情况下,可以让OSR副本成为Leader,而OSR中的数据显然不全;那么,就算之前的Leader重新上线了,也会被进行日志截断)
怎么尽可能保证 Kafka 的可靠性
参考7.1和7.2
消费者和消费者组有什么关系?
每个消费者从属于消费组
Kafka 的每个分区只能被一个消费者线程,如何做到多个线程同时消费一个分区?
数据传输的事务有几种?
数据传输的事务定义通常有以下三种级别:
最多一次: 消息不会被重复发送,最多被传输一次,但也有可能一次不传输
最少一次: 消息不会被漏发送,最少被传输一次,但也有可能被重复传输.
精确的一次(Exactly once): 不会漏传输也不会重复传输,每个消息都传输被
Kafka 消费者是否可以消费指定分区消息?
Kafa consumer消费消息时,向broker发出fetch请求去消费特定分区的消息,consumer指定消息在日志中的偏移量(offset),就可以消费从这个位置开始的消息,customer拥有了offset的控制权,可以向后回滚去重新消费之前的消息,这是很有意义的
Kafka消息是采用Pull模式,还是Push模式?
Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers将消息推送到consumer,也就是pull还push。在这方面,Kafka遵循了一种大部分消息系统共同的传统的设计:producer将消息推送到broker,consumer从broker拉取消息。
一些消息系统比如Scribe和Apache Flume采用了push模式,将消息推送到下游的consumer。这样做有好处也有坏处:由broker决定消息推送的速率,对于不同消费速率的consumer就不太好处理了。消息系统都致力于让consumer以最大的速率最快速的消费消息,但不幸的是,push模式下,当broker推送的速率远大于consumer消费的速率时,consumer恐怕就要崩溃了。最终Kafka还是选取了传统的pull模式。Pull模式的另外一个好处是consumer可以自主决定是否批量的从broker拉取数据。Push模式必须在不知道下游consumer消费能力和消费策略的情况下决定是立即推送每条消息还是缓存之后批量推送。如果为了避免consumer崩溃而采用较低的推送速率,将可能导致一次只推送较少的消息而造成浪费。Pull模式下,consumer就可以根据自己的消费能力去决定这些策略。Pull有个缺点是,如果broker没有可供消费的消息,将导致consumer不断在循环中轮询,直到新消息到t达。为了避免这点,Kafka有个参数可以让consumer阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发
Kafka 高效文件存储设计特点
Kafka把topic中一个parition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用。
通过索引信息可以快速定位message和确定response的最大大小。
通过index元数据全部映射到memory,可以避免segment file的IO磁盘操作。
通过索引文件稀疏存储,可以大幅降低index文件元数据占用空间大小
Kafka创建Topic时如何将分区放置到不同的Broker中
副本因子不能大于 Broker 的个数;
第一个分区(编号为0)的第一个副本放置位置是随机从 brokerList 选择的;
其他分区的第一个副本放置位置相对于第0个分区依次往后移。也就是如果我们有5个 Broker,5个分区,假设第一个分区放在第四个 Broker 上,那么第二个分区将会放在第五个 Broker 上;第三个分区将会放在第一个 Broker 上;第四个分区将会放在第二个 Broker 上,依次类推;
剩余的副本相对于第一个副本放置位置其实是由 nextReplicaShift 决定的,而这个数也是随机产生的;
Kafka新建的分区会在哪个目录下创建
我们知道,在启动 Kafka 集群之前,我们需要配置好 log.dirs 参数,其值是 Kafka 数据的存放目录,这个参数可以配置多个目录,目录之间使用逗号分隔,通常这些目录是分布在不同的磁盘上用于提高读写性能。当然我们也可以配置 log.dir 参数,含义一样。只需要设置其中一个即可。
如果 log.dirs 参数只配置了一个目录,那么分配到各个 Broker 上的分区肯定只能在这个目录下创建文件夹用于存放数据。
但是如果 log.dirs 参数配置了多个目录,那么 Kafka 会在哪个文件夹中创建分区目录呢?答案是:Kafka 会在含有分区目录最少的文件夹中创建新的分区目录,分区目录名为 Topic名+分区ID。注意,是分区文件夹总数最少的目录,而不是磁盘使用量最少的目录!也就是说,如果你给 log.dirs 参数新增了一个新的磁盘,新的分区目录肯定是先在这个新的磁盘上创建直到这个新的磁盘目录拥有的分区目录不是最少为止。
谈一谈 Kafka 的消费者组分区分配再均衡
在Kafka中,当有新消费者加入或者订阅的topic数发生变化时,会触发Rebalance(再均衡:在同一个消费者组当中,分区的所有权从一个消费者转移到另外一个消费者)机制,Rebalance顾名思义就是重新均衡消费者消费。Rebalance的过程如下:
第一步:所有成员都向coordinator发送请求,请求入组。一旦所有成员都发送了请求,coordinator会从中选择一个consumer担任leader的角色,并把组成员信息以及订阅信息发给leader。第二步:leader开始分配消费方案,指明具体哪个consumer负责消费哪些topic的哪些partition。一旦完成分配,leader会将这个方案发给coordinator。coordinator接收到分配方案之后会把方案发给各个consumer,这样组内的所有成员就都知道自己应该消费哪些分区了。所以对于Rebalance来说,Coordinator起着至关重要的作用
谈谈 Kafka 分区分配策略
如果topic的分区数 == 消费group的消费者个数,则每个消费者负责一个分区的消费
如果topic的分区数 < 消费group的消费者个数,则其中有n个消费各自负责一个分区,多余的消费者就没有分区分配!
如果topic的分区数 > 消费group的消费者个数,则每个消费者负责一个分区的消费
Kafka Producer 是如何动态感知主题分区数变化的?
Kafka 是如何实现高吞吐率的?
Kafka是分布式消息系统,需要处理海量的消息,Kafka的设计是把所有的消息都写入速度低容量大的硬盘,以此来换取更强的存储能力,但实际上,使用硬盘并没有带来过多的性能损失。kafka主要使用了以下几个方式实现了超高的吞吐率:
顺序读写;
零拷贝(这是kafka收发数据时用的一种操作系统上的底层机制)
文件分段
批量发送
数据压缩。
Kafka 监控都有哪些?
比较流行的监控工具有:
KafkaOffsetMonitor
KafkaManager
Kafka Web Console
Kafka Eagle
如何为Kafka集群选择合适的Topics/Partitions数量
谈谈你对 Kafka 事务的了解?
谈谈你对 Kafka 幂等的了解?
Kafka 缺点?
由于是批量发送,数据并非真正的实时;
对于mqtt协议不支持;
不支持物联网传感数据直接接入;
仅支持统一分区内消息有序,无法实现全局消息有序;
监控不完善,需要安装插件;
依赖zookeeper进行元数据管理;
Kafka 新旧消费者的区别
旧的 Kafka 消费者 API 主要包括:SimpleConsumer(简单消费者) 和 ZookeeperConsumerConnectir(高级消费者)。SimpleConsumer 名字看起来是简单消费者,但是其实用起来很不简单,可以使用它从特定的分区和偏移量开始读取消息。高级消费者和现在新的消费者有点像,有消费者群组,有分区再均衡,不过它使用 ZK 来管理消费者群组,并不具备偏移量和再均衡的可操控性。
现在的消费者同时支持以上两种行为,所以为啥还用旧消费者 API 呢?
Kafka 分区数可以增加或减少吗?为什么?
我们可以使用 bin/kafka-topics.sh 命令对 Kafka 增加 Kafka 的分区数据,但是 Kafka 不支持减少分区数。
Kafka 分区数据不支持减少是由很多原因的,比如减少的分区其数据放到哪里去?是删除,还是保留?删除的话,那么这些没消费的消息不就丢了。如果保留这些消息如何放到其他分区里面?追加到其他分区后面的话那么就破坏了 Kafka 单个分区的有序性。如果要保证删除分区数据插入到其他分区保证有序性,那么实现起来逻辑就会非常复杂。
关注微信公众号并加微信好友即可获取PDF文档
你要悄悄学会HBase,然后惊艳所有人(1.7万字建议收藏)