Kafka 学习笔记

一. Kafka 入门

1. 介绍

Kafka是由Apache开发的一个开源流处理平台,由Scala和Java编写。目标是为处理实时数据提供一个统一、高吞吐、低延迟的平台。其持久化层本质上是一个“按照分布式事务日志架构的大规模发布/订阅消息队列”, 这使它作为企业级基础设施来处理流式数据非常有价值。(维基百科)

2. kafka特性

  • 高吞吐量,低延迟: 每个topic可以拥有多个partition,被同一消费者组下的消费者同时消费。

  • 持久性:消息可以持久化到本地磁盘,并且支持数据备份。

  • 容错性:消息存储在kafka集群中分为多个partition,每个partition可以拥有多个副本(leader)。

  • 可扩展性:支持集群动态扩展

3. 使用场景

  • 日志收集

  • 消息系统

二. 架构及原理

1. 名词解释

  • Topic:Kafka将消息种子(Feed)分门别类,每一类的消息称之为一个主题(Topic)

  • Partition: 每个Topic中的消息会被分为若干个分区(Partition)

  • Producer: 发布消息的对象称之为主题生产者(Kafka topic producer)

  • Consumer: 订阅消息并处理发布的消息的种子的对象称之为主题消费者(consumer)

  • Consumer Group: 消息的消费群组, 拥有一个或者多个消费者(consumer)

  • Broker: 已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理(Broker)。消费者(Consumer)可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。

  • offset: 消费者消费进度,由消费者组管理,保存在Zookeeper上。

2. Kafka与Zookeeper的关系

  • 每个Broker上线的时候,都会到Zookeeper上进行注册,然后创建临时节点,当Broker下线时,临时节点会被删除。

  • Zookeeper不仅存储了Kafka的内部元数据,而且记录了消费组的成员列表、分区的消费进度(offset)、分区的所有者消费者要消费哪些分区的消息由消费组来决定,因为消费组管理所有的消费者,所以它需要知道集群中所有可用的分区和所有存活的消费者,才能执行分区分配算法,而这些信息都需要保存到ZK中。每个消费者都要在Zookeeper的消费组节点下注册对应的消费者节点,在分配到不同的分区后,才会开始各自拉取分区的消息。

3. 分区(Partition)的原理

消息的分区被分布到集群中的多个服务器上。每个服务器处理它分到的分区。 根据配置每个分区还可以复制到其它服务器作为备份容错。每个分区有一个leader,零或多个follower。Leader处理此分区的所有的读写请求,而follower被动的复制数据。如果leader宕机,其它的一个follower会被推举为新的leader。一台服务器可能同时是一个分区的leader,另一个分区的follower。这样可以平衡负载,避免所有的请求都只让一台或者某几台服务器处理。

4. 消息的生产及储存过程

Kafka储存的消息来自于生产者(Producer), 生产者将消息发布到指定的主题(Topic),同时也可以指定发布到主题的某个分区(Partition)中。

  • 如何确定消息存储在哪个分区?

首先判断消息是否指定了分区;其次判断消息的是否有key,如果有,则根据key的hash进行分区;否则进行随机分配(轮询)。

  • 消息的写入过程

Producer从Zookeeper中找到节点的leader信息,然后将消息发送给leader,leader将消息写入本地log,follower从leader pull(拉)消息,成功后写入本地log。

同步模式/异步模式: request.requred.ack=0(不应答) || 1(leader落盘应答) || -1(follower落盘应答) //可以通过初始化producer时的producerconfig进行配置

  • 消息存储策略

无论消息是否被消费,kafka 都会保留所有消息。有两种策略可以删除旧数据:

① 基于时间:log.retention.hours=168。

② 基于大小:log.retention.bytes=1073741824

5. 消息的消费模式及过程

  • 消费者的消费模式

① 所有的消费者同属于一个组,则类似于queue模式, 消息会在消费者之间进行负载均衡。

② 所有的消费者都具有不同的组,则是发布订阅模式,消息会广播到每个消费者中。

  • 消息的消费过程

消费者(Consumer)通过订阅主题(Topic),主动从集群的分区中拉取消息进行消费。一个分区(Partition)只能被同一消费者组中的一个消费者进行消费,避免消息的重复消费。消费者可以消费多个分区(即分区消费完后可以继续消费没有被同组消费者消费过的分区)。对于一个topic, 消费者组中的消费者数不能多于分区数,否则意味着有一些消费者无法接收到消息。

  • 消费过程中消费者宕机了,会不会影响消息的消费?

如果一个消费者宕机了,分配给这个消费者的分区需要重新分配给相同组的其他消费者;由于offset是以消费者组为单位进行维护,且保存在Zookeeper上,不关心哪个消费者在消费。所以能够保证消费消息是上一个消费者消费到地方。

6. 分区策略(partition.assignment.strategy)

  • Range策略

Range策略是对每个主题而言的,首先对同一个主题里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序。然后将partitions的个数除于消费者线程的总数来决定每个消费者线程消费几个分区。如果除不尽,那么前面几个消费者线程将会多消费一个分区。

  • RoundRobin(轮询):

将所有主题的分区组成TopicAndPartition列表,然后对 TopicAndPartition 列表按照 hashCode 进行排序,然后轮询分配(你一个我一个)。使用RoundRobin策略有两个前提条件必须满足:

① 同一个Consumer Group里面的所有消费者的num.streams必须相等。

② 每个消费者订阅的主题必须相同

  • 当以下事件发生时,会进行一次分区分配:
    • 同一个消费者组里新增消费者
    • 消费者离开所在组(shuts down/ crashes)
    • 订阅主题新增分区

7. 文件存储机制:

在Kafka文件存储中,同一个topic下有多个不同的partition,每个partiton为一个目录,partition的名称规则为:topic名称+序序号,第一个序号从0开始计,最大的序号为partition数量减1,partition是实际物理上的概念,而topic是逻辑上的概念。partition还可以细分为segment:(.log/.index):

segment文件由两部分组成,分别为“.index”文件和“.log”文件,分别表示为segment索引文件和数据文件(引入索引文件的目的是便于利用二分查找快速定位message位置)。这两个文件的命令规则为:partition全局的第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值。.index文件中存储的是offset和对应的.log文件中的实践偏移量,所以通过offset然后找到文件的实际偏移量就可以读到数据(数据具有固定格式)

三、集群搭建(centos6.5)

1. 环境准备:

jdk1.8

Zookeeper-3.4.14

Scala: 注意版本对应,如 kafka_2.11-2.2.0 ,2.11代表Scala版本,2.2.0为Kafka版本

2. 下载:

http://kafka.apache.org/downloads

3. 部署Zookeeper

  • 下载

https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.14/

  • 解压后进入conf目录,重命名zoo_sample.cfg为zoo.cfg

mv zoo_sample.cfg zoo.cfg

  • 配置zoo.cfg
    dataDir=/home/hadoop/zookeeper-3.4.14/zkData
    #配置多个为集群模式
    server.1=master:2888:3888
    #server.2=slave1:2888:3888

Server.A=B:C:D。
A是一个数字,表示这个是第几号服务器;
B是这个服务器的ip地址;
C是这个服务器与集群中的Leader服务器交换信息的端口;
D是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个

  • 在/home/hadoop/zookeeper-3.4.14/zkData目录下创建一个myid的文件,并添加对应server的编号。

vim myid

1
  • 启动Zookeeper

bin/zkServer.sh start

  • 查看状态

bin/zkServer.sh status

配置单个Zookeeper机器则会默认是standalone,多个则会进行选举,然后选出leader。

4. 部署Kafka

  • 解压后进入config目录修改配置:

vim server.properties

#每个broker的值应该是唯一的。
broker.id=1 
#运行日志位置
log.dirs=/home/hadoop/apps/kafka_2.11-2.2.0/logs 
#master为主机名(在/etc/hosts配置好的),多个的话用逗号(,)隔开
zookeeper.connect=master:2181
#是否允许删除topic
delete.topic.enable=true
  • 启动集群

bin/kafka-server-start.sh config/server.properties &

配置了多个broker只需逐个启动即可。

  • 创建Topic

bin/kafka-topics.sh --create --zookeeper master:2181 --replication-factor 1 --partitions 1 --topic first

–replication-factor 指定副本数

–partitons 指定分区数

–topic 指定topic名字

  • 查看topic

bin/kafka-topics.sh --zookeeper master:2181 --list

  • 启动生产者客户端并指定topic

bin/kafka-console-producer.sh --broker-list master:9092 --topic first

  • 启动消费者客户端,并指定消费主题

bin/kafka-console-consumer.sh --bootstrap-server master:9092 --from-beginning --topic first

四、Java API

1. Maven相关依赖

    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka_2.11</artifactId>
        <version>2.2.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>1.0.0</version>
    </dependency>

2. 编写一个生产者

import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class FirstProducer {
    public static void main(String[] args) {
        Properties properties = new Properties();
        // 定义kakfa 服务的地址,不需要将所有broker指定上
        properties.put("bootstrap.servers", "192.168.31.209:9092");
        // 等待所有副本节点的应答
        properties.put("acks", "all");
        //key value 序列化
        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
        for (int i = 0; i < 10; i++) {
            //Callback 为发送成功后的回调函数
            producer.send(new ProducerRecord<String, String>("first", i + "", "hello, kafka-" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    if (recordMetadata != null){
                        System.out.println(recordMetadata.offset());
                    }
                }
            });
        }
        // 记得加上,不然会出现神奇错误...
        producer.close();
    }
}

3. 自定义分区

  • 自定义分区类,实现Partitioner接口
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;

public class TestPartitioner implements Partitioner {

    /**
     * 控制分区
     */
    @Override
    public int partition(String s, Object o, byte[] bytes, Object o1, byte[] bytes1, Cluster cluster) {
        //System.out.println(s);
        //将所有数据存储到topic的第0号分区上,
        return 0;
    }

    @Override
    public void close() {

    }

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

    }
}

  • 在实现生产者时加入以下设置
//添加自定义分区类
properties.put("partitioner.class","partitoner.TestPartitioner");
  • 可自行观察log.dirs设置的目录路径下first主题分区的log日志动态变化情况

4. 编写一个消费者

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 CustomConsumer {
    public static void main(String[] args) {
        Properties props = new Properties();
        // 定义kakfa 服务的地址,不需要将所有broker指定上
        props.put("bootstrap.servers", "192.168.31.209:9092");
        // 制定consumer group
        props.put("group.id", "test");
        // 是否自动确认offset
        props.put("enable.auto.commit", "true");
        // 自动确认offset的时间间隔
        props.put("auto.commit.interval.ms", "1000");
        //超时时间
        props.put("session.timeout.ms", "30000");
        // key的序列化类
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        // value的序列化类
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        // 定义consumer
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

        // 消费者订阅的topic, 可同时订阅多个
        consumer.subscribe(Arrays.asList("first"));

        while (true) {
            // 读取数据,读取超时时间为100ms
            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());
        }
    }
}

五、拦截器

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值