【无标题】

一、kafka介绍

   1.1什么是kafka

        Kafka是由Apache软件基金会开发的一个开源流平台,由Scala和Java编写

Apache Kafka是一个分布式流平台。一个分布式的流平台应该包含3点关键的能力:

  1. 发布和订阅流数据流,类似于消息队列或者是企业消息传递系统
  2. 以容错的持久化方式存储数据流
  3. 处理数据流

1.2 kafka的应用场景

我们通常将Apache Kafka用在两类程序:

  1. 建立实时数据管道,以可靠地在系统或应用程序之间获取数据
  2. 构建实时流应用程序,以转换或响应数据流

上图,我们可以看到:

  1. Producers:可以有很多的应用程序,将消息数据放入到Kafka集群中。
  2. Consumers:可以有很多的应用程序,将消息数据从Kafka集群中拉取出来。
  3. Connectors:Kafka的连接器可以将数据库中的数据导入到Kafka,也可以将Kafka的数据导出到数据库中。
  4. Stream Processors:流处理器可以Kafka中拉取数据,也可以将数据写入到Kafka中。

 1.3 Kafka的优势

特性

ActiveMQ

RabbitMQ

Kafka

RocketMQ

所属社区/公司

Apache

Mozilla Public License

Apache

Apache/Ali

成熟度

成熟

成熟

成熟

比较成熟

生产者-消费者模式

支持

支持

支持

支持

发布-订阅

支持

支持

支持

支持

REQUEST-REPLY

支持

支持

-

支持

API完备性

低(静态配置)

多语言支持

支持JAVA优先

语言无关

支持,JAVA优先

支持

单机呑吐量

万级(最差)

万级

十万级

十万级(最高)

消息延迟

-

微秒级

毫秒级

-

可用性

高(主从)

高(主从)

非常高(分布式)

消息丢失

-

理论上不会丢失

-

消息重复

-

可控制

理论上会有重复

-

事务

支持

不支持

支持

支持

文档的完备性

提供快速入门

首次部署难度

-

从以上列表可以对比出kafka与其他消息队列的区别,从而看到kafka的优势非常明显。在大数据技术领域,一些重要的组件、框架都支持Apache Kafka,不论成成熟度、社区、性能、可靠性,Kafka都是非常有竞争力的一款产品。 

二、kafka的环境搭建

  2.1搭建kafka集群

  1.  将Kafka的安装包上传到虚拟机,并解压

cd /export/software/

tar -xvzf kafka_2.12-2.4.1.tgz -C ../server/

cd /export/server/kafka_2.12-2.4.1/

     2.修改 server.properties

cd /export/server/kafka_2.12-2.4.1/config

vim server.properties

# 指定broker的id

broker.id=0

# 指定Kafka数据的位置

log.dirs=/export/server/kafka_2.12-2.4.1/data

# 配置zk的三个节点

zookeeper.connect=node1.itcast.cn:2181,node2.itcast.cn:2181,node3.itcast.cn:2181

    3.将安装好的kafka复制到另外两台服务器

cd /export/server

scp -r kafka_2.12-2.4.1/ node2.itcast.cn:$PWD

scp -r kafka_2.12-2.4.1/ node3.itcast.cn:$PWD

修改另外两个节点的broker.id分别为1和2

---------node2.itcast.cn--------------

cd /export/server/kafka_2.12-2.4.1/config

vim erver.properties

broker.id=1

--------node3.itcast.cn--------------

cd /export/server/kafka_2.12-2.4.1/config

vim server.properties

broker.id=2

    4.配置KAFKA_HOME环境变量

vim /etc/profile

export KAFKA_HOME=/export/server/kafka_2.12-2.4.1

export PATH=:$PATH:${KAFKA_HOME}

分发到各个节点

scp /etc/profile node2.itcast.cn:$PWD

scp /etc/profile node3.itcast.cn:$PWD

每个节点加载环境变量

source /etc/profile

    5.启动服务器

# 启动ZooKeeper

nohup bin/zookeeper-server-start.sh config/zookeeper.properties &

# 启动Kafka

cd /export/server/kafka_2.12-2.4.1

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

# 测试Kafka集群是否启动成功

bin/kafka-topics.sh --bootstrap-server node1.itcast.cn:9092 --list

2.2 目录结构分析 

目录名称

说明

bin

Kafka的所有执行脚本都在这里。例如:启动Kafka服务器、创建Topic、生产者、消费者程序等等

config

Kafka的所有配置文件

libs

运行Kafka所需要的所有JAR包

logs

Kafka的所有日志文件,如果Kafka出现一些问题,需要到该目录中去查看异常信息

site-docs

Kafka的网站帮助文件

        

2.3 Kafka一键启动/关闭脚本

为了方便将来进行一键启动、关闭Kafka,我们可以编写一个shell脚本来操作。将来只要执行一次该脚本就可以快速启动/关闭Kafka。

  1. 在节点1中创建 /export/onekey 目录

cd /export/onekey

      2.准备slave配置文件,用于保存要启动哪几个节点上的kafka

node1.itcast.cn

node2.itcast.cn

node3.itcast.cn

    3.编写start-kafka.sh脚本

vim start-kafka.sh

cat /export/onekey/slave | while read line

do

{

 echo $line

 ssh $line "source /etc/profile;export JMX_PORT=9988;nohup ${KAFKA_HOME}/bin/kafka-server-start.sh ${KAFKA_HOME}/config/server.properties >/dev/nul* 2>&1 & "

}&

wait

done

    4.编写stop-kafka.sh脚本

vim stop-kafka.sh

cat /export/onekey/slave | while read line

do

{

 echo $line

 ssh $line "source /etc/profile;jps |grep Kafka |cut -d' ' -f1 |xargs kill -s 9"

}&

wait

done

    5.给start-kafka.sh、stop-kafka.sh配置执行权限

chmod u+x start-kafka.sh

chmod u+x stop-kafka.sh

    6.执行一键启动、一键关闭

./start-kafka.sh

./stop-kafka.sh

三、kafka基本操作

 3.1 创建topic

创建一个topic(主题)。Kafka中所有的消息都是保存在主题中,要生产消息到Kafka,首先必须要有一个确定的主题。

# 创建名为test的主题

bin/kafka-topics.sh --create --bootstrap-server node1.itcast.cn:9092 --topic test

# 查看目前Kafka中的主题

bin/kafka-topics.sh --list --bootstrap-server node1.itcast.cn:9092

3.2 生产消息到kafka

使用Kafka内置的测试程序,生产一些消息到Kafka的test主题中。

bin/kafka-console-producer.sh --broker-list node1.itcast.cn:9092 --topic test

 3.3 从kafka消费数据

使用下面的命令来消费 test 主题中的消息。

bin/kafka-console-consumer.sh --bootstrap-server node1.itcast.cn:9092 --topic test --from-beginning

3.4 使用Kafka Tools操作Kafka 

安装Kafka Tools后启动Kafka

四、使用java操作kafka

4.1 导入依赖

<repositories><!-- 代码库 -->

    <repository>

        <id>central</id>

        <url>http://maven.aliyun.com/nexus/content/groups/public//</url>

        <releases>

            <enabled>true</enabled>

        </releases>

        <snapshots>

            <enabled>true</enabled>

            <updatePolicy>always</updatePolicy>

            <checksumPolicy>fail</checksumPolicy>

        </snapshots>

    </repository>

</repositories>

<dependencies>

    <!-- kafka客户端工具 -->

    <dependency>

        <groupId>org.apache.kafka</groupId>

        <artifactId>kafka-clients</artifactId>

        <version>2.4.1</version>

    </dependency>

    <!-- 工具类 -->

    <dependency>

        <groupId>org.apache.commons</groupId>

        <artifactId>commons-io</artifactId>

        <version>1.3.2</version>

    </dependency>

    <!-- SLF桥接LOG4J日志 -->

    <dependency>

        <groupId>org.slf4j</groupId>

        <artifactId>slf4j-log4j12</artifactId>

        <version>1.7.6</version>

    </dependency>

    <!-- SLOG4J日志 -->

    <dependency>

        <groupId>log4j</groupId>

        <artifactId>log4j</artifactId>

        <version>1.2.16</version>

    </dependency>

</dependencies>

<build>

    <plugins>

        <plugin>

            <groupId>org.apache.maven.plugins</groupId>

            <artifactId>maven-compiler-plugin</artifactId>

            <version>3.7.0</version>

            <configuration>

                <source>1.8</source>

                <target>1.8</target>

            </configuration>

        </plugin>

    </plugins>

</build>

4.2 导入log4j.properties

将log4j.properties配置文件放入到resources文件夹中

log4j.rootLogger=INFO,stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%5p - %m%n

4.3 代码开发

可以参考以下方式来编写第一个Kafka示例程序

参考以下文档:kafka 2.4.0 API

下面是一个发送生产者消息到kafka中的示例

public class KafkaProducerTest {

    public static void main(String[] args) {

        // 1. 创建用于连接KafkaProperties配置

        Properties props = new Properties();

        props.put("bootstrap.servers", "192.168.88.100:9092");

        props.put("acks", "all");

        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        // 2. 创建一个生产者对象KafkaProducer

        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

        // 3. 调用send发送1-100消息到指定Topic test

        for(int i = 0; i < 100; ++i) {

            try {

                // 获取返回值Future,该对象封装了返回值

                Future<RecordMetadata> future = producer.send(new ProducerRecord<String, String>("test", null, i + ""));

                // 调用一个Future.get()方法等待响应

                future.get();

            } catch (InterruptedException e) {

                e.printStackTrace();

            } catch (ExecutionException e) {

                e.printStackTrace();

            }

        }

        // 5. 关闭生产者

        producer.close();

    }

}

 从kafka中的topic中消费消息参考代码如下

public class KafkaProducerTest {

    public static void main(String[] args) {

        // 1. 创建用于连接KafkaProperties配置

        Properties props = new Properties();

        props.put("bootstrap.servers", "node1.itcast.cn:9092");

        props.put("acks", "all");

        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        // 2. 创建一个生产者对象KafkaProducer

        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

        // 3. 调用send发送1-100消息到指定Topic test

        for(int i = 0; i < 100; ++i) {

            try {

                // 获取返回值Future,该对象封装了返回值

                Future<RecordMetadata> future = producer.send(new ProducerRecord<String, String>("test", null, i + ""));

                // 调用一个Future.get()方法等待响应

                future.get();

            } catch (InterruptedException e) {

                e.printStackTrace();

            } catch (ExecutionException e) {

                e.printStackTrace();

            }

        }

        // 5. 关闭生产者

        producer.close();

    }

}

参考官网API文档:

kafka 2.4.0 API

 异步使用带有回调函数方法生产消息

public class KafkaProducerTest {

    public static void main(String[] args) {

        // 1. 创建用于连接KafkaProperties配置

        Properties props = new Properties();

        props.put("bootstrap.servers", "node1.itcast.cn:9092");

        props.put("acks", "all");

        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        // 2. 创建一个生产者对象KafkaProducer

        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);

        // 3. 调用send发送1-100消息到指定Topic test

        for(int i = 0; i < 100; ++i) {

            // 一、同步方式

            // 获取返回值Future,该对象封装了返回值

            // Future<RecordMetadata> future = producer.send(new ProducerRecord<String, String>("test", null, i + ""));

            // 调用一个Future.get()方法等待响应

            // future.get();

            // 二、带回调函数异步方式

            producer.send(new ProducerRecord<String, String>("test", null, i + ""), new Callback() {

                @Override

                public void onCompletion(RecordMetadata metadata, Exception exception) {

                    if(exception != null) {

                        System.out.println("发送消息出现异常");

                    }

                    else {

                        String topic = metadata.topic();

                        int partition = metadata.partition();

                        long offset = metadata.offset();

                        System.out.println("发送消息到Kafka中的名字为" + topic + "的主题,第" + partition + "分区,第" + offset + "条数据成功!");

                    }

                }

            });

        }

        // 5. 关闭生产者

        producer.close();

    }

}

五、kafka的架构 

 5.1 kafka的基本概念

  5.1.1 broker

  1.  一个Kafka的集群通常由多个broker组成,这样才能实现负载均衡、以及容错
  2.  broker是无状态(Sateless)的,它们是通过ZooKeeper来维护集群状态
  3.  一个Kafka的broker每秒可以处理数十万次读写,每个broker都可以处理TB消息而不影响性能

 5.1.2 zookeeper

  1. ZK用来管理和协调broker,并且存储了Kafka的元数据(例如:有多少topic、partition、consumer)
  2.  ZK服务主要用于通知生产者和消费者Kafka集群中有新的broker加入、或者Kafka集群中出现故障的broker。

PS:Kafka正在逐步想办法将ZooKeeper剥离,维护两套集群成本较高,社区提出KIP-500就是要替换掉ZooKeeper的依赖。“Kafka on Kafka”——Kafka自己来管理自己的元数据

5.1.3 producer(生产者)

  1.  生产者负责将数据推送给broker的topic

 5.1.4 consumer(消费者)

  1.  消费者负责从broker的topic中拉取数据,并自己进行处理

5.1.5 consumer group (消费者组)

  1.  consumer group是kafka提供的可扩展且具有容错性的消费者机制
  2.  一个消费者组可以包含多个消费者
  3.  一个消费者组有一个唯一的ID(group Id)
  4.  组内的消费者一起消费主题的所有分区数据

5.1.6 分区(Partitions) 

  1.  在Kafka集群中,主题被分为多个分区

5.1.7 副本(Replicas) 

  1.  副本可以确保某个服务器出现故障时,确保数据依然可用 
  2.  在Kafka中,一般都会设计副本的个数>1(分区本身也称为一个副本)

5.1.8 主题(Topic)

  1.  主题是一个逻辑概念,用于生产者发布数据,消费者拉取数据
  2.  Kafka中的主题必须要有标识符,而且是唯一的,Kafka中可以有任意数量的主题,没有数量上的限制
  3.  在主题中的消息是有结构的,一般一个主题包含某一类消息
  4.  一旦生产者发送消息到主题中,这些消息就不能被更新(更改)

6.1.9 offset(偏移量) 

  1. offset记录着下一条将要发送给Consumer的消息的序号
  2.  默认Kafka将offset存储在ZooKeeper中
  3.  在一个分区中,消息是有顺序的方式存储着,每个在分区的消费都是有一个递增的id。这个就是偏移量offset
  4.  偏移量在分区中才是有意义的。在分区之间,offset是没有任何意义的

5.2 消费者组 

Kafka支持有多个消费者同时消费一个主题中的数据。同一个消费者组里的消费者不能消费同一个分区里的数据,如果需要两个消费者消费数据,那么至少需要两个分区存放生产者的消息

六、kafka的幂等性和事务

6.1 幂等性

       拿http举例来说,一次或多次请求,得到地响应是一致的(网络超时等问题除外),换句话说,就是执行多次操作与执行一次操作的影响是一样的。如果,某个系统是不具备幂等性的,如果用户重复提交了某个表格,就可能会造成不良影响。例如:用户在浏览器上点击了多次提交订单按钮,会在后台生成多个一模一样的订单。

       在生产者生产消息时,如果出现retry时,有可能会一条消息被发送了多次,如果Kafka不具备幂等性的,就有可能会在partition中保存多条一模一样的消息。

6.1.1 配置幂等性

props.put("enable.idempotence",true);

6.1.2 kafka实现幂等性原理 

为了实现生产者的幂等性,Kafka引入了 Producer ID(PID)和 Sequence Number的概念。

 PID:每个Producer在初始化时,都会分配一个唯一的PID,这个PID对用户来说,是透明的。

 Sequence Number:针对每个生产者(对应PID)发送到指定主题分区的消息都对应一个从0开始递增的Sequence Number。

   生产者向broker中发送消息时会携带pid 和Sequence Number,在数据保存报分区里时,会将分区里的Sequence Number和发送过来的Sequence Number进行比对,如果发送过来的Sequence Number已经在分区里面存在了,表示已经存储过了就不会再次存储,从而实现幂等性 

 6.2 Kafka事务

Kafka事务是2017年Kafka 0.11.0.0引入的新特性。类似于数据库的事务。Kafka事务指的是生产者生产消息以及消费者提交offset的操作可以在一个原子操作中,要么都成功,要么都失败。尤其是在生产者、消费者并存时,事务的保障尤其重要。(consumer-transform-producer模式)

Producer接口中定义了以下5个事务相关方法:

  1. initTransactions(初始化事务):要使用Kafka事务,必须先进行初始化操作
  2. beginTransaction(开始事务):启动一个Kafka事务
  3. sendOffsetsToTransaction(提交偏移量):批量地将分区对应的offset发送到事务中,方便后续一块提交
  4. commitTransaction(提交事务):提交事务
  5. abortTransaction(放弃事务):取消事务

 开启事务的配置:

生产者需要配置事务id

// 配置事务的id,开启了事务会默认开启幂等性

props.put("transactional.id", "first-transactional");

消费者需要设置隔离级别和关闭自动提交

// 1. 消费者需要设置隔离级别

props.put("isolation.level","read_committed");

//  2. 关闭自动提交

 props.put("enable.auto.commit", "false");

6.2.1 Kafka事务编程 

需求:

在Kafka的topic 「ods_user」中有一些用户数据,数据格式如下:

姓名,性别,出生日期

张三,1,1980-10-09

李四,0,1985-11-01

我们需要编写程序,将用户的性别转换为男、女(1-男,0-女),转换后将数据写入到topic 「dwd_user」中。要求使用事务保障,要么消费了数据同时写入数据到 topic,提交offset。要么全部失败。

6.2.1.1 创建生产者和消费者

# 创建名为ods_user和dwd_user的主题

bin/kafka-topics.sh --create --bootstrap-server node1.itcast.cn:9092 --topic ods_user

bin/kafka-topics.sh --create --bootstrap-server node1.itcast.cn:9092 --topic dwd_user

# 生产数据到 ods_user

bin/kafka-console-producer.sh --broker-list node1.itcast.cn:9092 --topic ods_user

# 从dwd_user消费数据

bin/kafka-console-consumer.sh --bootstrap-server node1.itcast.cn:9092 --topic dwd_user --from-beginning  --isolation-level read_committed

6.2.1.2 编写消费者代码

  1. 创建Kafka消费者配置

 Properties props = new Properties();

 props.setProperty("bootstrap.servers", "node1.itcast.cn:9092");

 props.setProperty("group.id", "ods_user");

 props.put("isolation.level","read_committed");

 props.setProperty("enable.auto.commit", "false");

 props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

   2.创建消费者,并订阅 ods_user 主题

   // 1. 创建消费者

    public static Consumer<String, String> createConsumer() {

        // 1. 创建Kafka消费者配置

        Properties props = new Properties();

        props.setProperty("bootstrap.servers", "node1.itcast.cn:9092");

        props.setProperty("group.id", "ods_user");

        props.put("isolation.level","read_committed");

        props.setProperty("enable.auto.commit", "false");

        props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        // 2. 创建Kafka消费者

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

        // 3. 订阅要消费的主题

        consumer.subscribe(Arrays.asList("ods_user"));

        

        return consumer;

6.2.1.3 编写生产者代码

1.创建生产者配置

Properties props = new Properties();

props.put("bootstrap.servers", "node1.itcast.cn:9092");

props.put("transactional.id", "dwd_user");

props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");

props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

 2.创建生产者对象 

public static Producer<String, String> createProduceer() {

        // 1. 创建生产者配置

        Properties props = new Properties();

        props.put("bootstrap.servers", "node1.itcast.cn:9092");

        props.put("transactional.id", "dwd_user");

        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);

        return producer;

 6.2.1.4 编写代码消费并生产数据

实现步骤:
1.调用之前实现的方法,创建消费者、生产者对象
2.生产者调用initTransactions初始化事务
3.编写一个while死循环,在while循环中不断拉取数据,进行处理后,再写入到指定的topic
(1)生产者开启事务
(2)消费者拉取消息
(3)遍历拉取到的消息,并进行预处理(将1转换为男,0转换为女)
(4)生产消息到dwd_user topic中
(5)提交偏移量到事务中
(6)提交事务
(7)捕获异常,如果出现异常,则取消事务

public static void main(String[] args) {
        Consumer<String, String> consumer = createConsumer();
        Producer<String, String> producer = createProducer();
        // 初始化事务
        producer.initTransactions();

        while(true) {
            try {
                // 1. 开启事务
                producer.beginTransaction();
                // 2. 定义Map结构,用于保存分区对应的offset
                Map<TopicPartition, OffsetAndMetadata> offsetCommits = new HashMap<>();
                // 2. 拉取消息
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(2));
                for (ConsumerRecord<String, String> record : records) {
                    // 3. 保存偏移量
                    offsetCommits.put(new TopicPartition(record.topic(), record.partition()),
                            new OffsetAndMetadata(record.offset() + 1));
                    // 4. 进行转换处理
                    String[] fields = record.value().split(",");
                    fields[1] = fields[1].equalsIgnoreCase("1") ? "男":"女";
                    String message = fields[0] + "," + fields[1] + "," + fields[2];
                    // 5. 生产消息到dwd_user
                    producer.send(new ProducerRecord<>("dwd_user", message));
                }
                // 6. 提交偏移量到事务
                producer.sendOffsetsToTransaction(offsetCommits, "ods_user");
                // 7. 提交事务
                producer.commitTransaction();
            } catch (Exception e) {
                // 8. 放弃事务
                producer.abortTransaction();
            }
        }
    }

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值