同一消费者组
相同点:
所有消费者共享同一个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]()