Golang通过kafka-go库操作kafka

package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

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

var (
	topic  = "user_click"
	reader *kafka.Reader
)

// 生产消息
func writeKafka(ctx context.Context) {
	writer := &kafka.Writer{
		Addr:                   kafka.TCP("localhost:9092"), //不定长参数,支持传入多个broker的ip:port
		Topic:                  topic,                       //为所有message指定统一的topic。如果这里不指定统一的Topic,则创建kafka.Message{}时需要分别指定Topic
		Balancer:               &kafka.Hash{},               //把message的key进行hash,确定partition
		WriteTimeout:           1 * time.Second,             //设定写超时
		RequiredAcks:           kafka.RequireNone,           //RequireNone不需要等待ack返回,效率最高,安全性最低;RequireOne只需要确保Leader写入成功就可以发送下一条消息;RequiredAcks需要确保Leader和所有Follower都写入成功才可以发送下一条消息。
		AllowAutoTopicCreation: true,                        //Topic不存在时自动创建。生产环境中一般设为false,由运维管理员创建Topic并配置partition数目
	}
	defer writer.Close() //记得关闭连接

	for i := 0; i < 3; i++ { //允许重试3次
		if err := writer.WriteMessages(ctx, //批量写入消息,原子操作,要么全写成功,要么全写失败
			kafka.Message{Key: []byte("1"), Value: []byte("A")},
			kafka.Message{Key: []byte("2"), Value: []byte("B")},
			kafka.Message{Key: []byte("3"), Value: []byte("C")},
			kafka.Message{Key: []byte("1"), Value: []byte("D")}, //key相同时肯定写入同一个partition
			kafka.Message{Key: []byte("2"), Value: []byte("E")},
		); err != nil {
			if err == kafka.LeaderNotAvailable { //首次写一个新的Topic时,会发生LeaderNotAvailable错误,重试一次就好了
				time.Sleep(500 * time.Millisecond)
				continue
			} else {
				fmt.Printf("batch write message failed: %v", err)
			}
		} else {
			break //只要成功一次就不再尝试下一次了
		}
	}
}

// 消费消息
func readKafka(ctx context.Context) {
	reader = kafka.NewReader(kafka.ReaderConfig{
		Brokers:        []string{"localhost:9092"}, //支持传入多个broker的ip:port
		Topic:          topic,
		CommitInterval: 1 * time.Second,   //每隔多长时间自动commit一次offset。即一边读一边向kafka上报读到了哪个位置。
		GroupID:        "recommend_biz",   //一个Group内消费到的消息不会重复
		StartOffset:    kafka.FirstOffset, //当一个特定的partition没有commited offset时(比如第一次读一个partition,之前没有commit过),通过StartOffset指定从第一个还是最后一个位置开始消费。StartOffset的取值要么是FirstOffset要么是LastOffset,LastOffset表示Consumer启动之前生成的老数据不管了。仅当指定了GroupID时,StartOffset才生效。
	})
	// defer reader.Close() //由于下面是死循环,正常情况下readKafka()函数永远不会结束,defer不会执行。所以需要监听信息2和15,当收到信号时关闭reader。需要把reader设为全局变量

	for { //消息队列里随时可能有新消息进来,所以这里是死循环,类似于读Channel
		if message, err := reader.ReadMessage(ctx); err != nil {
			fmt.Printf("read message from kafka failed: %v", err)
			// 可以在这里添加一个小的延迟,避免在出现持续性错误时过快地循环
            time.Sleep(5 * time.Second)
            continue // 使用 continue 而不是 break,这样可以继续循环而不是退出
		} else {
			offset := message.Offset
			fmt.Printf("topic=%s, partition=%d, offset=%d, key=%s, message content=%s\n", message.Topic, message.Partition, offset, string(message.Key), string(message.Value))
		}
	}
}

// 需要监听信息2和15,当收到信号时关闭reader
func listenSignal() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) //注册信号2和15
	sig := <-c                                        //阻塞,直到信号的到来
	fmt.Printf("receive signal %s\n", sig.String())
	if reader != nil {
		reader.Close()
	}
	os.Exit(0) //进程退出
}

func main() {
	ctx := context.Background()
	// writeKafka(ctx)

	go listenSignal()
	readKafka(ctx)
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值