kafka

kafka

github

中文文档

docker

docker cluster

# docker-compose.yml  因为consumer-group只能是集群的方式
version: '3.3'
services:
  zookeeper:
    image: wurstmeister/zookeeper
    container_name: zookeeper
    ports:
      - 2181:2181
    volumes:
      - /d/dockerFile/kafka_cluster/zookeeper/data:/data
      - /d/dockerFile/kafka_cluster/zookeeper/datalog:/datalog
      - /d/dockerFile/kafka_cluster/zookeeper/logs:/logs
    restart: always
  kafka1:
    image: wurstmeister/kafka
    depends_on:
      - zookeeper
    container_name: kafka1
    ports:
      - 9092:9092
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:9092
      KAFKA_LISTENERS: PLAINTEXT://kafka1:9092
      KAFKA_LOG_DIRS: /data/kafka-data
      KAFKA_LOG_RETENTION_HOURS: 24
    volumes:
      - /d/dockerFile/kafka_cluster/kafka1/data:/data/kafka-data
    restart: unless-stopped  
  kafka2:
    image: wurstmeister/kafka
    depends_on:
      - zookeeper
    container_name: kafka2
    ports:
      - 9093:9093
    environment:
      KAFKA_BROKER_ID: 2
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka2:9093
      KAFKA_LISTENERS: PLAINTEXT://kafka2:9093
      KAFKA_LOG_DIRS: /data/kafka-data
      KAFKA_LOG_RETENTION_HOURS: 24
    volumes:
      - /d/dockerFile/kafka_cluster/kafka2/data:/data/kafka-data
    restart: unless-stopped
  kafka3:
    image: wurstmeister/kafka
    depends_on:
      - zookeeper
    container_name: kafka3
    ports:
      - 9094:9094
    environment:
      KAFKA_BROKER_ID: 3
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka3:9094
      KAFKA_LISTENERS: PLAINTEXT://kafka3:9094
      KAFKA_LOG_DIRS: /data/kafka-data
      KAFKA_LOG_RETENTION_HOURS: 24
    volumes:
      - /d/dockerFile/kafka_cluster/kafka3/data:/data/kafka-data
    restart: unless-stopped

commond

topic

# create topic
/opt/bitnami/kafka/bin/kafka-topics.sh --create --bootstrap-server kafka_container:9092 --replication-factor 1 --partitions 1 --topic test

# list topic
/opt/bitnami/kafka/bin/kafka-topics.sh --bootstrap-server kafka_container:9092 --list

# delete topic
/opt/bitnami/kafka/bin/kafka-topics.sh --delete --bootstrap-server kafka_container:9092 --topic test

create product console

/opt/bitnami/kafka/bin/kafka-console-producer.sh --bootstrap-server kafka_container:9092 --topic test

create consumer console

/opt/bitnami/kafka/bin/kafka-console-consumer.sh --bootstrap-server kafka_container:9092 --topic test --from-beginning

broker

集群中的每个kafka服务就是一个broker

在这里插入图片描述

topic主题

topic

topic是一类待消费信息的集合,是逻辑上的划分。例如订单消息的集合

partition分区

每个topic可以划分为多个分区(物理划分,对应在磁盘上的不同文件夹讷),生成者推送的信息会随机分配到此topic下的不通分区。每个消息在被添加到分区时,都会被分配一个 offset,它是消息在此分区中的唯一编号,Kafka 通过 offset 保证消息在分区内的顺序,offset 的顺序不跨分区,即 Kafka 只保证在同一个分区内的消息是有序的

producer生成者

生产者发布流程

在这里插入图片描述

我们从创建 ProducerRecord 对象开始, ProducerRecord 对象需要包含目标主题和要发

的内容。我们还可以指定键或分区。在发送 Producer ecord 对象时,生产者要先把键和

值对象序列化成字节数组,这样它们才能够在网络上传输

接下来,数据被传给分区器。如果之前在 Produc rR cord 对象里指定了分区,那么分区器

就不会再做任何事情,直接把指定的分区返回。如果没有指定分区 ,那么分区器会根据

Prod uc巳rRecord 对象的键来选择 个分区。选好分区以后 ,生产者就知道该往哪个主题和

分区发送这条记录了。紧接着,这条记录被添加到 个记录批次里,这个批次里的所有消

息会被发送到相同的主题和分区上。有 个独立的线程负责把这些记录批次发送到相应的

broker 上。

服务器在收到这些消息时会返回一个响应。如果消息成功写入 Kafka ,就返回

RecordMetaData 对象,它包含了主题和分区信息,以及记录在分区里的偏移量。如果写入

失败, lj 会返回 个错误。生产者在收到错误之后会尝试重新发送消息,几次之后如果还

是失败, 就返回错误信息。

生产者发布方式

发送消息主要有以下 种方式。

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

我们把消息发送给服务器,但井不关 它是否正常到达。大多数情况下,消息会正常到

达,因为 fk 是高可用的,而且生产者会自动尝试重发。不过,使用这种方式有时候

也会丢失 些消息。

  • 同步发送

使用 send () 方怯发送消息 它会返回 Future 对象,调用 get () 方法进行等待

就可以知道悄息是否发送成功。

  • 异步发送

我们调用 send () 方怯,并指定 个回调函数, 务器在返回响应时调用该函数。

生产者属性

配置文件/path/to/config/producer.properties

必选属性

  • bootstrap.servers:brokers列表,host:port格式
  • key.serializer:消息key序列化方式
  • value.serializer:消息value序列化方式

重要的属性

  • acks:指定多少个分区副本收到消息,生产者才认为消息写入成功
    • acks == 0, 不需要回复,消息丢失也不知道
    • acks == 1, 首领节点收到,就会返回成功写入
    • acks == all, 所有节点返回成功,则告诉生产者写入成功
  • buffer.memory:该参数用来设置生产者内存缓冲区的大小,生产者用它缓冲要发送到服务器的消息
  • compression.type:消息压缩方式(snappy,,gzip, lz4)
  • retrires:重复发送次数
  • bath.size:当有多个消息需要被发送到同 个分区时,生产者会把它们放在一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算(而不是消息个数)。

kafka序列化方式

Avro

consumer消费者

消费者

消费者有两种获取数据的方式 pull, push

  • pull方式:消费者主动向broker拉取数据(一般都是这种)

    • 好处:
      • 消费者速率不同时,当消费者还为处理完当前消息,可以避免broker推消息导致网络拥堵
      • 速率根据消费者的节奏掌控
  • push方式:broker主动推消息给消费者

    • 好处

消费者组

  • 每个消费者组有1-n个消费者
  • 每个消费者组有一个字符串唯一标识group.id
  • 一个分区(partition)只能被消费者组中的某一个消费者消费,当消费组中的消费者多于分区时,多于的消费者会处于闲置状态

在这里插入图片描述

消费者群组和分区再平衡(rebalance)

分区的所有权从 个消费者转移到另 个消费者,这样的行为被称为再均衡rebalance。再均衡非常重要, 它为 肖费者群组带来了高可用性和伸缩性(我们可以放心地添加或移除梢费者),不过在正常情况下,我们并不希望发生这样的行为。在再均衡期间,消费者无法读取消息,造成整个群组 小段时间的不可用。另外,当分区被重新分配给另 个消费者时,消费者当前的读取状态会丢失,它有可能还需要去刷新缓存,在它重新恢复状态之前会拖慢应用程序。

创建消费者

创建消费者的3个必要属性:

  • bootstrap.servers:集群连接字符串,broker列表,host:port

  • key.deserializer:键的反序列化方法

  • value.deserializer:值得反序列化方法

  • group.id:如果是消费者组是必要属性,指定消费组的编号

订阅主题

  • 订阅单个主题
  • 正则匹配多个主题

问题:订阅多个主题时,如何管理offset

轮询loop

消息轮询是消费者API的核心,通过一个简单的轮询向服务器请求数据。一旦消费者订阅了主题,轮询就会处理所有的细节,包括:群组协调,分区再平衡,发送心跳和获取数据

消费者重要的配置

配置文件/path/to/config/consumer.properties

  • fetch.min.bytes:该属性指定了消费者’从服务器获取记录的最小字节数。 broker 在收到消费者的数据请求时,如果可用的数据量小于fetch.min.bytes指定的大小,那么它会等到有足够的可用数据时才把它返回给消费者。这样可以降低消费者和 broker 的工作负载,

https://blog.csdn.net/qq_31617409/article/details/82317267

提交commit和偏移量offset

消费者可以使用kafka来追踪消息在分区的位置(offset),更新分区当前位置的操作叫做提交

消费者往一个叫做_consumer_offset的特殊主题发送消息,消息里包含了每个分区的偏移量。当消费者崩溃或者有新的消费者加入时,会发生rebalance,为了能够继续之前的工作,消费者要读取每个分区最后一次提交的偏移量,然后从这个偏移量继续处理

在这里插入图片描述

在这里插入图片描述

通过上图会发现处理偏移量的方式对客户端有很多影响,所以kafka consumer API提供了很多方式来提交偏移量

自动提交auto commit

设置enable.auto.commit = true, 每隔5s poll()轮询会自动提交接收到的最大偏移量。

设置auto.commit.interval.ms控制自动提交时间、

自动提交并不知道哪些消息被处理,哪些处理成功

提交当前偏移量

消费者 API 提供了另一种提交偏移量的方式 开发者可 要的时候提交当前偏移量,而不是基于时间间隔。

设置enable.auto.commit = false让应用程序决定 时提交 移量。使用 commit.Sync()提交偏移量最简单 最可靠。这个 PI 会提交由 poll () 方能返回 最新偏移量,提交成功后马上返回,如果提交失败就抛出异常。

异步提交当前偏移量

手动提交有一个不足之处,在 broker 对提交请求作出回应之前,应用程序会 直阻塞,这样会限制应用程序的吞吐量。可以通过降低提交频率来提升吞吐盆,但如果发生了均衡, 会增 重复消息的数量。

这个时候可以使用异步提交 PI 。我 只管发送提交请求,无需等待 broker 的响应。

同步异步组合提交

一般情况下,针对偶尔出现的提交失败,不进行重试不会有太大问题,因为如果提交失败是因为临时问题导致的,那么后续的提交总会有成功的。但如果这是发生在关闭消费者或rebalance前的最后 次提交,就要确保能够提交成功。

在这里插入图片描述

go 从未提交处开始消费

package main

import (
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/Shopify/sarama"
)

func main() {
	//flag.Parse()
	//Produce()
	//Consume()
	//GroupConsume()
	OffsetManager("first", 0)	// 如果需要重复消费,需要把resetFlag设置成1,重置offset
}


func OffsetManager(topic string, resetFlag int) {
	config := sarama.NewConfig()
	// 配置开启自动提交 offset,这样 samara 库会定时帮我们把最新的 offset 信息提交给 kafka
	config.Consumer.Offsets.AutoCommit.Enable = true              // 开启自动 commit offset
	config.Consumer.Offsets.AutoCommit.Interval = 5 * time.Second // 自动 commit时间间隔
	config.Consumer.Offsets.Initial = sarama.OffsetOldest		  // 从最老的未提交的消息开始
	client, err := sarama.NewClient([]string{"kafka1:9092"}, config)
	if err != nil {
		log.Fatal("NewClient err: ", err)
	}
	defer client.Close()

	// offsetManager 用于管理每个 consumerGroup的 offset
	// 根据 groupID 来区分不同的 consumer,注意: 每次提交的 offset 信息也是和 groupID 关联的
	offsetManager, _ := sarama.NewOffsetManagerFromClient("myGroupID", client) // 偏移量管理器
	defer offsetManager.Close()

	var wg sync.WaitGroup

	for i:=0;i<3;i++ {
		wg.Add(i)
		go func(i int32) {

			// 每个分区的 offset 也是分别管理的,demo 这里使用 0 分区,因为该 topic 只有 1 个分区
			partitionOffsetManager, _ := offsetManager.ManagePartition(topic, i) // 对应分区的偏移量管理器
			defer partitionOffsetManager.Close()

            // 重置offset
			if resetFlag == 1 {
				partitionOffsetManager.ResetOffset(sarama.OffsetNewest, "")
				nextOffset, _ := partitionOffsetManager.NextOffset() // 取得下一消息的偏移量作为本次消费的起点
				fmt.Printf("reset result:  partition: %d, offset: %d \n", i, nextOffset)
				return
			}
            
			// defer 在程序结束后在 commit 一次,防止自动提交间隔之间的信息被丢掉
			defer offsetManager.Commit()
			consumer, _ := sarama.NewConsumerFromClient(client)
			// 根据 kafka 中记录的上次消费的 offset 开始+1的位置接着消费
			nextOffset, _ := partitionOffsetManager.NextOffset() // 取得下一消息的偏移量作为本次消费的起点
			fmt.Printf("partition: %d, offset: %d \n", i, nextOffset)
			pc, _ := consumer.ConsumePartition(topic, i, nextOffset)
			defer pc.Close()

			for message := range pc.Messages() {
				value := string(message.Value)
				log.Printf("[Consumer] partitionid: %d; offset:%d, value: %s\n", message.Partition, message.Offset, value)
				// 每次消费后都更新一次 offset,这里更新的只是程序内存中的值,需要 commit 之后才能提交到 kafka
				partitionOffsetManager.MarkOffset(message.Offset+1, "modified metadata") // MarkOffset 更新最后消费的 offset
				//if message.Offset < 5 {
				//	offsetManager.Commit()
				//}
				time.Sleep(5 * time.Second)
			}
			wg.Done()
		}(int32(i))
	}
	wg.Wait()
}

在这里插入图片描述

从上图可以看出,kafka记录了 每个topic的分区的 未提交或未生成的 offset 并且对应了 消费者组。这样消费者就可以从未提交的消息数据开始,并且如果有多个消费者组消费一个topic也能互不影响

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值