kafka资料

Kafka

一 消息队列/消息中间件(Message Queue)

1 什么是消息中间件?为什么要用消息中间件?

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

2 消息队列

2.1 Message

网络中两台设备之间传递的数据都是消息。例如:文字、图片、声音、视频等等

2.2 Queue

一种特殊的线性表,特点是先进先出。入队、出队

2.3 MQ(Message + Queue)

保存消息的队列,消息的传输过程中以队列作为容器,主要提供了生产、消费接口提供外部调用。
MQ一般分为两大类:
p2p(点对点)
pub/sub(发布/订阅)
2.3.1 p2p

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3hTuiBpd-1615266939063)(002.png)]

2.3.2 pub/sub

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9QROw380-1615266939065)(003.png)]

2.3.3 常见MQ
- RabbitMQ,Erlang编写,支持负载均衡,同时支持p2p和pub/sub
- Redis,基于Key/Value的Nosql的数据库,还可以做轻量级的pub/sub。短消息低于10kb性能比RabbitMQ更好。
- ZeroMQ轻量级的消息中间级,不需要单独的消息服务器。支持p2p
- ActiveMQ,JMS实现,支持p2p,支持持久化
- Kafka/Jafka,高性能跨语言的分布式发布/订阅的消息中间件。数据持久化、全分布式、支持在线和离线的处理
- RocketMQ,纯Java实现的,支持发布/订阅,支持本地事务和分布式事务

二 Kafka介绍与安装

1 简介

领英(LinkedIn)公司发布的,scala语言编写的。

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

2 kafka的3大特点

- 高吞吐量
支持每秒上百万的消息的生产和消费

- 持久性
有一套非常完善的消息存储机制,确保数据的安全——中间存储

- 分布式
基于分布式的扩容和容错机制。kafka的数据会被复制到多台服务器上,当单台机器故障还有其他的机器的数据保证。

3 Kafka服务

- Topic : 主题,Kafka中的处理消息的分类
- Broker : 消息服务器的代理,Kafka中的一个kafka服务器节点就是一个broker
- Partition : Topic物理上的分区,一个topic在一个broker上被分为1个或多个partition。partition在创建topic的时候指定。
- Message : 消息,通信的基本单位,每个消息都属于一个分区。

4 Kafka安装

4.1 Kafka的伪分布式安装和全分布式安装

全分布式的安装:
1. 安装全分布式的zookeeper

伪分布式的安装
1. 直接使用kafka自带的zookeeper脚本

4.2 伪分布式安装

4.2.1 安装
[root@chancechance software]# tar -zxvf kafka_2.11-1.1.1.tgz -C /opt/apps/
[root@chancechance apps]# mv kafka_2.11-1.1.1/ kafka-1.1.1

[root@chancechance kafka-1.1.1]# vi /etc/profile
export KAFKA_HOME=/opt/apps/kafka-1.1.1
export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin:$KAFKA_HOME/bin
4.2.2 server.properties
[root@chancechance config]# vi server.properties

############################# Server Basics #############################

# The id of the broker. This must be set to a unique integer for each broker.
broker.id=0

############################# Log Basics #############################

# A comma separated list of directories under which to store log files
log.dirs=/opt/apps/kafka-1.1.1/data/kafka

############################# Zookeeper #############################

# Zookeeper connection string (see zookeeper docs for details).
# This is a comma separated host:port pairs, each corresponding to a zk
# server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002".
# You can also append an optional chroot string to the urls to specify the
# root directory for all kafka znodes.
zookeeper.connect=10.206.0.4:2181/kafka

tip:
如果是全分布式你需要将你的kafka发布给其他的服务器节点,然后需要修改broker.id
4.2.3 启动kafka
## 1. 先启动zookeeper
[root@chancechance bin]# zookeeper-server-start.sh -daemon $KAFKA_HOME/config/zookeeper.properties

## 2. 连接zk服务器
[root@chancechance bin]# zookeeper-shell.sh 10.206.0.4:2181

## 3. 启动kafka
[root@chancechance bin]# kafka-server-start.sh -daemon $KAFKA_HOME/config/server.properties

4.3 全分布式

1. 自己安装zookeeper
2. servier.propertis
broker.id=1  qphone01
broker.id=2  qphone02
broker.id=3  qphone03
3. 启动
3.1启动全分布式的zookeeper
3.2启动kafka

5 zookeeper中kafka的znode节点含义

/kafka
	/cluster/id --> 表示kafka集群中的版本和编号
	{"version":"1","id":"XeVX1Ac1Trqj4qkFunQMEA"}
	
	/controller --> 他是kafka中非常重要的角色,控制kafka中的leader的选举,控制topic的crud。brokerid表示对应的broker承担了controller的角色
	{"version":1,"brokerid":1,"timestamp":"1602226517468"}
	
	/controller_epoch --> 1表示controller的纪元,换句话来说表示的是controller的更迭。每当controller的broker更换一次,controller的版本就+1,同时纪元+1
	1
	
	/brokers
		/ids [1,2,3] ---> 在当前kafka中的broker的实力id列表
		/topics [qphone01, hadoop, __consumer_offsets] --> kafka的主题列表
		/seqid --> kafka系统的序列id
		
	/consumers --> 老版本中用于存储kafka消费者信息,主要用于保存offset。新版本这个目录就没有用了
	/config --> 存储配置信息

三 Kafka的基本操作

1 命令行操作

1.1 kafka的topic的操作——kafka-topics.sh

1.1.1 create topic
kafka-topics.sh \
--create \
--topic hadoop \ ## 创建的主题名称
--zookeeper 10.206.0.4:2181/kafka \ ## 指定kafka关联的zk的地址
--partitions 3 \ ## 指定topic的分区数
--replication-factor 1 ## 指定副本因子,副本因子的数量必须小于等于broker的个数 

Created topic "hadoop".
1.1.2 list topic
kafka-topics.sh \
--list \
--zookeeper 10.206.0.4:2181/kafka
1.1.3 describe topic
kafka-topics.sh \
--describe \
--topic hadoop \
--zookeeper 10.206.0.4:2181/kafka

Topic:hadoop    PartitionCount:3        ReplicationFactor:3     Configs:
        Topic: hadoop   Partition: 0    Leader: 2       Replicas: 2,3,1     Isr: 2,3,1
        Topic: hadoop   Partition: 1    Leader: 3       Replicas: 3,1,2     Isr: 3,1,2 
        Topic: hadoop   Partition: 2    Leader: 1       Replicas: 1,2,3     Isr: 1,2,3

##
#Partition : 当前主题对应的分区编号
#Replicas : 副本因子,当前kafka集群中对应的partition所在的broker的broker.id列表
#Leader :该Partition中对应的所有副本的leader,处理该partition的读写请求
#Isr :该Partition存活的副本对应的broker.id列表
1.1.4 alter topic
kafka-topics.sh \
--alter \
--topic hadoop \
--zookeeper 10.206.0.4:2181/kafka \
--partitions 4

WARNING: If partitions are increased for a topic that has a key, the partition logic or ordering of the messages will be affected
Adding partitions succeeded!

tip:
分区是只能增加不能减少的
1.1.5 删除主题
kafka-topics.sh \
--delete \
--topic hadoop \
--zookeeper 10.206.0.4:2181/kafka
1.1.6 生产数据——kafka-console-producer.sh
kafka-console-producer.sh \
--broker-list 10.206.0.4:9092 \
--topic hadoop
1.1.7 消费数据——kafka-console-consumer.sh
kafka-console-consumer.sh \
--topic hadoop \
--bootstrap-server 10.206.0.4:9092

2 Java API操作

2.1 Producer API

2.1.1 导入依赖
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>1.1.1</version>
</dependency>
2.1.2 代码
package cn.qphone.kafka.day2;

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 Demo1_Producer {
    public static void main(String[] args) {
        //1. 设置属性
        Properties props = new Properties();
        props.put("bootstrap.servers", "146.56.208.76:9092");
        props.put("acks", "all");
        props.put("retries", 0);
        props.put("batch.size", 16384);
        props.put("linger.ms", 1);
        props.put("buffer.memory", 33554432);
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        //2. 获取到生产者对象
        Producer<String, String> producer = new KafkaProducer<>(props);

        //3. 发送消息
        ProducerRecord<String, String> record = new ProducerRecord<>("hadoop", null, "你好");
        producer.send(record);

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

tip:
	如果你使用的是云服务器,你会发现你的公网ip访问的kafka的消息,在消费者中是接收不到的,根本原因就是他默认是通过内网ip监听的服务接口
解决方案:修改server.properties
advertised.listeners=PLAINTEXT://146.56.208.76:9092

2.2 Consumer API

package cn.qphone.kafka.day2;

import org.apache.kafka.clients.consumer.Consumer;
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 Demo2_Consumer {
    public static void main(String[] args) {
        //1. 准备数据
        Properties props = new Properties();
        props.put("bootstrap.servers", "146.56.208.76:9092");
        props.put("group.id", "hzbigdata2004"); // 设置消费者组
        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");

        //2. 创建消费者对象
        Consumer<String, String> consumer = new KafkaConsumer<>(props);

        consumer.subscribe(Arrays.asList("hadoop")); // 订阅消息
        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());
            }
        }
    }
}

2.3 优化代码

2.3.1 producer.properties
bootstrap.servers=146.56.208.76:9092
acks=all
retries=0
batch.size=16384
linger.ms=1
buffer.memory=33554432
key.serializer=org.apache.kafka.common.serialization.StringSerializer
value.serializer=org.apache.kafka.common.serialization.StringSerializer
2.3.2 Producer
package cn.qphone.kafka.day2;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.io.IOException;
import java.util.Properties;

public class Demo1_Producer {
    public static void main(String[] args) throws IOException {
        //1. 设置属性
        Properties props = new Properties();
        props.load(Demo1_Producer.class.getClassLoader().getResourceAsStream("producer.properties"));

        //2. 获取到生产者对象
        Producer<String, String> producer = new KafkaProducer<>(props);

        //3. 发送消息

        ProducerRecord<String, String> record = new ProducerRecord<String, String>("hadoop", "hello");
        producer.send(record);

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

2.3.3 consumer.properties
bootstrap.servers=146.56.208.76:9092
group.id=hzbigdata2004
enable.auto.commit=true
auto.commit.interval.ms=1000
key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
2.3.4 Consumer
package cn.qphone.kafka.day2;

import org.apache.kafka.clients.consumer.Consumer;
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.util.Arrays;
import java.util.Properties;

public class Demo2_Consumer {
    public static void main(String[] args) throws IOException {
        //1. 准备数据
        Properties props = new Properties();
        props.load(Demo1_Producer.class.getClassLoader().getResourceAsStream("consumer.properties"));

        //2. 创建消费者对象
        Consumer<String, String> consumer = new KafkaConsumer<>(props);

        consumer.subscribe(Arrays.asList("hadoop")); // 订阅消息
        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());
            }
        }
    }
}

2.4 Topic API——AdminClient

2.4.1 create topic
package cn.qphone.kafka.day2;

import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.NewTopic;

import java.util.Arrays;
import java.util.Properties;

public class Demo3_Topic {
    public static void main(String[] args) {
        //1. 创建主题
        /**
         * kafka-topics.sh \
         * --create \
         * --topic hadoop \ ## 创建的主题名称
         * --zookeeper 10.206.0.4:2181/kafka \ ## 指定kafka关联的zk的地址
         * --partitions 3 \ ## 指定topic的分区数
         * --replication-factor 1 ## 指定副本因子,副本因子的数量必须小于等于broker的个数
         */


        //0. 设置参数
        Properties properties = new Properties();
        properties.setProperty("bootstrap.servers", "146.56.208.76:9092");

        //1. 创建对象
        AdminClient adminClient = AdminClient.create(properties);

        //2. 创建主题
        adminClient.createTopics(Arrays.asList(new NewTopic("spark", 3, (short)1)));

        //3. 释放
        adminClient.close();

    }
}
2.4.2 list topic
package cn.qphone.kafka.day2;

import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.ListTopicsResult;
import org.apache.kafka.common.KafkaFuture;

import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutionException;

public class Demo4_ListTopics {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //0. 设置参数
        Properties properties = new Properties();
        properties.setProperty("bootstrap.servers", "146.56.208.76:9092");

        //1. 创建对象
        AdminClient adminClient = AdminClient.create(properties);

        //2. list
        ListTopicsResult listTopicsResult = adminClient.listTopics();

        //2.1 names:获取主题名称
        KafkaFuture<Set<String>> names = listTopicsResult.names();
        Set<String> names_set = names.get(); // 获取名称的字符串表现形式

        //2.2 遍历
        for (String name: names_set) {
            System.out.println(name);
        }

        //3.
        adminClient.close();
    }
}

2.5 相关参数说明

bootstrap.servers : 你所要连接到kafka的brokers
key.serializer : key的序列化器
value.serializer : value的序列化器
acks=[0|-1/all|1] : 消息确认机制
	0 : 不做确认,只管发送消息即可
	-1/all : 不仅需要leader将数据写入到本地磁盘,并确认。还需要将所有的消息都同步到副本的服务器中。
	1 : 只需要leader进行消息确认。后期副本所在的服务器自己从leader从同步
batch.size : 每个分区用户缓存未发送record空间大小
linger.ms : 无论缓冲区是否沾满,都会延迟xxx ms来发送请求
buffer.memory : 一个生产者对象的缓冲区大小
retries : 当消息发送失败之后重试的次数

——————————————————————————————————————————————————————————————————————

group.id : 消费者组,它可以决定对这个主题的消费的最大并行度,最优的情况是消费者组中的消费者的数量刚好等于分区数。
enable.auto.commit : 自动的消费数据
auto.commit.interval.ms : 自动消费的时间间隔,他们俩一起决定了你的消费的速度
key.deserializer : key的反序列化器
value.deserializer : value的反序列化器 

2.6 消费者组

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

3 自定义分区器

3.0 默认的分区策略

每一条ProducerRecord,这个对象中包含了:topic,partition-id以及key和value对偶
3种策略:
1. 如果你指定了分区,那么你的这条record就直接添加到指定partition-id中
2. 如果没有指定分区编号,但是你指定了key,使用key进行hash选择partition
3. 如果既没有指定分区编号,又没有指定key,默认使用轮询的方式选择partition

3.1 随机分区器

3.1.1 分区器——Demo5_RandomPartitioner
package cn.qphone.kafka.day2;

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

import java.util.Map;
import java.util.Random;

public class Demo5_RandomPartitioner implements Partitioner {

    private Random random = new Random();

    /**
     * 决定了分区策略
     * @param topic 主题
     * @param key 数据key
     * @param keyBytes 数据key的字节数组
     * @param value 数据value
     * @param valueBytes 数据value的字节数组
     * @param cluster 集群对象
     * @return
     */
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        //1. 获取分区数
        Integer count = cluster.partitionCountForTopic(topic);
        //2. 产生分区索引
        int partition = random.nextInt(count);
        return partition;
    }

    /**
     * 释放
     */
    @Override
    public void close() {

    }

    /**
     * 设置你的配置
     * @param configs
     */
    @Override
    public void configure(Map<String, ?> configs) {

    }
}
3.1.2 producer.properties
partitioner.class=cn.qphone.kafka.day2.Demo5_RandomPartitioner

四 flume整合kafka

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

1 安装flume

2 新建一个主题

kafka-topics.sh \
--create \
--topic flume-kafka \
--zookeeper 10.206.0.4:2181/kafka \
--partitions 3 \
--replication-factor 1

3 netcat_kafka.conf

a1.sources = r1
a1.sinks = k1
a1.channels = c1

a1.sources.r1.type = netcat
a1.sources.r1.bind = 10.206.0.4
a1.sources.r1.port = 6666

a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
a1.channels.c1.byteCapacityBufferPercentage = 20
a1.channels.c1.byteCapacity = 800000

a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.kafka.topic = flume-kafka
a1.sinks.k1.kafka.bootstrap.servers = 10.206.0.4:9092
a1.sinks.k1.kafka.flumeBatchSize = 20
a1.sinks.k1.kafka.producer.acks = 1
a1.sinks.k1.kafka.producer.linger.ms = 1
a1.sinks.k1.kafka.producer.compression.type = snappy

a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

4 测试

##1. 启动yarn:start-yarn.sh
##2. 启动zk:
		zookeeper-server-start.sh -daemon $KAFKA_HOME/config/zookeeper.properties
		zkServer.sh start
##3. 启动kafka
		kafka-server-start.sh -daemon $KAFKA_HOME/config/server.properties
##4. 启动flume
nohup flume-ng agent -n a1 -c /opt/apps/flume-1.9.0/conf -f /opt/apps/flume-1.9.0/conf/netcat_kafka.conf > /dev/null 2>&1 &

##5. 启动消费者
kafka-console-consumer.sh \
--topic flume-kafka \
--bootstrap-server 10.206.0.4:9092

##6. 启动netcat
yum -y install telnet
telnet 10.206.0.4 6666

五 Kafka的架构之道

1 kafka相关术语

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2zf80RST-1615266939070)(007.png)]

1.1 replicas

	每一个分区,根据副本因子N,会有N个副本。比如broker1有一个topic,这个主题分区为topic-1,副本因子为2,那么在两个broker的数据目录里就都有一个topic-1,其中一个是leader,一个是follower

1.2 segment

	Partition在物理上是由多个segment组成的。每个segment中存储了很多的message

1.3 Leader

	每个Partition有多个副本,其中有且仅有一个是作为Leader,其他都是Follower。Leader负责将数据读写到Partition。

1.4 Follower

	Follower跟随Leader的,所有的写请求都是通过Leader路由的,Leader将数据广播给所有的Follower,follower的数据是保持和leader同步的。如果leader失效,会从剩下的follower选举出一个新的leader。当follower挂掉了、卡住、同步慢,leader会把这个follower从“in sync follower(isr)”列表中移除,重新创建一个新的follower。

1.5 Offset

	kafka的存储文件都是按照offset.log来命名的,用offset命名日志最大的好处就是方便查找。例如想要查找位于2100的数据,只需要查找2000.log,  3000.log。

2 Kafka的分布式模型

	Kafka的分布式指的其实就是分区被分布在多台server(broker)上。同时每个分区leader和follower组成的副本(replicas)。其实大佬和马仔,大佬负责处理,而马仔负责同步。
	kafka的分区日志(message)分布在kafka的集群上的

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

3 kafka的文件存储

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

4 topic的partition

4.1 为什么要分区

  1. 提高并发
  2. 方便集群中扩展

4.2 partition数据在哪个位置

1. server.properties
log.dir=$KAFKA_HOME/data/kafka

2. 例如:为了test-1/test-2分了4个partition

|--test-1-0
|--test-1-1
|--test-1-2
|--test-1-3
|--test-2-0
|--test-2-1
|--test-2-2
|--test-2-3

4.3 多节点partition存储分布

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6MkeASVb-1615266939072)(010.png)]

4.4 分区分配策略

1. 有n个broker和partition进行排序
2. 将第i个partition分配到i mod / n个broker

4.5 测试多leader的情况:4分区+2个副本+3个broker

kafka-topics.sh \
--create \
--topic kafka-topics \ ## 创建的主题名称
--zookeeper 10.206.0.4:2181/kafka \ ## 指定kafka关联的zk的地址
--partitions 4 \ ## 指定topic的分区数
--replication-factor 2

kafka-topics.sh \
--describe \
--topic kafka-topics \
--zookeeper 10.206.0.4:2181/kafka

Topic:hadoop    PartitionCount:4        ReplicationFactor:2     Configs:
        Topic: kafka-topics   Partition: 0    Leader: 1       Replicas: 1,2     Isr: 1,2
        Topic: kafka-topics   Partition: 1    Leader: 2       Replicas: 2,3     Isr: 2,3 
        Topic: kafka-topics   Partition: 2    Leader: 3       Replicas: 3,1     Isr: 3,1
        Topic: kafka-topics   Partition: 3    Leader: 1       Replicas: 1,3    Isr: 1,3
        
        
第1个partition分配到第(1 % 3) = 1个broker
第2个partition分配到第(2 % 3) = 2个broker
第3个partition分配到第(3 % 3) = 3个broker
第3个partition分配到第(4 % 3) = 1个broker

5 partition中的文件存储

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

5.1 kafka分区的segment

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

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

5.2 message的消息结构

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

6 Consumer Group

参考前面的说明

7 offset的维护

	由于consumer在消费的时候有可能会出现类似于宕机等等情况,consumer回复之后不能重头开始消费,需要接着上次宕机之前的offset继续消费,所以consumer需要实时的记录消费到哪个offset位置了。
	kafka默认是定期的帮你自动的提交offset,你也可以选择手动的提交。另外kafka还会自动group消费情况保存起来,形成了一个offset的map,如下图:

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

0.9版本之前使用的是zookeeper来维护kafka的偏移量,这种方式叫做low level API
0.9版本之后使用的是broker来维护kafka的偏移量,这种方式叫做high level API

	自动提交enable.auto.commit=true,更新频率auto.commit.interval.ms属性决定的。这种方式也被称为at most once。当捕获到消息就立刻更新偏移量,无论消费是否成功。
	手动提交enable.auto.commit=false,这种方式成为at least once。当捕获到消息,等到消费完成之后在调用方式提交偏移量;如果消费失败呢,则偏移量不会更新,那么当我们重复消费成功之后才会更新偏移量。

8 kafka中push和pull

一个思考问题,当我们在消费数据的时候,是消费者从kafka中pull数据,还是 broker push数据到消费者?首先生产者一定是向broker中push消息,并由消费者从broker中pull数据。

9 Exactly Once(恰好一次)

对于一些比较重要的消息,保证Exactly Once语义。保证消息被发送且仅被发送一次。

producer.properties

enable.idempotent=true
acks=-1

10 Kafka的leader选举

10.1 zab协议(zookeeper atomic broadcast)

这个协议可保证集群中的所有的zookeepr的读写操作都能正常执行:
zab协议2种模式:
广播模式
回复模式

10.2 curator框架

10.2.1 导入依赖
<dependencies>
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-client</artifactId>
    </dependency>
</dependencies>
10.2.2 znode的crud
  • CuratorUtils
package cn.qphone.kafka.day3;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class CuratorUtils {

    private static CuratorFramework client;

    static {
        client = CuratorFrameworkFactory.builder()
                .connectString("146.56.208.76")
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
    }

    public static CuratorFramework getCuratorClient() {
        if(client.getState() == CuratorFrameworkState.STOPPED) { // 如果说你的client已经close了
            client = CuratorFrameworkFactory.builder()
                    .connectString("146.56.208.76")
                    .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                    .build();
        }
        return client;
    }

    public static void close(CuratorFramework client) {
        if (null != client) client.close();
    }
}
  • 创建节点
package cn.qphone.kafka.day3;

import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.CreateMode;

public class Demo1_Curator {
    public static void main(String[] args) throws Exception {
        //1. 获取到入口类
        CuratorFramework client = CuratorUtils.getCuratorClient();
        //2. 必须先启动
        client.start();

        //3. 创建一个节点
        client.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.PERSISTENT)
                .forPath("/curator/zookeeper6", "nice to meet you".getBytes());

        //4. 释放资源
       CuratorUtils.close(client);

       client = CuratorUtils.getCuratorClient();
       client.start();
       client.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.PERSISTENT)
                .forPath("/curator/zookeeper7", "nice to meet you".getBytes());

        CuratorUtils.close(client);
    }
}

  • 查询
public class Demo1_Curator {
    public static void main(String[] args) throws Exception {
        //1. 获取到入口类
        CuratorFramework client = CuratorUtils.getCuratorClient();
        //2. 必须先启动
        client.start();

        //3. 罗列所有节点
        List<String> paths = client.getChildren().forPath("/");
        for (String path : paths) {
            System.out.println(path);
        }

        //4. 释放资源
       CuratorUtils.close(client);
    }
}
  • 获取数据
package cn.qphone.kafka.day3;

import org.apache.curator.framework.CuratorFramework;

public class Demo1_Curator {
    public static void main(String[] args) throws Exception {
        //1. 获取到入口类
        CuratorFramework client = CuratorUtils.getCuratorClient();
        //2. 必须先启动
        client.start();

        //3. 获取数据
        byte[] data = client.getData().forPath("/curator/zookeeper");
        System.out.println(new String(data));

        //4. 释放资源
       CuratorUtils.close(client);
    }
}

  • 修改数据
public class Demo1_Curator {
    public static void main(String[] args) throws Exception {
        //1. 获取到入口类
        CuratorFramework client = CuratorUtils.getCuratorClient();
        //2. 必须先启动
        client.start();

        //3. 获取数据
        client.setData().forPath("/curator/zookeeper", "hello".getBytes());

        byte[] data = client.getData().forPath("/curator/zookeeper");
        System.out.println(new String(data));

        //4. 释放资源
       CuratorUtils.close(client);
    }
}
  • 删除
public class Demo1_Curator {
    public static void main(String[] args) throws Exception {
        //1. 获取到入口类
        CuratorFramework client = CuratorUtils.getCuratorClient();
        //2. 必须先启动
        client.start();

        //3.删除节点
        client.delete().forPath("/curator/zookeeper");

        //4. 释放资源
       CuratorUtils.close(client);
    }
}
10.2.3 模拟动态的监听服务器的上下线

zk如何监听服务器的上下线?

当服务器启动的时候会向目标的zk注册一个节点路径,让这个路径存在说明这个服务器在线,当这个路径不存在说明服务器下线了。我们节点这个路径存在与否。

监听这个节点是否存在,使用watch

package cn.qphone.kafka.day3;

import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;

import java.util.List;

public class Demo3_CuratorService_Discover implements Watcher {

    private String path = "/taobao";
    private CuratorFramework client;
    private List<String> children;

    public Demo3_CuratorService_Discover() {
        try {
            client = CuratorUtils.getCuratorClient();
            client.start();
            children = client.getChildren().usingWatcher(this).forPath(path);
            System.out.println("初始化节点信息:" + children);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 当zk产生节点相关的事件的时候都会调用,zk自己调用
     */
    @Override
    public void process(WatchedEvent event) {
        try {
            System.out.println("当前方法被调用---->");
            List<String> newChildren = client.getChildren().usingWatcher(this).forPath(path);
            if (newChildren.size() > children.size()) { // 新增,上线
                for (String child : newChildren) {
                    if (!children.contains(child)) {
                        System.out.println("新增节点:"+child);
                    }
                }
            }else { // 减少
                for (String child : children) {
                    if (!newChildren.contains(child)) {
                        System.out.println("被删除的节点:"+child);
                    }
                }
            }
            children = newChildren; // 将新的子节点的列表覆盖老的子节点列表
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Demo3_CuratorService_Discover().start();
    }

    public void start() {
        for (;;){}
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值