go操作kafka进行分组消费

同一消费者组

相同点:

所有消费者共享同一个GroupID
消息会被组内消费者分摊消费(负载均衡)
每条消息只会被组内的一个消费者消费一次
消费偏移量在组级别管理

不同点:

消费任务被分配到不同的消费者实例上
提供了并行处理能力和容错性
如果一个消费者失败,其他消费者会接管其分区

不同消费者组

相同点:

每个消费者组都独立消费消息
每个组都可以消费主题中的所有消息

不同点:

各组之间完全独立,互不影响
每个组都有自己的消费偏移量记录
同一条消息会被不同组的消费者重复消费
适用于不同的业务场景(如:一个组用于实时处理,另一个组用于数据分析)
package main

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

	"github.com/segmentio/kafka-go"
)

const (
	kafkaAddr       = "172.17.0.185:9092"
	topicName       = "my-topic"
	totalPartitions = 3 // 分区数量可以通过通过API动态获取
	readDeadline    = 10 * time.Second
	writeDeadline   = 10 * time.Second
)

func main() {
	// 自动创建topic,to create topics when auto.create.topics.enable='true'
	// conn, err := kafka.DialLeader(context.Background(), "tcp", kafkaAddr, topicName, totalPartitions)
	// if err != nil {
	// 	panic(err.Error())
	// }

	// 获取主题的分区数量,如果后台已经手动命令行创建过了
	partitionCount, err := getPartitionCount()
	if err != nil {
		log.Fatal("failed to get partition count:", err)
	}

	// 发送消息到多个分区
	for i := 0; i < partitionCount; i++ {
		conn, err := kafka.DialLeader(context.Background(), "tcp", kafkaAddr, topicName, i)
		if err != nil {
			log.Printf("failed to dial leader for partition %d: %v", i, err)
			continue
		}

		conn.SetWriteDeadline(time.Now().Add(writeDeadline))
		_, err = conn.WriteMessages(
			kafka.Message{Value: []byte(fmt.Sprintf("message for partition %d - one!", i))},
			kafka.Message{Value: []byte(fmt.Sprintf("message for partition %d - two!", i))},
			kafka.Message{Value: []byte(fmt.Sprintf("message for partition %d - three!", i))},
		)
		if err != nil {
			log.Printf("failed to write messages to partition %d: %v", i, err)
		}

		if err := conn.Close(); err != nil {
			log.Printf("failed to close producer connection for partition %d: %v", i, err)
		}
	}
	//开启consumer
	consumeWithGroup(partitionCount)
}

func consumeWithGroup(partitionCount int) {
	// 为每个分区创建消费者协程,使用消费者组和手动确认
	var wg sync.WaitGroup

	for i := 0; i < partitionCount; i++ {
		partition := i
		wg.Add(1)

		go func() {
			defer wg.Done()

			// 使用消费者组,可以实现手动确认消息
			reader := kafka.NewReader(kafka.ReaderConfig{
				Brokers:  []string{kafkaAddr},
				Topic:    topicName,
				GroupID:  "my-consumer-group", // 使用消费者组,如果是订阅模式,那就多个消费者组名字不一样
				MinBytes: 10e3,                // 10KB
				MaxBytes: 1e6,                 // 1MB
			})
			defer reader.Close()

			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
			defer cancel()

			for {
				msg, err := reader.FetchMessage(ctx)
				if err != nil {
					// 检查是否是超时错误
					if ctx.Err() == context.DeadlineExceeded {
						log.Printf("Consumer for partition %d timeout reached, exiting...", partition)
						break
					}
					log.Printf("Error fetching message: %v", err)
					break
				}

				fmt.Printf("Topic: %s, Partition: %d, Offset: %d, Value: %s\n", msg.Topic, msg.Partition, msg.Offset, string(msg.Value))

				// 提交偏移量确认消息已处理(手动ACK)
				if err := reader.CommitMessages(context.Background(), msg); err != nil {
					log.Printf("Failed to commit message: %v", err)
				}
			}
		}()
	}

	wg.Wait() // 等待所有分区消费者完成
}

func getPartitionCount() (int, error) {
	// 创建一个连接来获取分区信息
	conn, err := kafka.Dial("tcp", kafkaAddr)
	if err != nil {
		return 0, err
	}
	defer conn.Close()

	// 获取主题的分区信息
	partitions, err := conn.ReadPartitions(topicName)
	if err != nil {
		return 0, err
	}

	return len(partitions), nil
}

image

posted @ 2025-12-23 11:19  朝阳1  阅读(11)  评论(0)    收藏  举报