大数据之路(八)——kafka

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wy888882/article/details/88680950

kafka简介

ApacheKafka®是一个分布式流媒体平台。
流媒体平台有三个关键功能:
1、发布和订阅记录流,类似于消息队列或企业消息传递系统。
2、以容错的持久方式存储记录流。
3、记录发生时处理流。
Kafka通常用于两大类应用:
1、构建可在系统或应用程序之间可靠获取数据的实时流数据管道
2、构建转换或响应数据流的实时流应用程序
首先是几个概念:
Kafka作为一个集群运行在一个或多个可跨多个数据中心的服务器上。
Kafka集群以称为主题的类别存储记录流。
每条记录由一个键,一个值和一个时间戳组成。
Kafka有四个核心API:
1、producer API允许应用程序发布的记录流至一个或多个卡夫卡的话题。
2、consumer API允许应用程序订阅一个或多个主题,并处理所产生的对他们记录的数据流。
3、streams API允许应用程序充当流处理器,从一个或多个主题消耗的输入流,并产生一个输出流至一个或多个输出的主题,有效地变换所述输入流,以输出流。
4、 connector API允许构建和运行卡夫卡主题连接到现有的应用程序或数据系统中重用生产者或消费者。例如,关系数据库的连接器可能捕获对表的每个更改。
在Kafka中,客户端和服务器之间的通信是通过简单,高性能,语言无关的TCP协议完成的。
在这里插入图片描述

Topics and Logs
首先深入探讨Kafka为记录流提供的核心抽象 - topic.。
topic是发布记录的类别或订阅源名称。Kafka的主题总是多用户; 也就是说,一个主题可以有零个,一个或多个消费者订阅写入它的数据。
对于每个主题,Kafka群集都维护一个分区日志,如下所示:
在这里插入图片描述

每个分区都是一个有序的,不可变的记录序列,不断附加到结构化的提交日志中。分区中的记录每个都被分配一个称为偏移的顺序ID号,它唯一地标识分区中的每个记录。
Kafka集群持久地保留所有已发布的记录 - 无论它们是否已被消耗 - 使用可配置的保留期。例如,如果保留策略设置为两天,则在发布记录后的两天内,它可供使用,之后将被丢弃以释放空间。Kafka的性能在数据大小方面实际上是恒定的,因此长时间存储数据不是问题。
在这里插入图片描述

每个消费者保留的唯一元数据是该消费者在日志中的偏移或位置。这种偏移由消费者控制:通常消费者在读取记录时会线性地提高其偏移量,但事实上,由于消费者控制位置,它可以按照自己喜欢的任何顺序消费记录。例如,消费者可以重置为较旧的偏移量以重新处理过去的数据,或者跳到最近的记录并从“现在”开始消费。
日志中的分区有多种用途。首先,它们允许日志扩展到超出适合单个服务器的大小。每个单独的分区必须适合托管它的服务器,但主题可能有许多分区,因此它可以处理任意数量的数据。
Topic、Partition文件存储

Topic与Partition的关系
Topic在逻辑上可以被认为是一个queue,每条消费都必须指定它的Topic,可以简单理解为必须指明把这条消息放进哪个queue里。为了使得Kafka的吞吐率可以线性提高,物理上把Topic分成一个或多个Partition,每个Partition在物理上对应一个文件夹,该文件夹下存储这个Partition的所有消息和索引文件。若创建topic1和topic2两个topic,且分别有13个和19个分区,则整个集群上会相应会生成共32个文件夹。partiton命名规则为topic名称+有序序号,第一个partiton序号从0开始,序号最大值为partitions数量减1。
Partition文件存储的特点
(1)每个partition目录相当于一个巨型文件被平均分配到多个大小相等segment数据文件中。但每个segment file消息数量不一定相等,这种特性方便old segment file快速被删除。

(2)每个partiton只需要支持顺序读写就行了,segment文件生命周期由服务端配置参数决定。

(3)segment file组成:由2大部分组成,分别为index file(后缀“.index”)和data file(后缀“.log”),此2个文件一一对应,成对出现。

(4)segment文件命名规则:partition全局的第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值。数值最大为64位long大小,19位数字字符长度,没有数字用0填充。
在这里插入图片描述
以上述图2中一对segment file文件为例,说明segment中index和log文件对应关系物理结构如下:
在这里插入图片描述
其中以索引文件中元数据3,497为例,依次在数据文件中表示第3个message(在全局partition表示第368772个message)、以及该消息的物理偏移地址为497。
在partition中如何通过offset查找message
例如读取offset=368776的message,需要通过下面2个步骤查找。

(1)第一步查找segment file

上图为例,其中00000000000000000000.index表示最开始的文件,起始偏移量(offset)为0.第二个文件00000000000000368769.index的消息量起始消息为368770 = 368769 + 1.同样,第三个文件00000000000000737337.index的起始消息为737338=737337 + 1,只要根据offset 进行二分查找文件列表,就可以快速定位到具体文件。当offset=368776时定位到00000000000000368769.index|log。

(2)第二步通过segment file查找message

通过第一步定位到segment file,当offset=368776时,依次定位到00000000000000368769.index的元数据物理位置和00000000000000368769.log的物理偏移地址,然后再通过00000000000000368769.log顺序查找直到offset=368776为止。
Kafka集群中Partition分布规则
首先来看一条在Linux下创建topic的命令:

bin/kafka-topics.sh --create --zookeeper ip1:2181,ip2:2181,ip3:2181,ip4:2181 --replication-factor 2 --partitions 4 --topic test

此命令的意思是在四个Broker的kafka集群上创建一个名为test的Topic,并且有4个分区2个备份(此处比较容易搞混,2个Replication表示Leader和Follower一共加起来有2个)。此时在四台机器上面就有8个Partition,如图所示。
在这里插入图片描述
Kafka集群Partition分布图1(图片来源于网络)

当集群中新增2节点,Partition增加到6个时分布情况如下:
在这里插入图片描述
Kafka集群Partition分布图2(图片来源于网络)

在Kafka集群中,每个Broker都有均等分配Leader Partition机会。

上述图Broker Partition中,箭头指向为副本,以Partition-0为例:broker1中parition-0为Leader,Broker2中Partition-0为副本。每个Broker(按照BrokerId有序)依次分配主Partition,下一个Broker为副本,如此循环迭代分配,多副本都遵循此规则。

副本分配算法:

(1)将所有n个Broker和待分配的i个Partition排序。

(2)将第i个Partition分配到第(i mod n)个Broker上。

(3)将第i个Partition的第j个副本分配到第((i + j) mod n)个Broker上

例如图2中的第三个Partition:partition-2,将被分配到Broker3((3 mod 6)=3)上,partition-2的副本将被分配到Broker4上((3+1) mod 6=4)。
kafka文件存储特点
(1)Kafka把topic中一个parition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用。可以设置segment文件大小定期删除和消息过期时间定期删除

(2)通过索引信息可以快速定位message。

(3)通过index元数据全部映射到memory,可以避免segment file的IO磁盘操作。

(4)通过索引文件稀疏存储,可以大幅降低index文件元数据占用空间大小。
Producers
生产者将数据发布到他们选择的主题。生产者负责选择分配给主题中哪个分区的记录。
Consumers
消费者使用消费者组名称标记自己,并且发布到主题的每个记录被传递到每个订阅消费者组中的一个消费者实例。消费者实例可以在单独的进程中,也可以在不同的机器

如果所有使用者实例具有相同的使用者组,则记录将有效地在使用者实例上进行负载平衡。

如果所有消费者实例具有不同的消费者组,则每个记录将广播到所有消费者进程。
在这里插入图片描述

两个服务器Kafka群集,托管四个分区(P0-P3),包含两个使用者组。消费者组A有两个消费者实例,B组有四个消费者实例。
Kafka作为存储系统
任何允许发布与消费它们分离的消息的消息队列实际上充当了正在进行的消息的存储系统。Kafka的不同之处在于它是一个非常好的存储系统。

写入Kafka的数据将写入磁盘并进行复制以实现容错。Kafka允许生产者等待确认,以便在完全复制之前写入不被认为是完整的,并且即使写入的服务器失败也保证写入仍然存在。

磁盘结构Kafka很好地使用了规模 - 无论服务器上有50 KB还是50 TB的持久数据,Kafka都会执行相同的操作。

由于认真对待存储并允许客户端控制其读取位置,您可以将Kafka视为一种专用于高性能,低延迟提交日志存储,复制和传播的专用分布式文件系统。
Kafka术语介绍
1、消息生产者:即:Producer,是消息的产生的源头,负责生成消息并发送到Kafka

服务器上。
2、消息消费者:即:Consumer,是消息的使用方,负责消费Kafka服务器上的消息。
3、主题:即:Topic,由用户定义并配置在Kafka服务器,用于建立生产者和消息者之间的订阅关系:生产者发送消息到指定的Topic下,消息者从这个Topic下消费消息。
4、消息分区:即:Partition,一个Topic下面会分为很多分区,例如:“kafka-test”这个Topic下可以分为6个分区,分别由两台服务器提供,那么通常可以配置为让每台服务器提供3个分区,假如服务器ID分别为0、1,则所有的分区为0-0、0-1、0-2和1-0、1-1、1-2。Topic物理上的分组,一个 topic可以分为多个 partition,每个 partition 是一个有序的队列。partition中的每条消息都会被分配一个有序的 id(offset)。
5、Broker:即Kafka的服务器,用户存储消息,Kafa集群中的一台或多台服务器统称为 broker。
6、消费者分组:Group,用于归组同类消费者,在Kafka中,多个消费者可以共同消息一个Topic下的消息,每个消费者消费其中的部分消息,这些消费者就组成了一个分组,拥有同一个分组名称,通常也被称为消费者集群。
7、Offset:消息存储在Kafka的Broker上,消费者拉取消息数据的过程中需要知道消息在文件中的偏移量,这个偏移量就是所谓的Offset。
java使用Kafka
1、maven依赖

<dependency>
 
<groupid>org.apache.kafka</groupid>
 
kafka-clients</artifactid>
 
<version>0.11.0.0</version>
 
</dependency>

2、Producer
2.1、producer发送消息

import java.util.Properties;
 
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
/**
 * 最简单的kafka producer
 */
public class ProducerDemo {
 
    public static void main(String[] args) {
        Properties properties =new Properties();
        //zookeeper服务器集群地址,用逗号隔开
        properties.put("bootstrap.servers", "172.16.0.218:9092,172.16.0.219:9092,172.16.0.217:9092");
        properties.put("acks", "all");
        properties.put("retries", 0);
        properties.put("batch.size", 16384);
        properties.put("linger.ms", 1);
        properties.put("buffer.memory", 33554432);
        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        //自定义producer拦截器
        properties.put("interceptor.classes", "com.lt.kafka.producer.MyProducerInterceptor");
        //自定义消息路由规则(消息发送到哪一个Partition中)
        //properties.put("partitioner.class", "com.lt.kafka.producer.MyPartition");
         
        Producer<string, string=""> producer = null;
        try {
            producer = new KafkaProducer<string, string="">(properties);
            for (int i = 20; i < 40; i++) {
                String msg = "This is Message:" + i;
                 
                /**
                 * kafkaproducer中会同时调用自己的callback的onCompletion方法和producerIntercepter的onAcknowledgement方法。
                 * 关键源码:Callback interceptCallback = this.interceptors == null 
                 * callback : new InterceptorCallback<>(callback,
                 * this.interceptors, tp);
                 */
                producer.send(new ProducerRecord<string, string="">("leixiang", msg),new MyCallback());
            }
        } catch (Exception e) {
            e.printStackTrace();
 
        } finally {
            if(producer!=null)
            producer.close();
        }
    }
 
}</string,></string,></string,>

2.2、自定义producer拦截器

import java.util.Map;
 
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
/**
 * 自定义producer拦截器
 */
public class MyProducerInterceptor implements ProducerInterceptor<string,string> {
 
    /**
     * 打印配置相关信息
     */
    public void configure(Map<string,> configs) {
        // TODO Auto-generated method stub
        System.out.println(configs.toString());
    }
 
    /**
     * producer发送信息拦截方法
     */
    public ProducerRecord<string,string> onSend(ProducerRecord<string, string=""> record) {
        System.out.println("拦截处理前》》》");
        String topic=record.topic();
        String value=record.value();
        System.out.println("拦截处理前的消息:"+value);
        ProducerRecord<string,string> record2=new ProducerRecord<string, string="">(topic, value+" (intercepted)");
        System.out.println("拦截处理后的消息:"+record2.value());
        System.out.println("拦截处理后《《《");
        return record2;
    }
 
    /**
     * 消息确认回调函数,和callback的onCompletion方法相似。
     * 在kafkaProducer中,如果都设置,两者都会调用。
     */
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
        if (metadata != null)
            System.out.println("MyProducerInterceptor onAcknowledgement:RecordMetadata=" + metadata.toString());
        if (exception != null)
            exception.printStackTrace();
    }
 
    /**
     * interceptor关闭回调
     */
    public void close() {
        System.out.println("MyProducerInterceptor is closed!");
    }
 
}</string,></string,string></string,></string,string></string,></string,string>

2.3、自定义消息路由规则

自定义路由规则,可以根据自己的需要定义消息发送到哪个分区。自定义路由规则需要实现Partitioner。

import java.util.Map;
 
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
 
public class MyPartition implements Partitioner{
 
    public void configure(Map<string,> arg0) {
        // TODO Auto-generated method stub
         
    }
 
    public void close() {
        // TODO Auto-generated method stub
         
    }
 
    public int partition(String arg0, Object arg1, byte[] arg2, Object arg3, byte[] arg4, Cluster arg5) {
        // TODO Auto-generated method stub
        return 0;
    }
 
}</string,>

3、Consumer
3.1、自动提交

import java.util.Arrays;
import java.util.Properties;
 
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
 
public class AutoCommitConsumerDemo {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "172.16.0.218:9092,172.16.0.219:9092,172.16.0.217:9092");
        props.put("group.id", "leixiang");
        props.put("enable.auto.commit", "true");
        //想要读取之前的数据,必须加上
        //props.put("auto.offset.reset", "earliest");
        /* 自动确认offset的时间间隔 */
        props.put("auto.commit.interval.ms", "1000");
        /*
         * 一旦consumer和kakfa集群建立连接,
         * consumer会以心跳的方式来高速集群自己还活着,
         * 如果session.timeout.ms 内心跳未到达服务器,服务器认为心跳丢失,会做rebalence
         */
        props.put("session.timeout.ms", "30000");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        //配置自定义的拦截器,可以在拦截器中引入第三方插件实现日志记录等功能。
        //props.put("interceptor.classes", "com.lt.kafka.consumer.MyConsumerInterceptor");
         
        @SuppressWarnings("resource")
        KafkaConsumer<string, string=""> consumer = new KafkaConsumer<string, string="">(props);
        try {
            /* 消费者订阅的topic, 可同时订阅多个 ,用逗号隔开*/
            consumer.subscribe(Arrays.asList("leixiang"));
            while (true) {
                //轮询数据。如果缓冲区中没有数据,轮询等待的时间为毫秒。如果0,立即返回缓冲区中可用的任何记录,则返回空
                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());
            }
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}</string,></string,></string,></string,>

3.2、手动提交

import java.util.Arrays;
import java.util.Properties;
 
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
 
public class ManualCommitConsumerDemo {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "172.16.0.218:9092,172.16.0.219:9092,172.16.0.217:9092");
        props.put("group.id", "leixiang");
        props.put("enable.auto.commit", "false");//手动确认
        /* 自动确认offset的时间间隔 */
        props.put("auto.commit.interval.ms", "1000");
        props.put("auto.offset.reset", "earliest");//想要读取之前的数据,必须加上
        /*
         * 一旦consumer和kakfa集群建立连接,
         * consumer会以心跳的方式来高速集群自己还活着,
         * 如果session.timeout.ms 内心跳未到达服务器,服务器认为心跳丢失,会做rebalence
         */
        props.put("session.timeout.ms", "30000");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        //配置自定义的拦截器,可以在拦截器中引入第三方插件实现日志记录等功能。
        props.put("interceptor.classes", "com.lt.kafka.consumer.MyConsumerInterceptor");
         
        KafkaConsumer<string, string=""> consumer = new KafkaConsumer<string, string="">(props);
        /* 消费者订阅的topic, 可同时订阅多个 ,用逗号隔开*/
        consumer.subscribe(Arrays.asList("leixiang"));
        while (true) {
            ConsumerRecords<string, string=""> records = consumer.poll(100);
            for (ConsumerRecord<string, string=""> record : records) {
                //处理消息
                saveMessage(record);
                //手动提交,并且设置Offset提交回调方法
                //consumer.commitAsync(new MyOffsetCommitCallback());
                consumer.commitAsync();
            }
        }
    }
     
    public static void saveMessage(ConsumerRecord<string, string=""> record){
        System.out.printf("处理消息:offset = %d, key = %s, value = %s%n", record.offset(), record.key(),
                            record.value());
    }
}</string,></string,></string,></string,></string,>

自定义Consumer拦截器

import java.util.Map;
 
import org.apache.kafka.clients.consumer.ConsumerInterceptor;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition;
 
public class MyConsumerInterceptor implements ConsumerInterceptor<string, string=""> {
 
    public void configure(Map<string,> configs) {
        System.out.println("MyConsumerInterceptor configs>>>"+configs.toString());
    }
 
    public ConsumerRecords<string, string=""> onConsume(ConsumerRecords<string, string=""> records) {
        System.out.println("onConsume");
        return records;
    }
 
    public void onCommit(Map<topicpartition, offsetandmetadata=""> offsets) {
        System.out.println("onCommit");
    }
 
    public void close() {
        System.out.println("MyConsumerInterceptor is closed!");
    }
 
}</topicpartition,></string,></string,></string,></string,>

自定义Offset提交回调方法

import java.util.Map;
 
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.clients.consumer.OffsetCommitCallback;
import org.apache.kafka.common.TopicPartition;
 
public class MyOffsetCommitCallback implements OffsetCommitCallback {
 
    public void onComplete(Map<topicpartition, offsetandmetadata=""> offsets, Exception exception) {
        if (offsets != null)
            System.out.println("offsets>>>" + offsets.toString());
        if (exception != null)
            exception.printStackTrace();
    }
 
}</topicpartition,>

Kafka常用命令
1、开启zookeeper(在安装目录下使用命令)
Linux:bin/zkServer.sh start

windows:bin\zkServer.cmd

2、启动kafka(安装目录下使用命令)
Linux:bin/kafka-server-start.sh start config/server.properties

windows:bin\windows\kafka-server-start.bat config\server.properties

3、查看topic名称列表
Linux:bin/kafka-topics.sh -list --zookeeper 172.16.0.99:2181,172.16.0.218:2181

windows:bin\windows\kafka-topics.bat -list --zookeeper 172.16.0.99:2181,172.16.0.218:2181

4、查看topic详情
Linux:bin/kafka-topics.sh -zookeeper localhost:2181 --topic test --describe

windows:bin\windows\kafka-topics.bat --zookeeper localhost:2181 --topic test --describe

5、删除topic
Linux:bin/kafka-topics.sh --zookeeper localhost:2181 --delete --topic “test”

windows:bin\windows\kafka-topics.bat --zookeeper localhost:2181 --delete --topic “test”

注意:集群中一台机器删除了topic,其他机器同步删除相同topic

6、创建topic
Linux:bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test

windows:bin\windows\kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test

注意:

replication-factor:副本个数,一般为小于等于Broker个数。

partitions:分区个数。如果副本个数为1,分区为4,则4个分区会均匀的分布在各个Broker上。如果Broker为2,副本为2,分区为4,则每个Broker上面都有4个分区。

7、创建Consumer
Linux:bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic test --from-beginning

windows:bin\windows\kafka-console-consumer.bat --zookeeper localhost:2181 --topic test --form-beginning

注意:form beginning表示从头拉取。

8、创建Producer
Linux:bin/kafka-console-producer.sh --broker-list 172.16.0.99:9020,172.16.0.218:9020 --topic test

windows:bin\windows\kafka-console-producer.bat --broker-list 172.16.0.99:9092,172.16.0.218:9080 --topic test

注意:此处是kafka的端口,而且在集群里如果此处填localhost,会报一个连接错误,猜想应该是消息没有到达集群,因此此处将集群的ip都填上。

9、查询topic所有分区的offset值
Linux:bin/kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list 10.162.160.115:9092 --topic s1mmetest --time -1

10、查询kafka集群当前topic所有分区中的消息数目
Linux:bin/kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list 10.162.160.115:9092 --topic s1mmetest --time -2

展开阅读全文
博主设置当前文章不允许评论。

没有更多推荐了,返回首页