赞
踩
知识点:
消息队列:发布(pub)/订阅模式,点对点(P2P)模式
kafka:是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统
特性: 可以实时的处理大量数据以满足各种需求场景
Broker:Kafka节点,一个Kafka节点就是一个broker,多个broker可以组成一个Kafka集群
Topic:一类消息,消息存放的目录即主题,例如page view日志、click日志等都可以以topic的形式存在,Kafka集群能够同时负责多个topic的分发
Partition:topic物理上的分组,一个topic可以分为多个partition,每个partition是一个有序的队列,可以削峰,负载均衡等。
Consumer Group:一个Consumer Group包含多个consumer
leaders / followers 以及 同一个消费者组不能同时消费同一个分区的数据。
所有处理都由leader处理,followers只做备份用。
ACK 应答机制:0 1 all ,0 发送不需要管kafka是否接收到数据,1 确保leader接收到数据,all确保leader和所有followers都备份
...............................
黑马程序员Kafka视频教程,大数据企业级消息队列kafka入门到精通_哔哩哔哩_bilibili
消息队列(Message Queue:MQ) 一种用来存储消息的队列,先进先出
特点:
1 消息队列用于存放消息的组件
2 程序员可以将消息放入队列中,也可以从消息队列中获取消息
3 很多时候消息队列不是一个永久性的存储,是作为临时存储存在的(设置一个期限)
4 消息队列中间件,eg:Kafka,Active MQ,RabbitMQ等
消息队列中间件就是用来存储消息的软件(组件),举个例子:为了分析网站的用户行为,我们需要记录用户的访问日志。这些一条条的日志,可以看作一条条的消息,我们可以把它们存到消息队列中,将来有一些应用程序需要处理这些日志,可以随时将这些消息取出来处理
用于保存信息的同时,还需要发送邮件,短信等,需要额外等待一段时间,此时可以用消息队列来进行异步处理,从而实现快速响应
源系一个微服务是通过接口(HTTP)调用另一个微服务,这时候耦合性很严重,只要接口发生变化就会导致系统不可用
使用消息队列可以将系统进行解耦合,现在第一个微服务可以将消息放入到消息队列中,另一个微服务可以从消息队列中把消息取出来进行处理,进行系统解耦合
因为消息队列是低延迟,高可靠,高吞吐,所以可以应对大量并发
可以使用消息队列作为临时存储,或者一种同学管道
生产者生产消息,放到消息队列中,消费者从消息队列中拉取消息
消息队列的两种模式:
发送者发送消息到消息队列中,然后消息接收者从消息队列中取出并消费消息。消息被消费以后,消息队列中不再有存储,所以消息接收者不可能消费到已经被消费的消息
1.1 每个消息只有一个接收者(consumer): 即一旦被消费,消息就不再消息队列中
1.2 发送者和接收者没有依赖性,发送者发送消息之后,不管有没有接收者在运行,都不会影响到发送者下次发送消息
1.3 接收者在成功接收消息之后需向队列应答成功,以便消息队列删除当前接收的消息
2.1 每个消息可以有多个订阅者
2.2 发布者和订阅者之间有时间上的依赖。 针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布的消息
2.3 为了消费消息,订阅者需要提前订阅该角色主题,并保持在线运行
Kafka配置文件server.properties详解_kafka server.properties-CSDN博客
主要的几个
- #这个表示当前broker在集群中的一个唯一标识,如果集群有3个broker,则分别为0,1,2
- broker.id=0
-
- #kafka存放数据的地方,如果要存放到多个地址的话用逗号分割 /tmp/kafka-log,/tmp/kafka-log2
- log.dirs=/tmp/kafka-logs
-
- #对外端口号
- # port=
- # sock的监听
- listeners=PLAINTEXT://192.168.52.32:9092
-
- #zookeeper集群的地址,可以是多个,多个之间用逗号分割
- hostname1:port1,hostname2:port2,hostname3:port3
- zookeeper.connect=localhost:2181
-
- #是否允许自动创建topic,如果是false,需要通过命令创建topic
- auto.create.topics.enable=true
-
- #一个topic,默认分区的replication(副本)个数,不能大于集群中broker的个数
- default.replication.factor=1
-
- #每个topic的分区个数
- num.partitions=1
-
- #是否开启日志压缩
- log.cleaner.enable=false
-
- #日志的清理策略 有delete和compact,主要针对过期数据的处理
- log.cleanup.policy=delete
-
查看kafka里的数据
bin下的脚本说明
- 1、kafka-acls.sh #配置,查看kafka集群鉴权信息
- 2、kafka-configs.sh #查看,修改kafka配置
- 3、kafka-console-consumer.sh #消费命令
- 4、kafka-console-producer.sh #生产命令
- 5、kafka-consumer-groups.sh #查看消费者组,重置消费位点等
- 6、kafka-consumer-perf-test.sh #kafka自带消费性能测试命令
- 7、kafka-mirror-maker.sh #kafka集群间同步命令
- 8、kafka-preferred-replica-election.sh #重新选举topic分区leader
- 9、kafka-producer-perf-test.sh #kafka自带生产性能测试命令
- 10、kafka-reassign-partitions.sh #kafka数据重平衡命令
- 11、kafka-run-class.sh #kafka执行脚本
- 12、kafka-server-start.sh #进程启动
- 13、kafka-server-stop.sh #进程停止
- 14、kafka-topics.sh #查询topic状态,新建,删除,扩容
-
- 常用: kafka-console-consumer.sh,kafka-console-producer.sh,kafka-topics.sh,kafka-server-start.sh,kafka-server-stop.sh
测试kafka的吞吐量,传输速度等
SpringBoot——集成Kafka详解_springboot kafka-CSDN博客
配置信息可是视情况增加或减少
Producer 采用推(push)模式将消息发布到broker,每条消息都被追加(append)到分区(partition)中,属于顺序写磁盘(顺序写磁盘效率比随机写内存要高,保障kafka吞吐率)
生产者参数配置:
生产者的参数配置
参数 | 描述 |
acks | acks 参数指定了必须要有多少个分区副本收到消息,生产者才会认为消费写入是成功的。acks = 0 生产者在成功写入消息之前不会等待任何来自服务器的响应。(缺点:无法确认消费是否成功;优点:高吞吐量);acks = 1 只要集群的首领节点收到消息,生产者就会收到一个来自服务器的成功响应。如果消费无法到达首领节点(比如首领节点奔溃,新的首领还没有被选举处理),生产者会收到一个错误响应,为了避免数据丢失,生产者会重发消息。不过,如果一个没有收到消息的节点成为新首领,消息还是会丢失。acks = all 只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。 |
buffer.memory | 该参数用来设置生产者缓冲区的大小,生产者用它缓冲要发送到服务器的消息。0.9.0.0 版本被替换成了 max.block.ms,表示在抛出异常之前可以阻塞一段时间 |
compression.type | 默认情况下为none,消费发送时不会被压缩。该参数可以设置为snappy、gzip或lz4,它指定了消息被发送给broker之前使用哪一种压缩算法进行压缩。1. snappy 压缩算法有Google发明,它占用较少的CPU,却能提供较好的性能和相当可观的压缩比(比较关注性能和网路带宽) 2. gzip 压缩算法一般会占用较多的CPU,但会提供更高的压缩比(网络带宽有限次采用) |
retries | 生产者从服务器收到的错误有可能是临时性的错误(比如分区找不到首领)。在这中情况下,retries参数是值决定了生产者可以重发消息的次数,如果达到这个次数,生产者会放弃重试并返回错误。默认情况下,生产者会在每次重试之间等待100ms,可以通过retry.backoff.ms参数来改变这个时间间隔. |
batch.size | 当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该采纳数指定了一个批次可以使用的内存大小,按照字节数计算(而不是消息个数)。1. 批次设置很大 不会造成延迟,只会占用更多的内存 2. 批次设置很小 因为生产者需要更频繁地发送消息,会增加一些额外的开销 |
linger.ms | 该参数指定了生产者在发送批次之前等待更多消息加入批次的时间。 |
client.id | 该参数可以是任意的字符串,服务器会用它来识别消息的来源,还可以用在日志和配额指标里 |
request.timeout.ms | 指定了生产者在发送数据时等待服务器返回响应的时间 |
max.block.ms | 该参数指定了在调用send()方法或使用partitionsFor()方法获取元数据时生产者的阻塞时间。当生产者的发送缓冲区已满,或者没有可用的元数据时,这些方法就会阻塞。在阻塞时间达到max.block.ms时,生产者会抛出超时异常 |
max.request.size | 该参数用于控制生产者发送的请求大小。它可以指能发送的单个消息的最大值,可以指单个请求里所有消息总的大小。 |
消息接收者称为Consumer
高级API 优点
高级API 缺点
低级API优点
低级API缺点
已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理
消息发送时都被发送到一个topic,其本质就是一个目录,而topic是由一些Partition Logs(分区日志)组成
已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理。)
副本因子操作的单位是以分区为单位,每个分区都有各自的主副本和从副本,主副本叫做leader,从副本叫做follower,处于同步状态的副本叫做in-sync。
导致副本同步失败的原因:
持续的副本:持续请求得到最新消息副本被称为同步的副本。在leader发生失效时,只有同步副本才有可能被选为新leader
Kafka的复制机制和分区的多副本架构是Kafka可靠性保证的核心。把消息写入多个副本可以是Kafka在发送崩溃时仍能保证消息的持久性。
控制器其实就是一个broker。集群里第一个启动的broker通过在Zookeeper里创建一个临时节点 /controller 让自己成为控制器。其他broker在启动时也会尝试创建这个节点,不过它们会收到一个"节点已存在"的异常,然后"意识"到控制器节点已存在,也就是说集群里已经有一个控制器了。其他broker在控制器节点上创建Zookeeper watch对象,这样它们就可以收到这个节点的变更通知。这种方式可以确保集群里一次只有一个控制器存在。
如果控制器被关闭或者Zookeeper断开连接,Zookeeper上的临时节点就会消失。集群里的其他broker通过watch对象得到控制器节点消失的通知,它们会尝试让自己成为新的控制器。第一个在Zookeeper里成功成功创建控制器节点的broker就会成为新的控制器,其他节点会收到"节点已存在"的异常,然后在新的控制器节点上再次创建watch对象。每个新选出的控制器通过Zookeeper的条件递增操作获得一个全新的、数值更大的controller epoch。其他broker在知道当前controller epoch后,如果收到有控制器发出的包含旧epoch的消息,就会忽略它们。
当控制器发现一个broker已经离开集群(通过观察相关的Zookeeper路径),它就知道,那些失去首领的分区需要一个新首领(这些分区的首领刚好在这个broker上)。控制器遍历这些分区,并确定谁应该成为新首领(简单来说就是分区副本列表里的下一个副本),然后向所有包含新首领或现有跟随者的broker发送请求。该请求消息包含了谁是新首领已经谁是分区跟随者的信息。随后新首领开始处理来着生产者和消费者的请求,而跟随者开始从新首领那里复制消息。
当控制器发现一个broker加入集群时,它会使用broker ID来检查新加入的broker是否包含现有的分区副本。如果有,控制器就把变更通知发送给新加入的broker和其他broker,新broker上的副本开始从首领那里复制消息。
Kafka 使用Zookeeper来维护集群成员的信息。每个broker都有一个唯一标识符,这个标识符可以在配置文件里指定,也可以自动生成。在broker启动的时候,它通过创建临时节点把自己的ID注册到Zookeeper。
Kafka组件监听Zookeeper的 /brokers/ids 路径(broker在Zookeeper上的注册路径),当有broker加入集群或退出集群时,这些组件就可以获得通知。在broker停机、出现网络分区或长时间垃圾回收停顿时,broker会从Zookeeper上断开连接,此时broker在启动时创建的临时节点会自动从Zookeeper上移除。监听broker列表的Kafka组件会被告知该broker已移除。在关闭broker时,它对应的节点也会消失,不过它的ID会继续存在于其他数据结构中
offset记录着下一条将要发送给consumer的消息的序号
默认kafka将offset存储在zk里
在一个分区中,消息是有顺序的方式存储着,每个在分区的消费都是一个递增的id,这个就是偏移量offset
偏移量在分区中才是有意义的,在分区之间,offset是没有任何意义的
1 某个主题,1个分区,2个消费者(A,B)在同一个消费者组,生产者持续有序写入1,2,3,4,5..., 其中一个消费者会获取消息:1 ,2,3,4,5..., 另一个消费者得不到消息。 因此一个消费者消费一个分区的数据。
2 某个主题,2个分区,2个消费者(A,B)在同一个消费者组,生产者持续有序写入1,2,3,4,5...,消费者A持续读取到:1,3,5,7,9...,另一个消费者B持续读取到:2,4,6,8...
因此,对于kafka而言,同一个消费者组的可以看作一个整理,消费一个topic,如果某个主题有多个消费者组,则每个组都能拿到1,2,3,4,5...用于不同的事情消费。而同一个消费者组内的消费者共同消费一份数据,只拿到一份1,2,3,4,5...
所谓幂等性,数学概念就是: f(f(x)) = f(x) 。f函数表示对消息的处理。
比如,银行转账,如果失败,需要重试。不管重试多少次,都要保证最终结果⼀定是⼀致的。
保证在消息重发的时候,消费者不会重复处理。即使在消费者收到重复消息的时候,重复处理,也要保证最终结果的⼀致性。
添加唯⼀ID,类似于数据库的主键,⽤于唯⼀标记⼀个消息。
Kafka为了实现幂等性,它在底层设计架构中引⼊了ProducerID SequenceNumber。
ProducerID:在每个新的Producer初始化时,会被分配⼀个唯⼀的ProducerID,这个ProducerID对客户端使用者是不可见的。
SequenceNumber:对于每个ProducerID,Producer发送数据的每个Topic和Partition都对应⼀个从0开始单调递增的SequenceNumber值。
幂等性的详细说明:Kafka的幂等性 - 简书
若没有指定 Partition 和 Key,就使用轮询策略
若没有指定 Partition,但是指定了 key,就按照 key 的 hash 值选择 Partition
Kafka中的消息都是被发布到某个Topic的。一个Topic可以配置多个Partition,Partition是Kafka进行并行操作的基本单位。
对于同一个Partition来说,其中消息必须严格按照生成顺序进行 Append。这保证了每个Partition内部的消息递增有序。但是跨Partition的消息则不保证有序。不同Partition分布在不同的Broker上,彼此之间没有顺序保证。
消费消息时,每个Partition只能被同一个Consumer Group中的一个Consumer消费,这样保证了每个Partition内消息的消费顺序。但不同Partition的消息可能被同一个Group的不同Consumer并行消费,因此无法保证顺序。
如果要跨Partition保证顺序,需要在消费端进行排序,维护消息偏移等元数据,以将不同Partition的消息重新组合有序。
此外,消费者还可以采用分区分配策略,使每个实例只消费一个或几个Partition,来简化排序问题。
所以Kafka本身只能保证每个Partition内的有序性,跨Partition需要消费端协作进行排序。这些机制共同支持消息序列的有序处理。
Topic配置Partition数为1,这样全局只有一个Partition,数据默认有序。但这制约了Kafka的 scalability。
Producer端对消息打上全局唯一的序号ID,或者使用Kafka自带的分区器按序号对消息Partition。消费时可以按序号排序。
Consumer端采用只订阅单个Partition的方式消费数据,而不是跨Partition订阅,这保证了一个Partition顺序。Consumer端自己维护偏移及排序,跨Partition订阅后MERGE排序。可在DB中存 last offset。
将相关联的消息发送到同一个Partition,不同类型消息分配到不同Partition。减小分区数可减少排序问题。采用可以重置偏移的Kafka消费者模式,以读取历史有序数据。采用Spark Streaming等支持有序接收Kafka数据并处理的框架。
设置为单节点单分区,并开启log compaction。新消息会覆盖key相同的老消息,确保单调递增顺序。调整max.message.bytes以控制批次大小,间接控制顺序粒度
Kafka中的Rebanlance称之为 重平衡 或者 再均衡,是kafka中确保消费者组下所有的消费者如何达成一致,分配订阅的主题的每个分区的机制
1 消费者组中的消费者的个数发生变化
2 订阅的主题的个数发生改变
3 订阅的主题的分区数发生变化
1 发生Rebalance时,消费者组下的所有消费者都会协调在一起共同参与,kafka使用分配策略尽可能达到最公平的分配
2 Rebanlance过程会对消费者组产生非常严重的影响,Rebalance的过程中所有的消费者都将停止工作,直到Rebalance完成
参考文档: 【Kafka】kafka 重平衡(Rebalance)_kafka rebalance-CSDN博客
Range范围分配策略是Kafka默认的分配策略,它可以确保每个消费者消费的分区数量是均衡的
- # 配置消费者的partition.assignment.strategy为org.apache.kafka.clients.consumer.RangeAssignor
-
- props.put("partition.assignment.strategy", "org.apache.kafka.clients.consumer.RangeAssignor");
算法公式:
n = 分区数量 / 消费者数量
m = 分区数量 % 消费者数量
前m个消费者消费n+1个,剩余消费者消费n个
RoundRobinAssignor 轮询策略是将消费组内所有消费者以及消费者所订阅的 所有topic的partition 按照字典序排序(topic和分区的hashcode进行排序),然后通过轮询方式逐个将分区以此分配给每个消费者
- #配置消费者的partition.assignment.strategy为org.apache.kafka.clients.consumer.RoundRobinAssignor:
-
- props.put("partition.assignment.strategy", "org.apache.kafka.clients.consumer.RoundRobinAssignor");
从Kafka 0.11.x开始,引入此类分配策略。主要目的:分区分配尽可能均匀
在发生 rebalance 的时候,分区的分配尽可能与上一次分配保持相同。没有发生rebalance 时,Striky粘性分配策略和 RoundRobin 分配策略类似
副本的目的就是冗余备份,当某个Broker上的分区数据丢失时,依然可以保障数据可用。因为在其他的Broker上的副本是可用的
- // 配置ACKs参数,all或者-1是确保消息写入到leader分区、还确保消息写入到对应副本都成功后,接着发送下一条,性能是最差的
- props.put("acks", "all");
-
- // 配置ACKs参数1, 生产者会等到leader分区写入成功后,返回成功,接着发送下一条,性能中等
- props.put("acks", "1");
-
- // acks = 0:生产者只管写入,不管是否写入成功,可能会数据丢失。性能是最好的
- props.put("acks", "0");
优点:
不需要执行管理offset, 直接通过zk管理, 也不需要管理分区、副本, 由kafka统一管理;
消费者会自动根据上一次在zk中保存的offset去接着获取数据;
在zk中, 不同的消费者组, 同一个topic记录不同的offset, 这样不同程序读取同一个topic, 不会受到offset的影响;
缺点:
通过使用低级API, 可以自己来控制offset, 想从哪读, 就从哪读; 而且, 可以自己控制连接分区, 对分区自定义负载均衡;
之前offset是自动保存在ZK中, 使用低级API, 可以将offset不一定使用zk存储, 可以自己来存储offset, 例如: 存储文件、Mysql、内存等;
但是低级API比较复杂, 需要执行控制offset连接到那个分区, 并找到分区的leader;
安装:kakfa从入门到放弃(四): 分区和副本机制、高级与低级API、 kafka-eagle、原理、数据清理、限速-CSDN博客
在kafka中,每个broker可以以配置多个分区以及多个副本,每个分区都有一个leader以及0个或者多个follower,在创建topic时,kafka会将每个分区的leader均匀地分配在每个broker上。我们正常使用kafka是感觉不到leader的日志数据文件,如果leader出现故障时,follower就会被选举成为leader。
1 kafka 中的leader负责处理读写操作,而follower只负责副本数据的同步
2 如果leader出现故障,其他follower会被重新选举成为leader
3 follower像一个consumer一样,拉取leader对于分区的数据,并保存到日志数据文件中
注意,和zk的区分
zk的leader负责读写,follower可以读取数据
kafka的leader负责读写,但follower不能读写数据,只负责同步。
kafka的一个topic有多个分区leader,一样可以实现负载均衡
AR ( Assigned Replicas ):分区中的所有副本统称为 AR
ISR(On-Sync Replicas):所有与 leader 副本保持一定程度同步的副本(包括 leader 副本在内〕组成 ISR
OSR (Out-of-Sync Replicas ):与 leader 副本同步滞后过多的副本(不包 leader 副本)组成
AR = ISR + OSR,默认情况下,当leader副本发生故障时,只有ISR集合中的副本才有资格被选举为新的leader
HW:HW是 High Watermark 的缩写,俗称高水位,它标识了一个特定的消息偏移量( offset ),消费者只能拉取到这个 offset 之前的消息。
LEO:LEO是 Log End Offset 缩写,它标识当前日志文件中下一条待写入消息 offset,如上图的日志中已经写了0~8号数据,那么offset为9的位置即为当前日志文件的LEO
参考文档: kafka中AR、ISR、OSR以及HW、LEO的区别_kafka leo-CSDN博客
ISR(In-Sync Replicas,同步副本) 的伸缩性是指ISR的能力适应负载变化和副本增减的能力。ISR的伸缩性对于Kafka集群的性能和可靠性非常重要。
当Kafka集群的负载增加或减少时,ISR的伸缩性可以通过以下方式来实现:
副本增加:当负载增加时,可以通过增加follower副本来提高ISR的伸缩性。新的follower副本将追赶上leader副本的同步进度,并加入ISR,从而分担负载。
副本减少:当负载减少时,可以通过减少follower副本来提高ISR的伸缩性。不再需要的follower副本将被移出ISR,并停止与leader副本的同步,从而减轻负载。
通过动态调整ISR的大小,Kafka可以根据负载的变化来自动平衡数据分布和副本同步,以提供更好的性能和可靠性。ISR的伸缩性是Kafka集群能够处理大规模数据流的关键因素之一。
参考文档:Kafka的ISR收缩机制_replica.lag.time.max.ms-CSDN博客
【项目实战】Kafka 分区中的AR、ISR、OSR_kafka isr osr-CSDN博客
kafak启动时, 会在所有的broker中选择一个controller, controller是高可用的;
leader和follower是针对partition副本的, 而controller是针对broker的;
创建topic, 添加分区, 修改副本数据量之类的任务管理都是由controller完成的;
kafka分区副本keader的选举, 也是有controller决定的;
在kafka集群启动的时候, 每个broker都会尝试去Zookeeper上注册称为Controller(zk临时节点);
但只有一个竞争成功, 其他的broker会注册该节点的监视器;
一个节点状态发生变化, 就可以进行相应的处理;
controller也是高可用的, 一旦某个broler崩溃, 其他的broker会重新注册为controller;
所有partition的leader选举都由controller决定的
controller会将leader的改变直接通过RPC的方式通知需要为此做出响应的broker
controller读取到当前分区ISR, 只要有一个Replica还幸存,就选择这个作为leader, 否则, 则任意选择一个replica作为leader
如果该partition的所有replica都已经宕机, 则新的leader为-1
为什么不能通过ZK的方式选举partition的leader?
如果业务很多, kafka集群会有很多partiton;
假设某个broker宕机, 就会出现很多个partition都需要重新选举leader;
如果使用zookeeper选举leader, 会给zookeeper带来巨大的压力; 所以, kafka中leader的选举不能使用zk实现;
kafka中引入[preferred-replica]的概念, 即: 优先的replic
在ISR列表中, 第一个replica就是preferred-replica
使用一下脚本可以将preferred-replica设置为leader, 均匀分配每个分区副本的leader
./kafka-leader-election.sh --bootstrap-server node1:9092 --topic 主题 --partition=1 --election-type preferred
如果某个broker crash之后, 就可能会导致副本的leader分布不均匀, 就是一个broker上存一个topic下不同partition的leader副本
参考文档:Kafka事务「原理剖析」-CSDN博客
参考: kakfa从入门到放弃(四): 分区和副本机制、高级与低级API、 kafka-eagle、原理、数据清理、限速-CSDN博客
数据丢失: 消费者获取offset,拉取数据,但存储过程失败,又提交了offset,拉取新的数据,这条数据便丢失了
数据重复:消费者获取offse,拉取数据,存储成功,但提交offset提交zk失败,拉取数据仍然是之前的offset,造成数据重复
只消费一次:低级 API ,可以从MYSQL中读取offset, 如果offset写入mysql中失败,可以使用Mysql事务,将写入到Mysql的数据和offset放在一个Mysql事务里,要么全部成功,要么全部失败
参考文档:
kafka面试题:kafka中controller的作用_Kafka最新面试题-CSDN博客
最后,感谢所有大佬做出的资料整理和分享
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。