Kafka详解

一 Kafka介绍

Kafka 是一个分布式流媒体平台

kafka官网:http://kafka.apachecn.org/

(1)流媒体平台有三个关键功能:

  • 发布和订阅记录流,类似于消息队列或企业消息传递系统。
  • 容错的持久方式存储记录流
  • 记录发生时处理流。

(2)Kafka通常用于两大类应用:

  • 构建可在系统或应用程序之间可靠获取数据的实时流数据管道
  • 构建转换或响应数据流的实时流应用程序

在这里插入图片描述

(3)kafka名词解释

  • topic:Kafka将消息分门别类,每一类的消息称之为一个主题(Topic)
  • producer:发布消息的对象称之为主题生产者(Kafka topic producer)
  • consumer:订阅消息并处理发布的消息的对象称之为主题消费者(consumers)
  • broker:已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理(Broker)。 消费者可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。

二 Kafka消息队列:安装和配置

因为Kafka强制依赖了zookeeper,所以要先安装zookeeper,再安装Kafka

1)安装zookeeper

1)下载zookeeper镜像

docker pull zookeeper

2)启动zookeeper容器

docker run -d --name zookeeper -p 2181:2181 -t zookeeper

2)安装Kafka

1)下载Kafka镜像

docker pull wurstmeister/kafka

2)启动Kafka容器

docker run -d --name kafka --publish 9092:9092 --link zookeeper --env KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 --env KAFKA_ADVERTISED_HOST_NAME=192.168.66.133 --env KAFKA_ADVERTISED_PORT=9092 --volume /etc/localtime:/etc/localtime wurstmeister/kafka:latest

12、Kafka消息队列:Kafka入门案例

1)创建工程kafka-demo

创建kafka-demo工程,引入依赖信息

<properties>
    <kafka.client.version>2.0.1</kafka.client.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>${kafka.client.version}</version>
    </dependency>
</dependencies>

做一个java普通的生产者和消费者只需要依赖kafka-clients即可

2)消息生产者

创建类:

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Properties;

/**
 * 消息生产者
 */
public class ProducerQuickStart {

    public static void main(String[] args) {
        //设置连接参数
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.66.133:9092");//服务器地址
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");//消息统一采用字符串序列化方式
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");//消息统一采用字符串序列化方式
        props.put(ProducerConfig.RETRIES_CONFIG,5);//重试次数


        //创建KafkaProducer对象,用于发送消息
        KafkaProducer kafkaProducer = new KafkaProducer(props);

        //创建消息对象
        /**
         * Kafka的消息格式:类似于Map接口
         *    都是key-value对形式
         */
        /**
         * 参数一:主题
         * 参数二:key
         * 参数三:value
         */
        ProducerRecord<String,String> msg = new ProducerRecord<>("hello","1001","hello kafka!");

        //发送消息
        kafkaProducer.send(msg);

        //释放连接
        kafkaProducer.close();


    }
}


3)消息消费者

创建消费者类:


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 org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.time.Duration;
import java.util.Collections;
import java.util.Properties;

/**
 * 消息消费者者
 */
public class ConsumerQuickStart {

    public static void main(String[] args) {
        //设置连接参数
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.66.133:9092");//服务器地址
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");//消息统一采用字符串序列化方式
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");//消息统一采用字符串序列化方式
        props.put(ConsumerConfig.GROUP_ID_CONFIG,"group1");//组名称

        /**
         * RocketMQ的消息模式
         *     messageModel:
         *        BLACADTING 广播   全部消费方同时接收消息
         *        CLUSTING  集群    消息者轮询接收消息
         *
         * Kaffa的消息模式
         *    组名相同,类似于集群模式,只有一个消费者接收到消息(先注册的优先接收)
         *    组名不同,类似于广播  模式,所有消费者同时接收到消息
         */

        //创建KafkaProducer对象,用于发送消息
        KafkaConsumer<String,String> kafkaConsumer = new KafkaConsumer(props);

        //监听主题
        kafkaConsumer.subscribe(Collections.singleton("hello"));

        while(true) {
            ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(5));

            for (ConsumerRecord record : records) {
                Object key = record.key();
                Object value = record.value();
                System.out.println(key + "---" + value);
            }

        }
    }
}


4) 测试

  • 生产者发送消息,同一个组中的多个消费者只能有一个消费者接收消息
  • 生产者发送消息,如果有多个组,每个组中只能有一个消费者接收消息,如果想要实现广播的效果,可以让每个消费者单独有一个组即可,这样每个消费者都可以接收到消息

三、Kafka消息队列:相关概念说明

1)Kafka相关概念

在这里插入图片描述

在kafka概述里介绍了概念包括:topic、producer、consumer、broker,这些是最基本的一些概念,想要更深入理解kafka还要知道它的一些其他概念定义:

  • 消息Message

    Kafka 中的数据单元被称为消息message,也被称为记录,可以把它看作数据库表中某一行的记录。

  • topic

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

  • 批次

    为了提高效率, 消息会分批次写入 Kafka,批次就代指的是一组消息。

  • 分区Partition

    主题可以被分为若干个分区(partition),同一个主题中的分区可以不在一个机器上,有可能会部署在多个机器上,由此来实现 kafka 的伸缩性。topic中的数据分割为一个或多个partition。每个topic至少有一个partition。每个partition中的数据使用多个文件进行存储。partition中的数据是有序的,partition之间的数据是没有顺序的。如果topic有多个partition,消费数据时就不能保证数据的顺序。在需要严格保证消息的消费顺序的场景下,需要将partition数目设为1。

  • broker

    一个独立的 Kafka 服务器就被称为 broker,broker 接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。

  • Broker 集群

    broker 是集群 的组成部分,broker 集群由一个或多个 broker 组成,每个集群都有一个 broker同时充当了集群控制器的角色(自动从集群的活跃成员中选举出来)。

  • 副本Replica(分片)

    Kafka 中消息的备份又叫做 副本(Replica),副本的数量是可以配置的,Kafka 定义了两类副本:领导者副本(Leader Replica) 和 追随者副本(Follower Replica);所有写请求都通过Leader路由,数据变更会广播给所有Follower,Follower与Leader保持数据同步。如果Leader失效,则从Follower中选举出一个新的Leader。当Follower与Leader挂掉、卡住或者同步太慢,leader会把这个follower从ISR列表(保持同步的副本列表)中删除,重新创建一个Follower。

  • Zookeeper

    kafka对与zookeeper是强依赖的,是以zookeeper作为基础的,即使不做集群,也需要zk的支持。Kafka通过Zookeeper管理集群配置,选举leader,以及在Consumer Group发生变化时进行重平衡。

  • 消费者群组Consumer Group

    生产者与消费者的关系就如同餐厅中的厨师和顾客之间的关系一样,一个厨师对应多个顾客,也就是一个生产者对应多个消费者,消费者群组(Consumer Group)指的就是由一个或多个消费者组成的群体。

  • 偏移量Consumer Offset

    偏移量(Consumer Offset)是一种元数据,它是一个不断递增的整数值,用来记录消费者发生重平衡时的位置,以便用来恢复数据。

  • 重平衡Rebalance

    消费者同组内某个消费者实例挂掉后,其他消费者实例自动重新分配订阅主题分区的过程。Rebalance 是 Kafka 消费者端实现高可用的重要手段。

2)生产者详解

(1)发送消息的工作原理

在这里插入图片描述

(2)三种消息发送类型

  • 异步发送并忘记(fire-and-forget)

    把消息发送给服务器,并不关心它是否正常到达,大多数情况下,消息会正常到达,因为kafka是高可用的,而且生产者会自动尝试重发,使用这种方式有时候会丢失一些信息

    //发送消息
    try {
       producer.send(record);
    }catch (Exception e){
        e.printStackTrace();
    }
    

    应用场景:如果业务只关心消息的吞吐量,容许少量消息发送失败,也不关注消息的发送顺序,那么可以使用发送并忘记的方式

  • 同步发送

    使用send()方法发送,它会返回一个Future对象,调用get()方法进行等待,就可以知道消息是否发送成功

    //发送消息
    try {
        RecordMetadata recordMetadata = producer.send(record).get();
        System.out.println(recordMetadata.offset());//获取偏移量
    }catch (Exception e){
        e.printStackTrace();
    }
    

    如果服务器返回错误,get()方法会抛出异常,如果没有发生错误,我们就会得到一个RecordMetadata对象,可以用它来获取消息的偏移量

应用场景: 如果业务要求消息尽可能不丢失且必须是按顺序发送的,那么可以使用同步的方式 ( 只能在一个partation上 )

  • 异步发送+回调

    调用send()方法,并指定一个回调函数,服务器在返回响应时调用函数。如下代码

    //发送消息
    try {
        producer.send(record, new Callback() {
            @Override
            public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                if(e!=null){
                    e.printStackTrace();
                }
                System.out.println(recordMetadata.offset());
            }
        });
    }catch (Exception e){
        e.printStackTrace();
    }
    

    如果kafka返回一个错误,onCompletion()方法会抛出一个非空(non null)异常,可以根据实际情况处理,比如记录错误日志,或者把消息写入“错误消息”文件中,方便后期进行分析。

应用场景: 如果业务需要知道消息发送是否成功,并且对消息的顺序不关心,那么可以用异步+回调的方式来发送消息

(3)参数详解

到目前为止,我们只介绍了生产者的几个必要参数(bootstrap.servers、序列化器等)

生产者还有很多可配置的参数,在kafka官方文档中都有说明,大部分都有合理的默认值,所以没有必要去修改它们,不过有几个参数在内存使用,性能和可靠性方法对生产者有影响

  • acks

    指的是producer的消息发送确认机制

    • acks=0

      生产者在成功写入消息之前不会等待任何来自服务器的响应,也就是说,如果当中出现了问题,导致服务器没有收到消息,那么生产者就无从得知,消息也就丢失了。不过,因为生产者不需要等待服务器的响应,所以它可以以网络能够支持的最大速度发送消息,从而达到很高的吞吐量。

    • acks=1 (默认值)

      只要集群首领节点收到消息,生产者就会收到一个来自服务器的成功响应,如果消息无法到达首领节点,生产者会收到一个错误响应,为了避免数据丢失,生产者会重发消息。

    • acks=all

      只有当所有参与赋值的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应,这种模式是最安全的,它可以保证不止一个服务器收到消息,就算有服务器发生崩溃,整个集群仍然可以运行。不过他的延迟比acks=1时更高。

  • retries 死信队列(Dead Letter Quque)

    生产者从服务器收到的错误有可能是临时性错误,在这种情况下,retries参数的值决定了生产者可以重发消息的次数,如果达到这个次数,生产者会放弃重试返回错误,默认情况下,生产者会在每次重试之间等待100ms

3)消费者详解

(1)消费者工作原理

在这里插入图片描述

(2)其他参数详解

  • enable.auto.commit

    该属性指定了消费者是否自动提交偏移量,默认值是true。为了尽量避免出现重复数据和数据丢失,可以把它设置为false,由自己控制何时提交偏移量。如果把它设置为true,还可以通过配置auto.commit.interval.ms属性来控制提交的频率。

  • auto.offset.reset

    • earliest

      当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费

    • latest(默认值)

      当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据

    • none

      topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常

    • anything else

      向consumer抛出异常

03、Kafka消息队列:SpringBoot集成Kafka(1)

1)环境搭建

(1)pom依赖,最终的依赖信息

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itheima</groupId>
    <artifactId>kafka-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 继承Spring boot工程 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.8.RELEASE</version>
    </parent>
    <properties>
        <kafka.version>2.5.8.RELEASE</kafka.version>
        <kafka.client.version>2.4.1</kafka.client.version>
        <fastjson.version>1.2.58</fastjson.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- kafkfa -->
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
            <version>${kafka.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.kafka</groupId>
                    <artifactId>kafka-clients</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>${kafka.client.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

(2)在resources下创建文件application.yml

server:
  port: 9991
spring:
  application:
    name: kafka-demo
  kafka:
    bootstrap-servers: 192.168.66.133:9092
    producer:
      retries: 10
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
    consumer:
      group-id: test-hello-group
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer

(3)引导类


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class KafkaApplication {

    public static void main(String[] args) {
        SpringApplication.run(KafkaApplication.class,args);
    }
}

2) 消息生产者

新建controller


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @Autowired
    private KafkaTemplate<String,String> kafkaTemplate;
    /**
     * 普通消息发送
     */
    @GetMapping("/hello")
    public String hello(){
        //kafkaTemplate.send("kafka-boot","001","hello spirngboot+kafka!!!");
        kafkaTemplate.send("kafka-boot","hello spirngboot+kafka!!!value.....");
        return "发送成功";
    }

}

3)消息消费者

新建监听类:


import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

/**
 * 消费消费监听
 */
@Component
public class HelloListener {

    /**
     * 监听消息(同时接收key和value)
     */
    /*@KafkaListener(topics = "kafka-boot")
    public void handleMsg(ConsumerRecord<String,String> record){
        String key = record.key();
        String value = record.value();
        System.out.println(key+"===="+value);
    }*/

    /**
     * 监听消息(只接收value)
     */
    @KafkaListener(topics = "kafka-boot")
    public void handleMsg(String value){
        System.out.println(value);
    }
}


4)测试

启动项目访问:http://localhost:9991/hello

控制台打印,效果如下

在这里插入图片描述

04、Kafka消息队列:SpringBoot集成Kafka(2)

目前SpringBoot整合后的Kafka,因为序列化器是StringSerializer,这个时候如果需要传递对象可以有两种方式

方式一:可以自定义序列化器,对象类型众多,这种方式通用性不强,本章节不介绍

方式二:可以把要传递的对象进行转json字符串,接收消息后再转为对象即可,本项目采用这种方式

(1)新建类User


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String username;
    private Integer age;
}

(2)修改消息发送

import com.alibaba.fastjson.JSON;
import com.heima.kafka.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Autowired
    private KafkaTemplate<String,String> kafkaTemplate;

    @GetMapping("/hello")
    public String hello(){
        //第一个参数:topics  
        //第二个参数:消息内容
        //kafkaTemplate.send("kafka-demo","黑马程序员");


        User user = new User();
        user.setUsername("zhangsan");
        user.setAge(18);
        kafkaTemplate.send("hello-itcast", JSON.toJSONString(user));
        return "ok";
    }
}

(4)修改消费者

@Component
public class HelloListener {

    @KafkaListener(topics = {"hello-itcast"})
    public void receiverMessage(ConsumerRecord<?,?> record){
          Object value = record.value();
          User user = JSON.parseObject((String) value, User.class);
          System.out.println(user);
    }
}

测试效果如下:

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值