Kafka消息队列学习笔记2——Kafka入门2

目录

1、Java编程操作Kafka

1.1、同步生产消息到Kafka中

1.1.1、需求

1.1.2、准备工作

1.1.3、生产者程序开发

1.2、从Kafka的topic中消费消息

1.2.1、需求

1.2.2、准备工作

1.2.3、消费者程序开发

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

2、Kafka架构

2.1、Kafka重要概念

2.1.1、broker

2.1.2、zookeeper

2.1.3、producer(生产者)

2.1.4、consumer(消费者)

2.1.5、consumer group(消费者组)

2.1.6、分区(Partitions)

2.1.7、副本(replicas)

2.1.8、主题(topic)

2.1.9、偏移量(offset)

2.2、消费者组

3、Kafka生产者幂等性与事务

3.1、幂等性

3.1.1、简介

3.1.2、Kafka生产者幂等性

3.1.3、配置幂等性

3.1.4、幂等性原理


1、Java编程操作Kafka

1.1、同步生产消息到Kafka中

1.1.1、需求

编写Java程序,将1-100的数字消息写入到Kafka中。

1.1.2、准备工作

1、导入Maven Kafka POM依赖

    <!-- 代码库 -->
    <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 -->
        <!-- 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>

2、导入log4j.properties

将log4j.properties配置文件放入到resource文件件中。

log4j.rootLogger=INFO,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%Sp - %m%n

3、创建包和类

创建cn.itcast.kafka,并创建KafkaProducerTest类。

1.1.3、生产者程序开发

  1. 创建用于连接Kafka的Properties配置;
    创建连接:
        bootstrap.servers:Kafka的服务器地址
        acks:表示当生产者生产数据到kafka中,Kafka中会以什么样的策略返回
        key.serializer:Kafka中的消息是以key、value键值对存储的,而且生产者生产的消息是需要在网络上传递的,这里指定的是StringSerializer方式,就是以字符串方式发送(将来还可以使用其他的一些序列化框架:Google Protobuf、Avro)
        value.serializer:同上
            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;
  3. 调用send()发送1-100消息(ProducerRecord,封装的是key-value键值对)到指定Topic test,并获取返回值Future,该对象封装了返回值;
  4. 再调用一个Future.get()方法表示等待服务端的响应;
  5. 关闭生产者。

参考代码:

/**
 * Kafka的生产者程序
 *  会将消息创建出来,并发送到Kafka集群中
 */
public class KafkaProducerTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1、创建用于连接Kafka的Properties配置
        Properties props = new Properties();
        props.put("bootstrap.servers", "127.0.0.1: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> kafkaProducer = new KafkaProducer<String, String>(props);

        // 3、发送1-100的消息到指定的topic中
        for (int i = 0; i < 100; i++) {
            // 构建一条消息 ProducerRecord
            ProducerRecord<String, String> producerRecord = new ProducerRecord<>("test-topic", null, i + "");
            Future<RecordMetadata> future = kafkaProducer.send(producerRecord);
            // 调用Future.get()方法等待响应
            future.get();
            System.out.println("第" + i + "条消息写入成功!");
        }
        // 4、关闭生产者
        kafkaProducer.close();
    }
}

1.2、从Kafka的topic中消费消息

1.2.1、需求

从test-topic中,将消息都消费,并将记录的offset、key、value打印出来。

1.2.2、准备工作

在cn.itcast包下创建KafkaConsumerTest类。

1.2.3、消费者程序开发

  • group.id:消费者组的概念,可以在一个消费组中包含多个消费者。如果若干个消费者的group.id是一样的,表示它们就在一个组中,一个组中的消费者是共同消费Kafka中topic的数据。
  • Kafka是一种拉消息模式的消息队列,在消费者中会有一个offset,表示从哪条消息开始拉取数据。
  • KafkaConsumer.poll:Kafka的消费者API是一批一批数据的拉取。
- 关闭consumer一直打印debug日志的方式,创建资源文件logback.xml,添加如下内容:

<configuration scan="true" scanPeriod="10 seconds">
    <include resource="org/springframework/boot/logging/logback/base.xml" />

    <!-- 屏蔽kafka debug -->
    <logger name="org.apache.kafka.clients" level="INFO" />
</configuration>

开发步骤:

参考代码:

/**
 * 消费者程序
 */
public class KafkaConsumerTest {
    public static void main(String[] args) {
        // 1、创建Kafka消费者配置
        Properties properties = new Properties();
        properties.setProperty("bootstrap.servers", "127.0.0.1:9092");
        // 消费者组(可以使用消费者组,将若干个消费者组织到一起,共同消费Kafka中topic的数据)
        // 每一个消费者需要指定一个消费者组,如果消费者的组名一样,就表明这几个消费者是一个组的
        properties.setProperty("group.id", "test");
        // 自动提交offset
        properties.setProperty("enable.auto.commit", "true");
        // 自动提交offset的时间间隔
        properties.setProperty("auto.commit.interval.ms", "1000");
        // 拉取的key、value数据的反序列化方式
        properties.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        properties.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        // 2、创建Kafka的消费者
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);

        // 3、订阅要消费的主题
        // 指定消费者从哪个topic中拉取数据
        kafkaConsumer.subscribe(Arrays.asList("test-topic"));

        // 4、使用一个while循环,不断从Kafka的topic中拉取消息
        while(true) {
            // Kafka的消费者一次拉取一批数据
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(5));
            // 5、将记录(ConsumerRecords)的offset、key、value都打印出来
            for(ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                // 主题名字
                String topic = consumerRecord.topic();
                // offset
                long offset = consumerRecord.offset();
                // key / value
                String key = consumerRecord.key();
                String value = consumerRecord.value();

                System.out.println("topic:" + topic + " offset:" + offset + " key:" + key + " value" + value);
            }
        }
    }
}

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

如果我们想获取生产者消息是否成功,或者成功生产消息到Kafka中后,执行一些其它动作。此时,可以很方便地使用带有回调函数来发送消息。

需求:

  • 在发送消息出现异常时,能够及时打印出异常消息;
  • 在发送消息成功时,打印Kafka到的topic名字、分区id、offset。

使用匿名内部类实现Callback接口,该接口中表示Kafka服务器响应给客户端,会自动调用onCompletion()方法:

  • metadata:消息的元数据(属于哪个topic、属于哪个partition、对应的offset是什么);
  • exception:这个对象Kafka生产消息封装了出现的异常,如果为null,表示发送成功,如果不为空,标识出现异常。
/**
 * Kafka的生产者程序
 *  会将消息创建出来,并发送到Kafka集群中
 */
public class KafkaProducerTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1、创建用于连接Kafka的Properties配置
        Properties props = new Properties();
        props.put("bootstrap.servers", "127.0.0.1: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> kafkaProducer = new KafkaProducer<String, String>(props);

        // 3、发送1-100的消息到指定的topic中
        for (int i = 0; i < 100; i++) {
            // 方式1:使用同步等待方式发送消息
//            // 构建一条消息 ProducerRecord
//            ProducerRecord<String, String> producerRecord = new ProducerRecord<>("test-topic", null, i + "");
//            Future<RecordMetadata> future = kafkaProducer.send(producerRecord);
//            // 调用Future.get()方法等待响应
//            future.get();
//            System.out.println("第" + i + "条消息写入成功!");

            // 方式2:使用异步回调的方式发送消息
            ProducerRecord<String, String> producerRecord = new ProducerRecord<>("test-topic", null, i + "");
            kafkaProducer.send(producerRecord, new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    // 1、判断发送消息是否成功
                    if (e == null) {
                        // 发送成功
                        // 主题
                        String topic = recordMetadata.topic();
                        // 分区id
                        int partition = recordMetadata.partition();
                        // 偏移量
                        long offset = recordMetadata.offset();
                        System.out.println("topic:" + topic + " 分区id:" + partition + " 偏移量:" + offset);
                    } else {
                        // 发送失败
                        System.out.println("生产消息出现异常!");
                        // 打印异常消息
                        System.out.println(e.getMessage());
                        // 打印调用栈
                        System.out.println(e.getStackTrace());
                    }
                }
            });
        }
        // 4、关闭生产者
        kafkaProducer.close();
    }
}

2、Kafka架构

2.1、Kafka重要概念

2.1.1、broker

  • Kafka服务器进程,生产者、消费者都要连接broker;
  • 一个Kafka的集群通常由多个braker组成,这样才能实现负载均衡,以及容错;
  • braker是无状态(Stateless)的,它们是通过Zookeeper来维护集群状态;
  • 一个Kafka的broker每秒可以处理数十万次读写,每个broker都可以处理TB消息而不影响性能。

2.1.2、zookeeper

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

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

2.1.3、producer(生产者)

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

2.1.4、consumer(消费者)

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

2.1.5、consumer group(消费者组)

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

2.1.6、分区(Partitions)

在Kafka集群中,主题topic被分为多个分区partition。Kafka集群的分布式就是由分区来实现的,一个topic中的消息可以分布在topic的不同partition中。

2.1.7、副本(replicas)

副本可以确保某个服务出现故障时,确保数据依然可用。在Kafka中,一般都会设计副本的个数>1。

2.1.8、主题(topic)

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

2.1.9、偏移量(offset)

  • offset记录着下一条将要发送给Consumer的消息的序号,相对消费者来说,可以通过offset来拉取数据;
  • 默认Kafka将offset存储在ZooKeeper中;
  • 在一个分区中,消息是有顺序的方式存储着,在每个分区的消费都是有一个递增的id。这个就是偏移量offset;
  • 偏移量在分区中才是有意义的。在分区之间,offset是没有任何意义的。

2.2、消费者组

  • 一个消费者组中可以包含多个消费者,共同来消费topic中的数据;
  • 一个topic中如果只有一个分区,那么这个分区只能被某个组中的一个消费者消费;
  • 有多少个分区,那么就可以被同一个组的多少个消费者消费。

3、Kafka生产者幂等性与事务

3.1、幂等性

3.1.1、简介

拿http举例来说,一次或多次请求,得到的响应是一致的(网络超时等问题除外),换句话说,就是执行多次操作与执行一次操作的影响是一样的。

3.1.2、Kafka生产者幂等性

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

3.1.3、配置幂等性

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

3.1.4、幂等性原理

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

  • PID:每个Producer在初始化时,都会分配一个唯一的PID,这个PID对用户来说,是透明的;
  • Sequence Number:针对每个生产者(对应PID)发送到指定主题分区的消息都是对应一个从0开始递增的Sequence Number。

生产者消息重复问题:Kafka生产者生产消息到partition,如果直接发送消息,Kafka会将消息保存到分区中,但Kafka会返回一个ack给生产者,表示当前操作是否成功,是否已经保存了这条消息,如果ack响应的过程中失败了,此时生产者会重试,继续发送没有发送成功的消息,Kafka又会保存一条一模一样的消息。

在Kafka中可以开启幂等性:当Kafka的生产者生产消息时,会增加一个pid(生产者的唯一编号)和sequence number(针对消息的一个递增序列);发送消息,会连着pid和sequence number一并保存下来;如果ack响应失败,生产者重试,Kafka会根据pid、sequence number是否需要再保存一条消息;判断条件:生产者发送过来的sequence number是否小于等于partition中消息对应的sequence。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值