[ kafka-go消息中间价[kafka] ]


使用

生产者

  • topic

消费者

  • GroupID
package main

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

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

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

func kafkaConn() *kafka.Conn {
    topic := "my-toc"
    partition := 0

    conn, err := kafka.DialLeader(context.Background(), "tcp", "42.193.152.252:9092", topic, partition)
    if err != nil {
        log.Fatal("failed to dial leader:", err)
    }
    // kafkaW(conn)
    return conn

}

func kafkaW(conn *kafka.Conn) {
    conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
    _, err := conn.WriteMessages(
        kafka.Message{Value: []byte("one!")},
        kafka.Message{Value: []byte("two!")},
        kafka.Message{Value: []byte("three!")},
    )
    if err != nil {
        log.Fatal("failed to write messages:", err)
    }

    if err := conn.Close(); err != nil {
        log.Fatal("failed to close writer:", err)
    }
    fmt.Println("写入成功")
}

func kafkaR(conn *kafka.Conn) {
    fmt.Println("开始读取")
    conn.SetReadDeadline(time.Now().Add(10 * time.Second))
    batch := conn.ReadBatch(10e3, 1e6) // fetch 10KB min, 1MB max

    b := make([]byte, 10e3) // 10KB max per message
    for {
        n, err := batch.Read(b)
        if err != nil {
            break
        }
        fmt.Println(string(b[:n]))
    }

    if err := batch.Close(); err != nil {
        log.Fatal("failed to close batch:", err)
    }

    if err := conn.Close(); err != nil {
        log.Fatal("failed to close connection:", err)
    }
    fmt.Println("读取完毕")
}

// 生产消息
func writeKafka(ctx context.Context) {
    writer := &kafka.Writer{
        Addr:                   kafka.TCP("47.120.55.99: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("大")},
            kafka.Message{Key: []byte("2"), Value: []byte("乔")},
            kafka.Message{Key: []byte("3"), Value: []byte("乔")},
            kafka.Message{Key: []byte("1"), Value: []byte("教")}, //key相同时肯定写入同一个partition
            kafka.Message{Key: []byte("2"), Value: []byte("育")},
        ); err != nil {
            if err == kafka.LeaderNotAvailable { //首次写一个新的Topic时,会发生LeaderNotAvailable错误,重试一次就好了
                time.Sleep(500 * time.Millisecond)
                continue
            } else {
                fmt.Printf("kafka写入失败: %v", err)
            }
        } else {
            fmt.Println("写入成功")
            break //只要成功一次就不再尝试下一次了
        }
    }
}

// 消费消息
func readKafka(ctx context.Context) {
    reader = kafka.NewReader(kafka.ReaderConfig{
        Brokers:        []string{"47.120.55.99: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("kafka读取失败: %v", err)
            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, 由于执行kill时无法触发defer关闭连接, 所以需要在主进程进行监听, 关闭信号
func listenSignal() {
    c := make(chan os.Signal, 1)                      // 创建chan管道
    signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) // 注册信号2和15到管道里
    sig := <-c                                        // 阻塞,直到信号的到来
    fmt.Printf("收到信号: %s\n", sig.String())            // ctrl + c
    if reader != nil {
        reader.Close() // 关闭kafka连接
    }
    os.Exit(0) //进程退出
}

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

    go listenSignal()
    readKafka(ctx)
    // kafkaW(kafkaConn())
    // kafkaR(kafkaConn())
}