Kafka系统学习

1. 概念

1. Kafka是一个分布式的基于发布/订阅的消息队列,应用于大数据实时处理。
	1.1 消息队列就是为了让消费者跟生产者解耦,实现高效,但是安全性肯定降低,需要额外处理。
	1.2 大数据一般处理流程:日志收集者(flume) ---> Kafka ---> 消息消费者(spark、flink)。
	1.3 消息队列分两种:
		1.3.1 点对点(一对一)
		1.3.2 发布/订阅(一对多)

2. 架构

在这里插入图片描述

发布1
订阅1
发布2
订阅2
发布3
订阅3
Producer
TopicA-Partiyion0
group0
Consumer0
TopicA-Partiyion1
Consumer1
TopicA-Partiyion2
Consumer2
1. 每一个Topic分为多个partition
2. 每一个partition都有多个副本在不同机器
3. 消费者组内两个消费者只能消费同一个topic的不同partition分区的消息(一个分区只能被一个消费组内的一个消费者消费),组内消费者形成一个订阅者
4. 消费者组内消费者数量等于分区数时,平行度最好
5. zookeeper帮助Kafka存储机器broker信息
6. 0.9版本之前zk还存储着offset,之后版本将offset存储在了Kafka,减少Kafka于zk过多的通信

3. 安装部署

1. 依赖于zookeeper,首先安装zookeeper
2. 解压Kafka安装包
3. 配置环境变量
4. 配置Kafka属性
	4.1 server.property文件
	// kafka只有几台并行的机器没有主从结构:必须配置broker ID,集群时,分发后修改其他机器此值
	broker.id=0
	// kafka数据存储的目录
	log.dirs=/kafka/logs
	// 分区数
	num.partitions=1
	// 数据存储时间:一周以内数据不删除
	log.retention.hours=168
	// 消息的每个segment的大小
	log.retention.bytes=1073741824
	// log被删除的的情况检查时间 默认为5分钟
	log.retention.check.interval.ms=300000
	// 配置依赖的zookeeper
	zookeeper.connect=master:2181,slave1:2181,slave2:2181
5. 启动Kafka(预先启动zookeeper)
	kafka-sever-start.sh config/server.property &

4. 命令行操作

1. 查看所有topic
	kafka-topics.sh --list --zookeeper master:2181,slave1:2181
2. 新建topic 指定topic名 指定副本数 指定分区数
	kafka-topics.sh --create --zookeeper cdh-master:2181,cdh-slave1:2181  --topic qdd1 --replication-factor 2 --partitions 2
2.*** 修改分区数23
	kafka-topics.sh --alter --zookeeper master:2181,slave1:2181  --topic first --partitions 3
3. 查看/删除指定topic详细
	kafka-topics.sh --describe/delete --zookeeper master:2181,slave1:2181  --topic first
4. 控制台开启一个生产者
	kafka-console-producer.sh --broker-list master:9092,slave1:9092 --topic first
5. 控制台开启一个消费者  --from-beginning 从头开始消费
	kafka-console-consumer.sh --bootstrap-server master:9092,slave1:9092 --topic first

5. 高级释义

1. 数据文件存储说明
	1.1 .log文件存储真正的数据片段segement,数据满了1g后新建一个.log生成新的片段;
	1.2 .index文件存储index
2. 生产者分区策略
	2.1 分区原因: 方便扩展以及提高并发
	2.2 分区原则:
	2.2.1 指定分区发数据
	2.2.2 指定key,根据hash方式到分区
	2.2.3 什么也不指定,按照随机开始的方式,然后轮询发往各分区
3. 数据同步acks返回机制
	3.1 半数以上副本同步完成即返回ack acks=-1/all(ISR同步完成)
		3.1.1 延迟低,但是容灾差
	3.2 全部副本完成同步再返回ack(Kafka使用此种,因为网络延迟对Kafka影响小)
		3.2.1 延迟高,但是容灾好
	3.3 leader写完数据就返回ack acks=1
	3.4 不等待任何应答即返回ack 可能leader都未完成写操作 acks=0
4. ISR(in-sync replica set) 同步副本
	4.1 例如:由于Kafka选用全部副本同步完成才返回ack,这样会有一种情况:当某一个副本由于网络原因长时间不同同步完成,
	此时leader便不能返回ack。为了解决此问题,提出ISR列表的概念:当某一个follower,长时间未向leader同步数据,
	便将此副本从ISR列表剔除,以便ISR列表保证全部可以快速完成同步返回ack,当然ISR列表也用于当leader挂了的时候,
	可以从中选择数据完整的副本作为新的leader。注:当然,所有副本同步数据不完整时,便选择同步多的作为ISR,
	但是给消费者只暴露最低的同步数(即HW - high watermark),即使leader复活也要截取到当前的HW。(生产者丢失了一部分数据哦)
	4.2 replica.lag.time.max.ms 决定是否被剔除ISR列表的同步数据时间
5. Exactly Once语义
	5.1 ACK设置为-1: At Least Once(至少一次) 保证数据不丢失,但是不保证数据不重复
	5.2 ACK设置为0: At Most Once(最多一次) 生产者只发送一次,保证不重复,但是会丢失
	5.3 Kafka 0.11引入特性: 幂等性
		At Least Once + 幂等性 = Exactly Once (数据去重后 便是 仅此一次)
	5.4 设置幂等性: enable.idompotence=true
6. 消费者
	6.1 消费方式
		6.1.1 pull 消费者从broker拉取数据(Kafka选用)
			Kafka没有数据时,消费者需要循环请求返回空数据,Kafka传入timeout决定不要频繁拉取空数据列表
		6.1.2 push broker向消费者推送数据
			broker决定发送速率,会造成发送太快,consumer来不及处理消息
	6.2 分区分配策略
		6.2.1 RoundRobin轮询(如果同一个消费者组内消费者订阅同一样的主题,可用;如果不一样会造成轮询分配错误,因为他把主题作为一个主体了)
		6.2.2 Range(一个消费者组只能消费一个主题的同一个分区一次,其他消费者便不可以再消费)
7. Kafka高效读写
	7.1 顺序写磁盘 600M/s > 随机写磁盘 100K/s ,省去了大量磁头寻址的时间
	7.2 零拷贝
		直接将你的读写交给操作系统去执行,不经过代码拷贝;
8. Zookeeper作用
	负责broker管理,以及选主(ISR)管理
9. Kafka事务
	幂等性+At least Once,只解决单次回话内数据不重复,当生产者挂了时,不保证重复消费
	此时,需要引入事务:
	9.1 Producer事务
		引入一个Transaction ID,客户端保存一个事务ID和生产者ID的绑定,当生产挂掉重启后,根据事务ID获取之前的PID重新赋予,
		而不是重新生成PID,此时便保证了幂等性的效果,从而达到精准一次性消费的效果。
	9.2 Consumer事务
		消费者事务修改offset,但是比较弱 

6. KafkaAPI

1. 消息发送流程
	Kafka的生产者Producer发送消息是异步;
	main线程、sender线程两个线程,一个共享变量-RecordAccumulator
	main线程将消息发送给RecordAccumulator,sender线程不断从RecordAccumulator拉取消息发送给Kafka broker
	1.1 main线程:
	sender(ProducerRecord) -> Interceptors -> Serializer -> Partitioner -> RecordAccumulator
	1.2 相关参数
	batch.size 只有数据积累到该量时,sender才会发送数据
	linger.ms 当数据迟迟达不到batch.size时,sender等待linger.ms之后就会自动发送数据

7. Java Producer代码

<!-- 直接使用 Kafka 客户端jar-->
    <dependency>
      <groupId>org.apache.kafka</groupId>
      <artifactId>kafka-clients</artifactId>
      <version>2.2.0</version>
    </dependency>
package com.dream.bigdata.bi.es.kafka;

import org.apache.kafka.clients.producer.*;

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

public class MyKafkaProducer  {
    
    

    public static void main(String[] args) {
    
    

        // 1. 自定义Kafka生产者的配置参数
        Properties properties = new Properties();
        // 2. 指定连接的kafka集群
        // properties.put("bootstrap.servers", "113.143.100.155:9092,113.143.100.140:9092,113.143.100.148:9092");
        // 当记不住这些参数时,可以通过ProducerConfig类去查找
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "aaa:9092,bbb:9092,ccc:9092");
        // 3. ACK应答级别
        properties.put("acks", "all");
        // 4. 重试次数
        properties.put("retries", 3);
        // 5. 批次的大小
        properties.put("batch.size", 16384);
        // 6. 未到达批次时,等待时间
        properties.put("linger.ms", 1);
        // 7. 共享变量RecordAccumulator的缓冲区大小
        properties.put("buffer.memory", 33554432); //32M
        // 8. key、value的序列化方式
        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        // 自定义分区器
        properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "com.dream.bigdata.bi.es.kafka.MyPartitioner");

        // 9. 创建Kafka生产者对象
        KafkaProducer producer = new KafkaProducer(properties);

        // 10. 发送消息
        for(int i=0; i<10; i++){
    
    
//            producer.send(new ProducerRecord("qdd100", "qdd is comming" + i));// 未指定分区
            try {
    
    
                producer.send(new ProducerRecord("qdd100", "qdd is comming" + i),
                        new Callback() {
    
     // 此处可以使用lambda表达式
                    @Override
                    public void onCompletion(RecordMetadata metadata, Exception exception) {
    
    
                        if(null == exception){
    
    
                            System.out.println(metadata.partition() + " ===> " + metadata.offset());
                        }else{
    
    
                            exception.printStackTrace();
                        }
                    }
                }).get(); // 此处调用get方法 阻塞当前线程 将main线程阻塞实现同步 一般用不到
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } catch (ExecutionException e) {
    
    
                e.printStackTrace();
            }
//            producer.send(new ProducerRecord("qdd100", "qdd is comming" + i),// 未指定分区
//                    new Callback() { // 此处可以使用lambda表达式
//                @Override
//                public void onCompletion(RecordMetadata metadata, Exception exception) {
    
    
//                    if(null == exception){
    
    
//                        System.out.println(metadata.partition() + " ===> " + metadata.offset());
//                    }else{
    
    
//                        exception.printStackTrace();
//                    }
//                }
//            });
        }

        // 11. 关闭客户端 会调用清理内存操作,即使未达到等待时间 也会发送
        producer.close();

    }
}


8. Java Consumer代码

package com.dream.bigdata.bi.es.kafka;

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

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

public class MyKafkaConsumer {
    
    

    public static void main(String[] args) {
    
    

        // 1. 创建消费者配置信息
        Properties properties = new Properties();

        // 2. Kafka集群信息
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "aaa:9092,bbb:9092,ccc:9092");

        // 3. 开启自动提交
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);

        // 4. 自动提交时间
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000); // 默认提交时间1000ms

        // 5. key、value的反序列化方式
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");

        // 6. 消费者组
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "qdd");

        // 7. 创建消费者
        KafkaConsumer consumer = new KafkaConsumer(properties);

        // 8. 消费者订阅主题
        consumer.subscribe(Arrays.asList("qdd100", "qdd", "qdd1"));

        while (true){
    
    
            // 9. 获取数据
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
            // 10. 解析并打印结果
            for (ConsumerRecord<String, String> consumerRecord: consumerRecords) {
    
    
                System.out.println(consumerRecord.key() + " ===> " + consumerRecord.value());
            }
        }

    }
}

9. 拦截器(interceptor)案例

9.1 在发送之前给 消息添加时间戳前缀的 拦截器1

package com.dream.bigdata.bi.es.kafka;

import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Map;

public class MyAddTimestampInterceptor implements ProducerInterceptor<String, String> {
    
    

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

    }

    /**
     * 在发送之前拦截 给value加上时间戳
     * @param record
     * @return
     */
    @Override
    public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
    
    
        // 取出消息的值
        String value = record.value();
        // 创建一个新的ProducerRecord<String, String>,修改值并返回
        ProducerRecord<String, String> producerRecord = new ProducerRecord<>(record.topic(), record.partition()
                , record.key(), System.currentTimeMillis()+"_"+value);
        return producerRecord;
    }

    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
    
    

    }

    @Override
    public void close() {
    
    

    }

}

9.2 在逐条发送过程中给数据成功做统计,并在拦截器关闭前输出统计结果的拦截器2

package com.dream.bigdata.bi.es.kafka;

import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Map;

public class MyCountInterceptor implements ProducerInterceptor<String, String> {
    
    

    int success;
    int error;

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

    }

    @Override
    public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
    
    
        return record;
    }

    /**
     * 在发送成功后,将成功失败的数量统计
     * @param metadata
     * @param exception
     */
    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
    
    
        if(exception != null){
    
    
            error++;
        }else{
    
    
            success++;
        }
    }

    /**
     * 逐条统计完的结果 在拦截器关闭之前 输出统计结果
     */
    @Override
    public void close() {
    
    
        System.out.println("success: " + success);
        System.out.println("error: " + error);
    }
}

9.3 创建一个带拦截器的生产者

package com.dream.bigdata.bi.es.kafka;

import org.apache.kafka.clients.producer.*;

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

public class MyKafkaProducer  {
    
    

    public static void main(String[] args) {
    
    

        // 1. 自定义Kafka生产者的配置参数
        Properties properties = new Properties();
        // 2. 指定连接的kafka集群
        // properties.put("bootstrap.servers", "113.143.100.155:9092,113.143.100.140:9092,113.143.100.148:9092");
        // 当记不住这些参数时,可以通过ProducerConfig类去查找
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "113.143.100.155:9092,113.143.100.140:9092,113.143.100.148:9092");
        // 3. ACK应答级别
        properties.put("acks", "all");
        // 4. 重试次数
        properties.put("retries", 3);
        // 5. 批次的大小
        properties.put("batch.size", 16384);
        // 6. 未到达批次时,等待时间
        properties.put("linger.ms", 1);
        // 7. 共享变量RecordAccumulator的缓冲区大小
        properties.put("buffer.memory", 33554432); //32M
        // 8. key、value的序列化方式
        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        // 自定义分区器
        properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "com.dream.bigdata.bi.es.kafka.MyPartitioner");

        // 添加自定义拦截器
        properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,
                Arrays.asList("com.dream.bigdata.bi.es.kafka.MyAddTimestampInterceptor",
                        "com.dream.bigdata.bi.es.kafka.MyCountInterceptor"));

        // 9. 创建Kafka生产者对象
        KafkaProducer producer = new KafkaProducer(properties);

        // 10. 发送消息
        for(int i=0; i<10; i++){
    
    
//            producer.send(new ProducerRecord("qdd100", "qdd is comming" + i));// 未指定分区
            producer.send(new ProducerRecord("qdd100", "qdd", "qdd is comming" + i),
                    new Callback() {
    
     // 此处可以使用lambda表达式
                        @Override
                        public void onCompletion(RecordMetadata metadata, Exception exception) {
    
    
                            if(null == exception){
    
    
                                System.out.println(metadata.partition() + " ===> " + metadata.offset());
                            }else{
    
    
                                exception.printStackTrace();
                            }
                        }
                    });
//            producer.send(new ProducerRecord("qdd100", "qdd is comming" + i),// 未指定分区
//                    new Callback() { // 此处可以使用lambda表达式
//                @Override
//                public void onCompletion(RecordMetadata metadata, Exception exception) {
    
    
//                    if(null == exception){
    
    
//                        System.out.println(metadata.partition() + " ===> " + metadata.offset());
//                    }else{
    
    
//                        exception.printStackTrace();
//                    }
//                }
//            });
        }

        // 11. 关闭客户端 会调用清理内存操作,即使未达到等待时间 也会发送
        producer.close();

    }
}

猜你喜欢

转载自blog.csdn.net/q18729096963/article/details/112483198
今日推荐