15.kafka知识点

一、消息队列的简介

1.1 为什么要有消息队列

峰值处理能力(消峰能力):消息系统可顶住峰值流量,业务系统可根据处理能力从消息系统中获取并处理对应量的请求
解耦	:各系统之间通过消息系统这个统一的接口交换数据,无须了解彼此的存在
冗余	:部分消息系统具有消息持久化能力,可规避消息处理前丢失的风险
扩展	:消息系统是统一的数据接口,各系统可独立扩展
可恢复性:系统中部分键失效并不会影响整个系统,它恢复会仍然可从消息系统中获取并处理数据
异步通信:在不需要立即处理请求的场景下,可以将请求放入消息系统,合适的时候再处理

1.2 消息队列是什么

- 消息(Message)
	网络中的两台计算机或者两个通讯设备之间传递的数据。例如说:文本、音乐、视频等内容。
      
- 队列 (Queue)
	一种特殊的线性表(数据元素首尾相接),特殊之处在于只允许在首部删除元素和在尾部追加元素(FIFO)。入队、出队。

- 消息队列(MQ)
	消息+队列,保存消息的队列。消息的传输过程中的容器;主要提供生产、消费接口供外部调用做数据的存储和获取。

1.3 消息队列的分类

MQ主要分为两类:点对点(p2p)、发布订阅(Pub/Sub)

1)Peer-to-Peer

1. 一般基于Pull或者Polling接收数据
2. 发送到队列中的消息被一个而且仅仅一个接收者所接受,即使有多个接收者在同一个队列中侦听同一消息。
3. 支持异步“即发即收”的消息传递方式,也支持同步请求/应答传送方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RY8nqi5i-1615727785629)(ClassNotes.assets/1568684012083.png)]

2)发布订阅

1. 发布到同一个主题的消息,可被多个订阅者所接收
2. 发布/订阅即可基于Push消费数据,也可基于Pull或者Polling消费数据
3. 解耦能力比P2P模型更强

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-07BPohwi-1615727785631)(ClassNotes.assets/1568684181173.png)]

3) p2p和发布订阅MQ的比较

  • 共同点:

    消息生产者生产消息发送到queue中,然后消息消费者从queue中读取并且消费消息。
    
  • 不同点:

    p2p模型
    	包括:消息队列(Queue)、发送者(Sender)、接收者(Receiver)
        一个生产者生产的消息只有一个消费者(Consumer)(即一旦被消费,消息就不在消息队列中)。比如说打电话。
    pub/Sub
    	包含:消息队列(Queue)、主题(Topic)、发布者(Publisher)、订阅者(Subscriber)
        每个消息可以有多个消费者,彼此互不影响。比如我发布一个微博:关注我的人都能够看到。
    

1.4 常用的消息队列框架

  • RabbitMQ

    Erlang编写,支持多协议AMQP,XMPP,SMTP,STOMP。支持负载均衡、数据持久化。同时支持Peer-to-Peer和发布/订阅模式。
    
  • Redis

    基于Key-Value对的NoSQL数据库,同时支持MQ功能,可做轻量级队列服务使用。就入队操作而言,Redis对短消息(小于10kb)的性能比RabbitMQ好,长消息性能比RabbitMQ差。
    
  • ZeroMQ

    轻量级,不需要单独的消息服务器或中间件,应用程序本身扮演该角色,Peer-to-Peer。它实质上是一个库,需要开发人员自己组合多种技术,使用复杂度高。
    
  • ActiveMQ

    JMS实现,Peer-to-Peer,支持持久化、XA(分布式)事务
    
  • Kafka/Jafka

     高性能跨语言的分布式发布/订阅消息系统,数据持久化,全分布式,同时支持在线和离线处理
    
  • MetaQ/RocketMQ

    纯Java实现,发布/订阅消息系统,支持本地事务和XA分布式事务
    

二、KAFKA的简介

2.1 KAFKA是什么

1. 是一个分布式的流处理平台
2. 是apache旗下的顶级开源项目
3. 是用scala语言写的(scala语言开发的另外一款比较出名的软件是spark)
4. 有四大特征:
	- 高吞吐量:     每秒可以处理数百万级的消息的生产和消费
	- 持久化:本身有一套完善的存储机制,可以做消息的持久化
	- 分布式:消息可以存储在分布式集群上,消息有副本冗余,某一台宕机,生产者和消费者转而使用其它机器上的消体
	- 健壮性: 稳定,功能强大

2.2 kafka的设计目标和核心概念

2.2.1 设计目标

1. 高吞吐量
2. 持久化
3. 分布式
4. 在线处理和离线处理

2.2.2 核心概念

一、Kafka的组成部分:
	- Topic:主题,Kafka处理的消息的不同分类。
	- Broker:消息服务器代理,也就是kafka集群的一个节点,主要存储消息数据。
			存在硬盘中。每个topic都是有分区的。
	- Partition:Topic物理上的分组,一个topic在broker中被分为1个或者多个partition,
		分区在创建topic的时候指定。
	- Message:消息,是通信的基本单位,每个消息都属于一个partition

二、Kafka服务相关
    - Producer:消息和数据的生产者,向Kafka的一个topic发布消息。
    - Consumer:消息和数据的消费者,定于topic并处理其发布的消息。
    - Zookeeper:协调kafka的正常运行。		

三、KAFKA的安装部署

配置步骤:

1)上传,解压,更名,配置环境变量

[root@mei01 ~]# tar -zxvf kafka_2.11-1.1.1.tgz -C /usr/local/
[root@mei01 ~]# cd /usr/local/
[root@mei01 local]# mv kafka_2.11-1.1.1/ kafka
[root@mei01 local]# vim /etc/profile
...省略....
# spark  environment
export KAFKA_HOME=/usr/local/kafka
export PATH=$KAFKA_HOME/bin:$PATH
[root@mei01 kafka]# source /etc/profile

2)修改配置文件

[root@mei01 local]# vim kafka/config/server.properties
-->找到以下key进行修改。

#kafka的每一个服务节点的唯一表示,用整数来表示,不能出现重复
broker.id=10

#设置一个主题topic的分区数量
num.partitions=3

#配置kafka的消息存储的本地路径
log.dirs=/usr/local/kafka/data/kafka-logs

#配置kafka要使用zookeeper服务
zookeeper.connect=mei01:2181,mei02:2181,mei03:2181/kafka-2020

3)分发到其他节点,并修改broker.id

[root@mei01 local]# scp -r kafka/ mei03:/usr/local/
[root@mei01 local]# scp -r kafka/ mei03:/usr/local/

[root@mei01 local]# scp /etc/profile  mei02:/etc/
[root@mei01 local]# scp /etc/profile  mei03:/etc/

千万别忘记修改broker.id

4)启动kafka

1. 先启动zookeeper
	注意:你的是三台机器,应该都启动
2. 再启动三台机器上的kafka的服务

[root@mei01 ~]# kafka-server-start.sh -daemon /usr/local/kafka/config/server.properties
[root@mei02 ~]# kafka-server-start.sh -daemon /usr/local/kafka/config/server.properties
[root@mei03 ~]# kafka-server-start.sh -daemon /usr/local/kafka/config/server.properties

3. 查看服务项:jps

注意:如果出错了,去${KAFKA_HOME}/logs/目录下查看相关文件:server.log

四、KAFKA在zookeeper中的目录

1. 在zookeeper中的位置由server.properties里的zookeeper.connect决定。

注意:如果不在mei01:2181,mei02:2181,mei03:2181这个串后面维护一个根znode,那么kafka生成的所有znode都直接在/下。显得很乱,所以建议维护一个根znode.

2. 重要的znode说明:
/kafka-2020
    /cluster		
    	/id  {"version":"1","id":"Pks8sWZUT6GBJHqyVGQ5OA"}  
    	#代表的是一个kafka集群包含集群的版本,和集群的id
    
    /controller  {"version":1,"brokerid":11,"timestamp":"1564976668049"} 
    	#controller是kafka中非常重要的一个角色,意为控制器,控制partition的leader选举,topic的
    	#crud操作。brokerid意为由其id对应的broker承担controller的角色。
    /controller_epoch 2 
    	#代表的是controller的纪元,换句话说是代表controller的更迭,每当controller的brokerid更换
    	#一次,controller_epoch就+1.
    /brokers
       /ids	 [11, 12, 13] 					
       #存放当前kafka的broker实例列表
       
       /topics	[hadoop, __consumer_offsets] 
       #当前kafka中的topic列表
       
       /seqid	#系统的序列id
    /consumers 
    	#老版本用于存储kafka消费者的信息,主要保存对应的offset,新版本中基本不用,此时用户的消费信
    	#息,保存在一个系统的topic中:__consumer_offsets
    /config	
    	#存放配置信息

五、KAFKA的基本操作

5.1 topic的CRUD

5.1.1) 说明

kafka的消息都是按照不同的主题进行分开存储的。

5.1.2)主题的创建

参数说明

--alter                                修改主题的分区,副本,配置信息
--create                                 Create a new topic.
--delete                                 Delete a topic
--describe                               List details for the given topics.
--list                                   List all available topics.
--partitions <Integer: # of partitions>  指定分区数量,在修改时消息的分区和顺序会受到影响
--replication-factor <Integer:           每一个分区的副本因子
  replication factor>                      
--topic <String: topic>                 增删改查时指定的主题名称
--zookeeper <String: hosts>             所需要的zookeeper服务器列表

练习1:

]# kafka-topics.sh  \
--zookeeper mei01:2181,mei02:2181,mei03:2181/kafka-2020  \
--create \
--topic pet \
--partitions 3 \
--replication-factor 3 

练习2:创建时的副本数不能大于broker的数量

]# kafka-topics.sh  \
--zookeeper mei01:2181,mei02:2181,mei03:2181/kafka-2020  \
--create \
--topic pig \
--partitions 3 \
--replication-factor 4

Error while executing topic command : Replication factor: 4 larger than available brokers: 3.

5.1.3 ) 指定主题的查看

[root@mei01 ~]# kafka-topics.sh  --zookeeper mei01:2181,mei02:2181,mei03:2181/kafka-2020  --describe --topic pet

Topic:pet       PartitionCount:3        		ReplicationFactor:3     Configs:
Topic: pet      Partition: 0    Leader: 11      Replicas: 11,12,10      Isr: 11,12,10
Topic: pet      Partition: 1    Leader: 12      Replicas: 12,10,11      Isr: 12,10,11
Topic: pet      Partition: 2    Leader: 10      Replicas: 10,11,12      Isr: 10,11,12

#解析
Partition: 一个主题的分区号
Leader:指的是一个分区的所有副本中的领导者
Replicas:一个分区的所有的副本的位置信息,位于哪些broker上(与创建时的副本因子数量相同)
Isr: 一个分区现有存活的副本的位置信息。

5.1.4)列出所有的主题

[root@mei01 ~]# kafka-topics.sh  --zookeeper mei01:2181,mei02:2181,mei03:2181/kafka-2020  --list

5.1.5)修改主题

[root@mei01 ~]# kafka-topics.sh  \
--zookeeper mei01:2181,mei02:2181,mei03:2181/kafka-2020 \
--alter \
--topic pet \
--partitions 4 \
注意:
- 分区数量只能增大,不能减少
- 不能修改副本因子

5.1.6)删除主题

[root@mei01 ~]# kafka-topics.sh  \
--zookeeper mei01:2181,mei02:2181,mei03:2181/kafka-2020 \
--delete \
--topic pet

5.2 生产和消费消息

1) 启动生产者

[root@mei01 ~]# kafka-console-producer.sh --broker-list  mei01:9092,mei02:9092,mei03:9092 \
--topic pet

查看kafka-console-producer.sh有哪些参数,直接在命令行输入此脚本,回车即可

2) 启动消费者

案例1:默认情况下,消费者获取的是启动后生产者生产的信息,

[root@mei03 ~]# kafka-console-consumer.sh --bootstrap-server mei01:9092,mei02:9092,mei03:9092 --topic pet

案例2: 如果想要获取启动之前的所有消息,需要加参数–from-beginning。

[root@mei03 ~]# kafka-console-consumer.sh --bootstrap-server mei01:9092,mei02:9092,mei03:9092 --topic pet --from-beginning

5.3 消费信息的offset

offset:
1. 是kafka的topic中的一个partition中的每一条消息的标识,如何区分该条消息在kafka对应的partition的位置,就是用该偏移量。
2. offset的数据类型是Long,8个字节长度。
3. offset在分区内是有序的,分区间是不一定有序。
4. 如果想要kafka中的数据全局有序,就只能让partition个数为1。

参考下图:

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DYlqdsoV-1615727785633)(…/…/day01/%25E6%2596%2587%25E6%25A1%25A3/Kafka/assets/log_anatomy.png)]

案例演示1:指定分区,获取所有的数据

[root@mei03 ~]# kafka-console-consumer.sh --bootstrap-server mei01:9092,mei02:9092,mei03:9092 --topic pet --offset earliest

offset有三种值:

earliest:从分区的第一条消息开始获取
latest:获取启动后,最近生产者产生的信息。
none: 指的是什么都不做,空闲

案例2:指定一个非负数的自然数作为偏移量开始消费消息

root@mei01 ~]# kafka-console-consumer.sh --bootstrap-server mei01:9092,mei02:9092,mei03:9092 --topic pet --partition 0 --offset 0

5.4 消费信息的无序说明

1. 分区间的消息是无序的,分区内的数据是有序的。
2. 可以使用--from-beginning 来显示效率。
   获取所有分区的全部消息时,不一定是先获取哪一个分区的数据。

5.5 消费者组与Partition(重要)

1. 消费者是可以分组的。
2. 生产者的信息可以被多个组的消费者同时消费,组与组之前不受影响
3. 同一组内的消费者,一个消费者只能消费一个分区的数据,前提是分区数等于组内消费者数量
   消费者的数量<分区的数量:尽可能的均分,有一个负载均衡的机制。
   比如有6个分区,4个消费者:有两个消费者各处理一个,另外两个消费者各处理2个
   比如有4个分区,2个消费者:各处理两个  分区号0,1,2,3    一个处理0和2,一个处理1,3.
   比如有3个分区,2个消费者:一个消费者处理1个分区,另一个处理两个分区
   	    
   消费者的数量>分区数量: 一个消费者处理一个分区的数据,剩下的消费者闲置。

案例1:设置消费者所属组

kafka-console-consumer.sh --bootstrap-server mei01:9092,mei02:9092,mei03:9092 --topic pet --group g1

在组内,kafka的topic的partition个数,代表了kafka的topic的并行度,同一时间最多可以有多个线程来消费topic的数据,所以如果要想提高kafka的topic的消费能力,应该增大partition的个数。

六、kafka的编程api

maven依赖

<!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients -->
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- 下面的依赖,包含了上面的kafka-clients,所以只需要引入下面即可 -->
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka_2.11</artifactId>
    <version>1.1.1</version>
</dependency>

6.1 kafka生产者的api操作

6.1.1 简单编程

package com.qf.kafka.day01

import java.util.Properties
import org.apache.kafka.clients.producer.{KafkaProducer, ProducerRecord}
/**
 * 练习kafka的生产者的API操作
 * 1. 要创建一个生产者对象
 * 2. 要指定主题,获取消息对象(记录对象)
 * 3. 发送数据
 */
object _01ProducerTest {
    def main(args: Array[String]): Unit = {

        //设置配置信息
        val prop = new Properties()
       /* prop.setProperty("bootstrap.servers",
       "mei01:9092,mei02:9092,mei03:9092")
        prop.setProperty("acks","0")
        prop.setProperty("key.serializer",
        "org.apache.kafka.common.serialization.IntegerSerializer")
        prop.setProperty("value.serializer",
        "org.apache.kafka.common.serialization.StringSerializer")*/

        // 不使用上面的配置,加载配置文件里的属性信息,这样更方便维护
        prop.load(_01ProducerTest.getClass
                  .getClassLoader.getResourceAsStream("producer.properties"))

        //获取kafka的生产者对象
        val producer = new KafkaProducer[Integer,String](prop)

        //获取一个记录对象
        val message = new ProducerRecord[Integer, String]("pet","nishizuibangde")

        //调用send方法向集群上的指定主题发送数据
        producer.send(message)

        //释放资源
        producer.close()
    }
}

6.1.2 send方法的解析

1. 是一个异步发送数据的方法,当数据到达缓冲区立即返回。

6.1.3 幂等机制

1. 为了避免生产者在重试时出现重复的数据保存到消息队列中,引入了幂等性。

2. 什么时候会出现重复发送:
   当生产者没有收到broker的确认信息时,会认为发送失败,然后再次发送。就会出现重复发送的数据。
   
3. 什么是幂等性:
   多次发送的数据,相当于一次发送的数据。去掉重复性。保证Exactly-Once(仅一次) 
   
  Kafka为了实现幂等性,它在底层设计架构中引入了ProducerID和SequenceNumber。
  那这两个概念的用途是什么呢?

	- ProducerID:在每个新的Producer初始化时,会被分配一个唯一的ProducerID,
	这个ProducerID对客户端使用者是不可见的。
	- SequenceNumber:对于每个ProducerID,Producer发送数据的每个Topic和Partition
	都对应一个从0开始单调递增的SequenceNumber值。
	
	重新尝试发送时,SequenceNumber是不递增的。

4. 如何使用幂等性?
   非常简单:只需要在producer.properties里
   设置如下属性即可
   enable.idempotence=true
   
   注意:
   	- 重试次数要大于0
    - 还要使用acks机制

6.1.4 事务机制

使用事务机制来保证数据的安全性和一致性时
- 1:需要开启幂等性
- 2:要保证你的副本数至少大于等于3
- 3:同时要求在写入一条数据过程中,必须要有一半以上副本写入成功才行

6.2 kafka消费者的api操作

案例1:

package com.qf.kafka.day02;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

public class _01ConsumerTest {
    public static void main(String[] args) throws IOException {
        //创建配置对象
        Properties properties = new Properties();
        //读取配置文件
        InputStream is = _01ConsumerTest.class.getClassLoader().getResourceAsStream("consumer.properties");
        //将配置属性加载到配置对象上
        properties.load(is);

        //创建消费者
        KafkaConsumer<Integer, String> c = new KafkaConsumer<Integer, String>(properties);
        //消费者订阅主题
        List<String> topics = new ArrayList<String>();
        topics.add("pet");
        c.subscribe(topics);
		
       
        while (true){
            //拉取消息
       		 ConsumerRecords<Integer, String> messages = c.poll(1000);
            //遍历消息
            Iterator<ConsumerRecord<Integer, String>> it = messages.iterator();
            while(it.hasNext()){
                //获取当前消息
                ConsumerRecord<Integer, String> next = it.next();
                //打印
                System.out.println(next.topic()+"\t"+next.partition()+"\t"+next.offset()+"\t"+next.key()+"\t"+next.value());
            }
        }

    }
}

案例2:

package com.qf.kafka.day02;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

/**
 * 消费者指定分区中的偏移量来消费消息
 */
public class _02ConsumerTest {
    public static void main(String[] args) throws IOException {
        //创建配置对象
        Properties properties = new Properties();
        //读取配置文件
        InputStream is = _02ConsumerTest.class.getClassLoader().getResourceAsStream("consumer.properties");
        //将配置属性加载到配置对象上
        properties.load(is);

        //创建消费者
        KafkaConsumer<Integer, String> c = new KafkaConsumer<Integer, String>(properties);
        //消费者订阅主题,指定分区
        List<TopicPartition> list = new ArrayList<>();
        TopicPartition p1 = new TopicPartition("pet",0);
        TopicPartition p2 = new TopicPartition("pet",1);
        list.add(p1);
        list.add(p2);
        c.assign(list);
        //定位偏移量
        c.seek(p1,10);
        c.seek(p2,20);

        while (true){
            //拉取消息
            ConsumerRecords<Integer, String> messages = c.poll(1000);
            //遍历消息
            Iterator<ConsumerRecord<Integer, String>> it = messages.iterator();
            while(it.hasNext()){
                //获取当前消息
                ConsumerRecord<Integer, String> next = it.next();
                //打印
                System.out.println(next.topic()+"\t"+next.partition()+"\t"+next.offset()+"\t"+next.key()+"\t"+next.value());
            }
        }
    }
}

6.3 分区策略

6.3.1 默认的分区策略

/**
 * The default partitioning strategy:
 * <ul>
 * <li>If a partition is specified in the record, use it
 * <li>If no partition is specified but a key is present choose a partition based on a hash of the key
 * <li>If no partition or key is present choose a partition in a round-robin fashion
 */
public class DefaultPartitioner implements Partitioner {


public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
    List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
    int numPartitions = partitions.size();
    if (keyBytes == null) {
        int nextValue = nextValue(topic);
        List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
        if (availablePartitions.size() > 0) {
            int part = Utils.toPositive(nextValue) % availablePartitions.size();
            return availablePartitions.get(part).partition();
        } else {
            // no partitions are available, give a non-available partition
            return Utils.toPositive(nextValue) % numPartitions;
        }
    } else {
        // hash the keyBytes to choose a partition
        return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
    }
}
1、如果指定了partition,那么直接进入该partition

2、如果没有指定partition,但是指定了key,使用key的hash选择partition

3、如果既没有指定partition,也没有指定key,使用轮询的方式进入partition

6.3.2 自定义随机分区器

package com.qf.kafka.day02;

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;

import java.util.Map;

/**
 * 自定义随机分区器
 * 1. 实现Partitioner接口
 * 2. 重写partition方法
 */
public class RandParitioner implements Partitioner {
    /**
     * 计算当前消息的分区号
     * @param topic
     * @param key
     * @param keyBytes
     * @param value
     * @param valueBytes
     * @param cluster
     * @return
     */
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        //先获取分区的数量
        Integer count = cluster.partitionCountForTopic(topic);
        //随机一个分区号,应该是一个0~count-1的自然数
        int number = (int) (Math.random() * count);
        return number;
    }

    @Override
    public void close() {

    }

    @Override
    public void configure(Map<String, ?> configs) {

    }
}

测试:

package com.qf.kafka.day02;

import com.qf.kafka.day01._02SendMethodParse;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.concurrent.Future;

public class _TestRandPartioner {
    public static void main(String[] args) throws IOException {
        InputStream is = _02SendMethodParse.class.getClassLoader().getResourceAsStream("producer.properties");
        //获取配置对象
        Properties properties = new Properties();
        //加载流对象里的配置信息
        properties.load(is);
        properties.setProperty("partitioner.class","com.qf.kafka.day02.RandParitioner");

        //获取kafka的生产者对象
        KafkaProducer producer = new KafkaProducer<Integer,String>(properties);

        Future<RecordMetadata> send = null;
        ProducerRecord message = null;
        for(int i = 0;i<10;i++){
            //获取一个记录对象
            message = new ProducerRecord<Integer, String>("pet",15,""+i);
            send = producer.send(message);

        }
        //释放资源
        producer.close();
    }
}

6.3.3 自定义hash分区器

package com.qf.kafka.day02;

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;

import java.util.Map;

public class HashPartitioner implements Partitioner {
    /**
     * 使用key的hash值来计算消息的分区号
     * @param topic
     * @param key
     * @param keyBytes
     * @param value
     * @param valueBytes
     * @param cluster
     * @return
     */
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        //获取分区数量
        Integer count = cluster.partitionCountForTopic(topic);
        //获取key的hash执行取模运算
        int number = key.toString().hashCode() % count;
        return number;
    }

    @Override
    public void close() {

    }

    @Override
    public void configure(Map<String, ?> configs) {

    }
}

测试:

package com.qf.kafka.day02;

import com.qf.kafka.day01._02SendMethodParse;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.concurrent.Future;

/**
 * 测试自定义的hash分区器
 * 将写好的hash分区器 设置到配置文件producer.properties中的属性partitioner.class上
 */
public class _TestHashPartioner {
    public static void main(String[] args) throws IOException {
        InputStream is = _02SendMethodParse.class.getClassLoader().getResourceAsStream("producer.properties");
        //获取配置对象
        Properties properties = new Properties();
        //加载流对象里的配置信息
        properties.load(is);

        //获取kafka的生产者对象
        KafkaProducer producer = new KafkaProducer<Integer,String>(properties);

        Future<RecordMetadata> send = null;
        ProducerRecord message = null;
        for(int i = 0;i<10;i++){
            //获取一个记录对象
            message = new ProducerRecord<Integer, String>("pet",i,"hello"+i);
            send = producer.send(message);

        }
        //释放资源
        producer.close();
    }
}

6.3.4 自定义轮询分区器

package com.qf.kafka.day02;

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;

import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 自定义一个轮询分区器
 */
public class RoundPartitioner implements Partitioner {
    //获取一个全局的计数器
    private AtomicInteger atomic = new AtomicInteger();

    //计算当前消息的分区号
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        //获取主题的分区数量
        Integer count = cluster.partitionCountForTopic(topic);
        //获取计数器上的自增值
        int andIncrement = atomic.getAndIncrement();
        //对分区数量取模运算,就可以得到 0,1,2,0,1,2.....轮询的效果
        int i = andIncrement % count;
        return i;
    }

    @Override
    public void close() {

    }

    @Override
    public void configure(Map<String, ?> configs) {

    }
}

测试:

package com.qf.kafka.day02;

import com.qf.kafka.day01._02SendMethodParse;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.concurrent.Future;
/**
 * 测试自定义的轮询分区器
 * 将写好的轮询分区器 设置到配置文件producer.properties中的属性partitioner.class上
 */
public class _TestRoundPartioner {
    public static void main(String[] args) throws IOException {
        InputStream is = _02SendMethodParse.class.getClassLoader().getResourceAsStream("producer.properties");
        //获取配置对象
        Properties properties = new Properties();
        //加载流对象里的配置信息
        properties.load(is);
        //获取kafka的生产者对象
        KafkaProducer producer = new KafkaProducer<Integer,String>(properties);

        Future<RecordMetadata> send = null;
        ProducerRecord message = null;
        for(int i = 0;i<13;i++){
            //获取一个记录对象
            message = new ProducerRecord<Integer, String>("pet",15,""+i);
            send = producer.send(message);

        }
        //释放资源
        producer.close();
    }
}

七、Kafka与flume的整合

7.1 说明

flume是一个数据采集框架,可以采集行为数据,到hdfs或者是hbase或hive上,做离线分析。
flume还可以将数据采集到kafka上,做实时分析。

7.2 案例演示

7.2.1 案例1:

将flume采集到的数据落地到kafka上,因此sink源要是kafka的(是生产者身份)

[root@mei01 flumeconf]# vim syslog-mem-kafka.properties
#定义agent和三大核心组件的名称
a1.sources = r1
a1.channels = c1
a1.sinks = s1
#关联
a1.sources.r1.channels = c1
a1.sinks.s1.channel = c1

#定义source源
a1.sources.r1.type = syslogtcp
a1.sources.r1.host = mei01
a1.sources.r1.port = 10086

#定义channel源
a1.channels.c1.type = memory
a1.channels.c1.capacity= 1000
a1.channels.c1.transactionCapacity=100

#定义sink源
a1.sinks.s1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.s1.kafka.bootstrap.servers = mei01:9092,mei02:9092,mei03:9092
#主题必须提前存在
a1.sinks.s1.kafka.topic = pet
a1.sinks.s1.kafka.producer.acks = 1
#缓冲区flush到磁盘的时间阈值
a1.sinks.s1.kafka.producer.linger.ms = 1
#a1.sinks.k1.kafka.producer.compression.type = snappy

启动:

[root@mei01 flumeconf]# flume-ng agent -c ../conf -f syslog-mem-kafka.properties -n a1 -Dflume.root.logger=INFO,console  &

测试:

1. 先启动一个消费者,来准备消费消息
2. 测试数据
[root@mei01 flumeconf]# echo "aaaaa" | nc mei01 10086

7.2.2 案例2:

使用kafka的source源来消费kafka消息队列里的数据,落地到hdfs

说明:kafka的source源是从kafka集群上读取数据,也就是消费者的身份,将数据封装成flume的event。

[root@mei01 flumeconf]# vim kafka-mem-hdfs.properties

#定义agent和三大核心组件的名称
a1.sources = r1
a1.channels = c1
a1.sinks = s1
#关联
a1.sources.r1.channels = c1
a1.sinks.s1.channel = c1


#定义source源
a1.sources.r1.type = org.apache.flume.source.kafka.KafkaSource
a1.sources.r1.kafka.bootstrap.servers = mei01:9092,mei02:9092,mei03:9092
a1.sources.r1.kafka.consumer.group.id = g1
# 消费的主题,多个主题使用逗号分隔
a1.sources.r1.kafka.topics = pet


#定义channel源
a1.channels.c1.type = memory
a1.channels.c1.capacity= 1000
a1.channels.c1.transactionCapacity=100

#定义sink源
a1.sinks.s1.type = hdfs
a1.sinks.s1.hdfs.path = hdfs://mei01:8020/kafka/pet/%y%m%d/%h%M
#主题必须提前存在
a1.sinks.s1.hdfs.filePrefix = FlumeData
a1.sinks.s1.hdfs.fileSuffix = .kafka
a1.sinks.s1.hdfs.rollInterval=0
a1.sinks.s1.hdfs.rollSize = 0
# 满足10条一滚动文件,有第11条才会真的滚动
a1.sinks.s1.hdfs.rollCount = 10
a1.sinks.s1.hdfs.writeFormat=Text
a1.sinks.s1.hdfs.fileType=DataStream
a1.sinks.s1.hdfs.useLocalTimeStamp =true 

启动

[root@mei01 flumeconf]# flume-ng agent -c ../conf -f kafka-mem-hdfs.properties -n a1 -Dflume.root.logger=INFO,console  &

测试:

1. 开启一个生产者,使用生产者发送消息
方式1:使用api
方式2:使用shell

八、Kafka的架构

8.1 Kafka的组织架构模型

8.1.1 Kafka的相关术语

一、从集群模型上说:
   broker: kafka集群中的每一个存储节点,用来在磁盘上持久化(几天,几小时)存储消息数据的。可以保证消息数据的安全性(重用性:消费者可以重新消费消息)
   zookeeper:维护一个集群的broker的controller角色以及宕机后的重写选举,还有其他元数据,比如broker的动态上下线,主题及其分区信息,分区副本的leader角色
   producer:生产者,用于生产消息的
   consumer:消费者,用于消费消息的
二、从消息上说:
   topic:生产者生产的消息是按照主题分类的。
   partiton:生产者为每一个主题生产的消息又被划分为不同的分区。(提高消息消费的并行度)
   replication:每一个分区的数据都有N个副本。提高消息数据的安全性,可靠性
   leader:N个副本中的老大,负责写和读数据,做一个应答机制。
   follower:同步leader写的数据
   isr:现存活的副本
   segment: 指的是分区内的数据按照段来存储,(方便根据offset查找消息,也方便删除旧数据)
   offset: 每个分区里的消息的偏移量。在目录下是以offset.log来命名的

8.1.2 Kafka的集群架构

通常,一个典型的Kafka集群中包含
1. 若干Producer(可以是web前端产生的Page View,或者是服务器日志,系统CPU、Memory等),
2. 若干broker(Kafka支持水平扩展,一般broker数量越多,集群吞吐率越高),
3. 若干Consumer Group,
4. 以及一个Zookeeper集群。Kafka通过Zookeeper管理集群配置,选举leader,以及在Consumer Group发生变化时进行rebalance。
5. Producer使用push模式将消息发布到broker,
6. Consumer使用pull模式从broker订阅并消费消息(消费者能消费多少信息应该由自己说的算,而不是被动的,应该是主动的)

8.1.3 Kafka的分布式模型

1. 一个主题的消息是分不同分区的。
2. 每一个分区的数据有多个副本,分别存在不同的机器上,来保证数据的安全性,提高容错性。
3. 这些副本中的某一个作为leader,进行数据的读和写操作,其他的作为follower来同步leader的数据
4. 如果leader挂掉了,follower中某一个会成为leader,来继续工作。体现了kafka的去中心化。
5. 消费者的并行度是由分区数来决定的。

8.2 Kafka的文件存储机制

8.2.1 文件结构

1. 在kafka中,主题topic是一个逻辑概念
2. 每一个主题的所有消息是按照partition进行物理存储的。
3. partition就是主题的一个分区,对应的是磁盘上的目录,目录名是topicName-序列,序列从0开始
4. 分区内的数据是分segment进行存储的。
5. segment对应两个文件,分别是xxxxxxxx.index和xxxxxxxxx.log文件。
	- .index文件存储的是key-value对,并且是稀疏存储。key是.log文件中的第几条消息的这个数字,value是这个消息的针对于这个文件的偏移量。
	- .log文件存储的是真正的消息,  大小阈值,默认是1G。
	- xxxxxxxxx指的是上一个segment的最后的消息索引

8.2.2 分区

1)为什么要分区

kafka可以将一个主题的消息来分目录进行管理。   本质上分区就是分目录

2)分区的好处

1. 方便在集群中扩展,一个分区可以存在不同的节点上,进行分布式存储
2. 多个生产者可以向同一个主题的不同的分区同时写数据,做到并发
3. 消费者组内的成员可以同时消费不同分区里的数据

注意:同一个分区的副本不可能在同一台broker上

3)分区策略

假设现在有3个broker,4个分区。 不考虑多副本的情况。

算法:将第i个Partition分配到第(i mode n)个broker上。因此分布如下:

Topic:test3     PartitionCount:4        ReplicationFactor:2     Configs:
Topic: test3    Partition: 0    Leader: 10       Replicas:10   Isr:10
Topic: test3    Partition: 1    Leader: 11       Replicas:11   Isr:11
Topic: test3    Partition: 2    Leader: 12       Replicas:12   Isr:12
Topic: test3    Partition: 3    Leader: 10       Replicas:10   Isr:10

4)副本分布策略

1. 一旦leader确定了,其他的副本一定不是在leader这台机器上,而是尽可能的分散到其他节点上。
2. 目的就是负载均衡。

5)消息分配策略(默认情况下,当然可以指定分区器)

1、如果指定了partition,那么直接进入该partition

2、如果没有指定partition,但是指定了key,使用key的hash选择partition

3、如果既没有指定partition,也没有指定key,使用轮询的方式进入partition

8.3 分区的文件存储机制

1. 分区的本质是一个目录 
2. 分区内的文件是按照segment存储的,也就是说,一个分区有N个segment(段,抽象概念)。
3. segment大小是1G。
4. segment对应的文件是按照顺序读写的,因此速度特别快
5. 分段存储,方便删除旧段的文件(否则,只有一个文件时,还需要进行切分才能删除旧数据),提高磁盘利用率
6. segment对应三个文件:
	- xxxx.index
	- xxxx.log
	- xxxx.timeindex

8.4 segment的物理结构

1. 分区里的文件是按照segment进行物理存储的,但是segment是一个抽象概念
2. segment在物理上分三个文件存储,其中两个非常重要,一个是xxxx.index.一个是xxx.log
3. 文件名: offset.index     offset.log          offset指的是之前的所有段文件里的总条数的数字
4. offset.log的文件结构:
	offset: 0 
	position: 0 
	CreateTime: 1606877011740 
	isvalid: true 
	keysize: -1 		key的长度,如果没有key,是-1
	valuesize: 350 	    value的长度
	magic: 2 			服务协议版本号
	compresscodec: NONE 
	producerId: -1 
	producerEpoch: -1 
	sequence: -1 
	isTransactional: false 
	headerKeys: [] 
	payload: 			真正的value数据
5. 	offset.index的文件结构:有一堆键值对 offset-position
	offset: 0    指的是对应的log文件中的第几条消息          第一条就是0
	position: 0  指的是对应的log文件中的第几条消息的开始字节位置(针对于这个文件的偏移量)
	注意:offset.index文件是稀疏存储的文件格式。

8.4 Kafka的消息查找流程

举例说明:查找消息索引为1041的消息。

段文件分布如下:
00000000000000000000.index		00000000000000000000.log
00000000000000001034.index		00000000000000001034.log
00000000000000003067.index		00000000000000003067.log
00000000000000005148.index		00000000000000005148.log

索引文件:						log文件:
00000000000000001034.index		00000000000000001034.log
1034    0							offset:1034   postition:0    valuesize:101
1035    200						offset:1035   postition:200   valuesize:101
1036    400						offset:1036   postition:400   valuesize:101
1039    1000						offset:1037   postition:600   valuesize:101
1042    1600						offset:1038   postition:800   valuesize:101
                                offset:1039   postition:1000   valuesize:101
                                offset:1040   postition:1200   valuesize:101
                                offset:1041   postition:1400  valuesize:101
                                offset:1042   postition:1600  valuesize:101
                                offset:1043   postition:1800  valuesize:101
                                offset:1044   postition:2000  valuesize:101

步骤如下:

第一步:先判断要查找的消息索引在哪一个段。
	   使用索引和文件名上的索引比较,来确定在哪一个段里。
	   1041和1034以及3067比较,发现在1034里
第二步:在索引文件中查找偏移量:1041-1034 = 7,查看是否有此偏移量,如果没有,那就确定范围。也就是5对应的1000这个偏移量。
第三步:在对应的log文件中通过偏移量1000找到1039这个索引,继续往下找1041就可以了。

8.5 消费者组的概念

1. kafka因为多分区的概念,提出了消费者组的概念
2. 消费者组具有可扩展和容错机制
3. 消费者组有唯一标识 groupid
4. 组与组之间订阅的主题没有任何关系。
5. 三大特征:
	- 组内有多个消费者,消费者可以是一个进程,也可以是一个线程
	- 公用一个唯一组标识
	- 一个分区只能被组内的一个消费者消费

8.6 消费者对offset的维护

8.6.1 说明

消费者在消费数据时,发生了宕机之类的故障后,再重写启动时,消费的数据是否要重头开始,还是要从之前宕机的位置开始读取呢?

如果从头读取时,有一部分消息一定出现了重复消费。
如果从宕机时的消费位置开始读取,就不会出现重复消费。  所以kafka采取这种机制,就涉及到了offset的维护。

8.6.2 offset的维护

两种方式:

方式1:kafka的自动提交(enable.auto.commit = true)。 也叫at most once。最多提交一次。不会出现重复提交

方式2:消费者的手动提交(enable.auto.commit = false,需要调用consumer.commitSync()以及配合事务一起使用),也叫at least once。最少提交一次,会出现重复提交。


offset是如何维护记录的呢?
0.9.0版本以前,将这些offset存在zookeeper里,但是zookeeper不适合维护这样的大批量的数据。因此在0.9.0版本以后,kafka专门维护了一个主题__consumer_offsets。用来记录所有消费者对所有主题里的分区读取的offset即时位置。
内部结构包括:  groupid  topicName-partition  offset

8.7 push和pull的优缺点

1. push模式的目标是尽可能以最快速度传递消息,因此缺点是推送消息时很难适应消费者的消费速率以及消费信息量,容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。
2. pull模式的目标是能消费多少数据是有自己决定的,但是缺点是如果没有数据被拉取,则陷入循环中,会占用资源。

kafka的生产者采用了push模式将生产的消息push到broker上,也就可以避免直接与消费者直接交互。
kafka的消费者采用了pull模式拉取broker里的数据,同时kafka提供了一个timeout参数,来增加一次循环的等待时间,减少循环次数。

8.8 kafka的ack机制

Kafka为用户提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行权衡,选择以下的配置。

1. ack=0
	生产者生产消息发送到leader后,就继续发送其他的消息,不需要leader的ack。
	缺点:数据可能丢失,不会出现重复。
2. ack=1
	生产者生产消息发送到leader,leader会将消费落地到磁盘,然后就向生产者进行ack。
	缺点:数据可能丢失(leader落地了,并ack了,但是ack后,follower还为同步,leader挂了)
	     数据可能重复(leader落地了,但是ack失败了,生产者会再次重复发送消息)
3. ack=-1(all)
	生产者生产消息发送到leader,leader会将消费落地到磁盘,follower会同步leader里的数据,所有的follower同步成功后会向leader进行汇报,leader再向生产者进行ack.
	缺点:数据会重复(ack失败了,会重复发送此消息)

8.9 kafka的数据保障

8.9.1 说明

问题:生产者如何发送新的一条消息啊? kafka引入了ack机制。ack机制有三种策略。一个是0,一个是1,还有一个是-1(all).

0和1 不细说了,参考8.8

-1的情况如下:

正常情况下,副本数据同步策略有以下两种方案,进行比较

方案优点缺点
半数以上完成同步,就发送ack延迟低选举新的leader时,容忍n台节点的故障,需要2n+1个副本
全部完成同步,才发送ack选举新的leader时,容忍n台节点的故障,需要n+1个副本延迟高

Kafka针对于-1选择了第二种方案,原因如下:

1.同样为了容忍n台节点的故障,第一种方案需要2n+1个副本,而第二种方案只需要n+1个副本,而Kafka的每个分区都有大量的数据,第一种方案会造成大量数据的冗余。

2.虽然第二种方案的网络延迟会比较高,但网络延迟对Kafka的影响较小。

8.9.2 ISR(in-sync replica set)

设想一个问题:

-1 是所有的follower都要同步成功后,leader才会向生产者发送ack。但是可能某一个follower宕机了,那么生产者就迟迟不会收到leader的ack. 耽误发送下一条新的消息。

因此kafka提供了一个ISR机制,即ISR是一个已经同步数据的所有副本,当某一个follower迟迟没有向leader发送汇报时,就会将此follower踢出ISR。这样就可以避免leader迟迟不向生产者发送ack。


ISR中包含了所有的存活副本,包括leader和follower。
如果leader宕机了,会从ISR中的所有follower中选举出一个新的leader,继续工作。follower为了保证数据的一致性,会先将高于水位线的消息截取掉,然后从新的leader上重新同步数据。



如果follower宕机了,会将其踢出ISR。回复后,会将高于水位线以上的消息截取掉,重新从leader的水位线开始同步,当同步成功后,再加入到ISR中

8.10 ExActly Once语义

8.10.1 三种语义

1. at most once 	:最多一次   数据不可能重复,ack=0
2. at least once	:最少一次	数据可能重复   ack=-1|1
3. exactly once		:正好一次   数据不可能重复 ,需要配合ack=-1以及幂等机制
	也就是:idempotent + at least once = exactly once
	在producer的配置文件中添加enable.idempotence=true

8.10.2 幂等机制的简介

1. 为了避免生产者在重试时出现重复的数据保存到消息队列中,引入了幂等性。

2. 什么时候会出现重复发送:
   当生产者没有收到broker的确认信息时,会认为发送失败,然后再次发送。就会出现重复发送的数据。
   
3. 什么是幂等性:
   多次发送的数据,相当于一次发送的数据。去掉重复性。保证Exactly-Once(仅一次) 
   
  Kafka为了实现幂等性,它在底层设计架构中引入了ProducerID和SequenceNumber。
  那这两个概念的用途是什么呢?

	- ProducerID:在每个新的Producer初始化时,会被分配一个唯一的ProducerID,
	这个ProducerID对客户端使用者是不可见的。
	- SequenceNumber:对于每个ProducerID,Producer发送数据的每个Topic和Partition
	都对应一个从0开始单调递增的SequenceNumber值。
	
	重新尝试发送时,SequenceNumber是不递增的。broker会将收到的SequenceNumber和之前缓存过的SequenceNumber进行比较,如果相同,则认为是同一个,就不存储了。如果发现SequenceNumber大于缓存的SequenceNumber则认为是新的消息,就存储。
	
	

4. 如何使用幂等性?
   非常简单:只需要在producer.properties里
   设置如下属性即可
   enable.idempotence=true
   
   注意:
   	- 重试次数要大于0
    - 还要使用acks机制

8.11 zookeeper在kafka中的作用

1. zookeeper在kafka中启动一个框架协调作用,维护broker的controller角色的选举
  如果选举:
  启动后的所有broker都会向zookeeper注册一个临时节点/controller, 谁注册成功谁就是controller
  注册成功的那一个会启动一个KafkaController进程。
  KafkaController负责管理集群broker的上下线,所有topic的分区副本分配和leader选举等工作
2.Controller的管理工作都是依赖于Zookeeper的。  

Controller的管理工作都是依赖于Zookeeper的。

以下为partition和Controller的leader选举过程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpnaNiWr-1615727785636)(ClassNotes.assets/1577975440646.png)]

1. at most once 	:最多一次   数据不可能重复,ack=0
2. at least once	:最少一次	数据可能重复   ack=-1|1
3. exactly once		:正好一次   数据不可能重复 ,需要配合ack=-1以及幂等机制
	也就是:idempotent + at least once = exactly once
	在producer的配置文件中添加enable.idempotence=true

8.10.2 幂等机制的简介

1. 为了避免生产者在重试时出现重复的数据保存到消息队列中,引入了幂等性。

2. 什么时候会出现重复发送:
   当生产者没有收到broker的确认信息时,会认为发送失败,然后再次发送。就会出现重复发送的数据。
   
3. 什么是幂等性:
   多次发送的数据,相当于一次发送的数据。去掉重复性。保证Exactly-Once(仅一次) 
   
  Kafka为了实现幂等性,它在底层设计架构中引入了ProducerID和SequenceNumber。
  那这两个概念的用途是什么呢?

	- ProducerID:在每个新的Producer初始化时,会被分配一个唯一的ProducerID,
	这个ProducerID对客户端使用者是不可见的。
	- SequenceNumber:对于每个ProducerID,Producer发送数据的每个Topic和Partition
	都对应一个从0开始单调递增的SequenceNumber值。
	
	重新尝试发送时,SequenceNumber是不递增的。broker会将收到的SequenceNumber和之前缓存过的SequenceNumber进行比较,如果相同,则认为是同一个,就不存储了。如果发现SequenceNumber大于缓存的SequenceNumber则认为是新的消息,就存储。
	
	

4. 如何使用幂等性?
   非常简单:只需要在producer.properties里
   设置如下属性即可
   enable.idempotence=true
   
   注意:
   	- 重试次数要大于0
    - 还要使用acks机制

8.11 zookeeper在kafka中的作用

1. zookeeper在kafka中启动一个框架协调作用,维护broker的controller角色的选举
  如果选举:
  启动后的所有broker都会向zookeeper注册一个临时节点/controller, 谁注册成功谁就是controller
  注册成功的那一个会启动一个KafkaController进程。
  KafkaController负责管理集群broker的上下线,所有topic的分区副本分配和leader选举等工作
2.Controller的管理工作都是依赖于Zookeeper的。  

Controller的管理工作都是依赖于Zookeeper的。

以下为partition和Controller的leader选举过程:

[外链图片转存中…(img-hpnaNiWr-1615727785636)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值