分布式消息队列kafka

前言

什么是Kafka?

Kafka 是 由Apache 软件基金会开发一个开源 高吞吐量分布式 发布-订阅 消息系统,由 Scala 写成,具有 高性能、持久化、多副本备份、横向扩展的特点。Kafka 最初是由 LinkedIn 公司开发,用作 LinkedIn 的活动流(Activity Stream)和运营数据处理管道(Pipeline)的基础,现在它已被多家不同类型的公司作为多种类型的数据管道和消息系统使用。

Kafka 对消息保存时根据 Topic 进行归类,发送消息 者称为 Producer,消息接受者称为 Consumer,生产者往队列里写消息,消费者从队列里取消息进行业务逻辑处理。 Kafka构建在ZooKeeper同步服务之上。它与Apache Storm和Spark非常好地集成,用于实时流式数据分析。

Kafka的特性

  • 高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒,每个topic可以分多个partition, consumer group 对partition进行consume操作。
  • 可扩展性:kafka集群支持热扩展,无需停机
  • 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失。通过O(1)的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能。
  • 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
  • 高并发:支持数千个客户端同时读写
  • 轻量级,支持实时数据处理和离线数据处理两种方式

Kafka的意义

kafka 之所以受到越来越多的青睐,与它所扮演的三大角色是分不开的的:

  • 消息系统:kafka 与传统的消息中间件都具备系统解耦、冗余存储、流量削峰、缓冲、异步通信、扩展性、可恢复性等功能。与此同时,kafka 还提供了大多数消息系统难以实现的消息顺序性保障及回溯性消费的功能。
  • 存储系统:kafka 把消息持久化到磁盘,相比于其他基于内存存储的系统而言,有效的降低了消息丢失的风险。这得益于其消息持久化和多副本机制。也可以将 kafka 作为长期的存储系统来使用,只需要把对应的数据保留策略设置为“永久”或启用主题日志压缩功能。
  • 流式处理平台:kafka 为流行的流式处理框架提供了可靠的数据来源,还提供了一个完整的流式处理框架,比如窗口、连接、变换和聚合等各类操作。

Kafka工作原理

kafka架构介绍

一个典型的 Kafka 包含若干 Producer、若干 Broker、若干 Consumer 以及一个 Zookeeper 集群。Zookeeper 是 Kafka 用来负责集群元数据管理、控制器选举等操作的。Producer 是负责将消息发送到 Broker 的,Broker 负责将消息持久化到磁盘,而 Consumer 是负责从 Broker 订阅并消费消息。

Kafka架构是一种生产者生产消息、kafka集群、消费者获取消息的架构,如下图:

Kafka的集群图。

img

Producer

producer生产消息需要如下参数:

  • topic:往哪个topic生产消息。
  • partition:往哪个partition生产消息。
  • key:根据该key将消息分区到不同partition。
  • message:消息。

Broker

一个 Kafka 服务器也称为 Broker,它接受生产者发送的消息并存入磁盘;Broker 同时服务消费者拉取分区消息的请求,返回目前已经提交的消息。使用特定的机器硬件,一个 Broker 每秒可以处理成千上万的分区和百万量级的消息。

若干个 Broker 组成一个 集群Cluster),其中集群内某个 Broker 会成为集群控制器(Cluster Controller),它负责管理集群,包括分配分区到 Broker、监控 Broker 故障等。在集群内,一个分区由一个 Broker 负责,这个 Broker 也称为这个分区的 Leader;当然一个分区可以被复制到多个 Broker 上来实现冗余,这样当存在 Broker 故障时可以将其分区重新分配到其他 Broker 来负责。

Consumer

每个consumer属于一个consumer group。在kafka中,一个partition的消息只会被group中的一个consumer消费;可以认为一个group就是一个“订阅者”。一个Topic中的每个partition只会被一个“订阅者”中的一个consumer消费。

Patition

消息发送时都被发送到一个 topic,其本质就是一个目录,而 topic 即是由一些 Partition Logs(分区日志)组成。每个 Partition 中的消息都是 有序 的,生产的消息被不断追加到 Partition log 上,其中的每一个消息都被赋予了一个唯一的 offset 值。

Zookeeper

kafka集群几乎不需要维护任何Consumer和Producer的信息。这些信息由Zookeeper保存。发送到Topic的消息,只会被订阅此Topic的每个group中的一个consumer消费。

kafka工作流程

发布-订阅消息的工作流程

以下是Pub-Sub消息的逐步工作流程:

  • 生产者定期向主题发送消息。

  • Kafka代理存储为该特定主题配置的分区中的所有消息。 它确保消息在分区之间平等共享。 如果生产者发送两个消息并且有两个分区,Kafka将在第一分区中存储一个消息,在第二分区中存储第二消息。

  • 消费者订阅特定主题。

  • 一旦消费者订阅主题,Kafka将向消费者提供主题的当前偏移,并且还将偏移保存在Zookeeper系综中。

  • 消费者将定期请求Kafka(如100 Ms)新消息。

  • 一旦Kafka收到来自生产者的消息,它将这些消息转发给消费者。

  • 消费者将收到消息并进行处理。

  • 一旦消息被处理,消费者将向Kafka代理发送确认。

  • 一旦Kafka收到确认,它将偏移更改为新值,并在Zookeeper中更新它。 由于偏移在Zookeeper中维护,消费者可以正确地读取下一封邮件,即使在服务器暴力期间。

  • 以上流程将重复,直到消费者停止请求。

  • 消费者可以随时回退/跳到所需的主题偏移量,并阅读所有后续消息。

队列消息/用户组的工作流

在队列消息传递系统而不是单个消费者中,具有相同 "组ID "的一组消费者将订阅主题。 简单来说,订阅具有相同 " Group ID "的主题的消费者被认为是单个组,并且消息在它们之间共享。

实际工作流程:

  • 生产者以固定间隔向某个主题发送消息。

  • Kafka存储在为该特定主题配置的分区中的所有消息,类似于前面的方案。

  • 单个消费者订阅特定主题,假设
    " Topic-01 "的
    " Group ID "为
    " Group-1 "。

  • Kafka以与发布 - 订阅消息相同的方式与消费者交互,直到新消费者以相同的
    "组ID "订阅相同主题
    " Topic-01 " 1 。

  • 一旦新消费者到达,Kafka将其操作切换到共享模式,并在两个消费者之间共享数据。 此共享将继续,直到用户数达到为该特定主题配置的分区数。

  • 一旦消费者的数量超过分区的数量,新消费者将不会接收任何进一步的消息,直到现有消费者取消订阅任何一个消费者。 出现这种情况是因为Kafka中的每个消费者将被分配至少一个分区,并且一旦所有分区被分配给现有消费者,新消费者将必须等待。

  • 此功能也称为"使用者组"。 同样,Kafka将以非常简单和高效的方式提供两个系统中最好的。

kafka的使用与实践

Kafka 安装

Linux 系统下安装好 jdk 1.8 以上版本,正确配置环境变量 Linux 系统下安装好 scala 2.11 版本

下载地址:http://kafka.apache.org/downloads.html

安装完成后启动并创建消息队列:

kafka-topics.sh --create --zookeeper 192.168.1.111:2181 --topic test --replication-factor 1 --partitions 1

使用springboot集成

1.导入pom依赖
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>2.1.1</version>
</dependency>
2.创建生产者
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;

public class MyProducer {
    public static void main(String[] args) {
        // 定义配置信息
        Properties prop = new Properties();
        // kafka地址,多个地址用逗号分割  "your_kafka_ip:9092,192.168.23.77:9092"
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.56.137:9092");
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class);
        KafkaProducer<String,String> prod = new KafkaProducer<String, String>(prop);

        // 发送消息
        try {
            for(int i=0;i<10;i++) {
                // 生产者记录消息
                ProducerRecord<String, String> pr = new ProducerRecord<String, String>("test", "hello world"+i);
                prod.send(pr);
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            prod.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.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

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

public class MyConsumer {
    public static void main(String[] args) {
        Properties prop = new Properties();
        prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "your_kafka_ip:9092");
        prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);

        prop.put("session.timeout.ms", "30000");
        //消费者是否自动提交偏移量,默认是true 避免出现重复数据 设为false
        prop.put("enable.auto.commit", "false");
        prop.put("auto.commit.interval.ms", "1000");
        //auto.offset.reset 消费者在读取一个没有偏移量的分区或者偏移量无效的情况下的处理
        //earliest 在偏移量无效的情况下 消费者将从起始位置读取分区的记录
        //latest 在偏移量无效的情况下 消费者将从最新位置读取分区的记录
        prop.put("auto.offset.reset", "earliest");

        // 设置组名
        prop.put(ConsumerConfig.GROUP_ID_CONFIG, "group");

        KafkaConsumer<String, String> con = new KafkaConsumer<String, String>(prop);

        con.subscribe(Collections.singletonList("test"));

        while (true) {
            ConsumerRecords<String, String> records = con.poll(Duration.ofSeconds(100));
            for (ConsumerRecord<String, String> rec : records) {
                System.out.println(String.format("offset:%d,key:%s,value:%s", rec.offset(), rec.key(), rec.value()));

            }
        }
    }
}
4.application.yml 详细配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource    # 配置当前要使用的数据源的操作类型
    driver-class-name: com.mysql.jdbc.Driver      # 配置MySQL的驱动程序类
    url: jdbc:mysql://your_ip:3306/kafka?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8          # 数据库连接地址
    username: kafka                                  # 数据库用户名
    password: 123456                            # 数据库连接密码
    dbcp2:                                          # 进行数据库连接池的配置
      min-idle: 5                                   # 数据库连接池的最小维持连接数
      initial-size: 5                               # 初始化提供的连接数
      max-total: 5                                  # 最大的连接数
      max-wait-millis: 200                          # 等待连接获取的最大超时时间
  kafka:
    # 以逗号分隔的地址列表,用于建立与Kafka集群的初始连接(kafka 默认的端口号为9092)
    bootstrap-servers: your_ip:9092
    producer:
      # 发生错误后,消息重发的次数。
      retries: 0
      #当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。
      batch-size: 16384
      # 设置生产者内存缓冲区的大小。
      buffer-memory: 33554432
      # 键的序列化方式
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      # 值的序列化方式
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      # acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。
      # acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
      # acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
      acks: 1
    consumer:
      # 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式,如1S,1M,2H,5D
      auto-commit-interval: 1S
      # 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
      # latest(默认值)在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的记录)
      # earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
      auto-offset-reset: earliest
      # 是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量
      enable-auto-commit: false
      # 键的反序列化方式
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      # 值的反序列化方式
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    listener:
      # 在侦听器容器中运行的线程数。
      concurrency: 5
      #listner负责ack,每调用一次,就立即commit
      ack-mode: manual_immediate
server:
  port: 8080

#  mybatis.config-location=classpath:mybatis/mybatis-config.xml
#  mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
mybatis:
  mapper-locations: classpath:mapper/*.xml

logging:
  level:
    com.xy.kafka : debug
  • 22
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 37
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 37
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值