代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/20-go-kafka
官网地址:https://kafka.apache.org/downloads
我下载的是3.6.0
版本
注:图中2.13是Scala的版本,后面的3.6.0才是kafka的版本
注意:2.2.0版本之前的Kafka,运行依赖于 Zookeeper,所以还需要下并安装Zookeeper,ZooKeeper和Kafka版本之间有一定的对应关系,不同版本的ZooKeeper和Kafka可以相互兼容,但需要满足一定的条件。 Kafka 2.2.0 开始支持使用内置的ZooKeeper替代外部ZooKeeper。 所以3.6.0是不需要安装Zookeeper的,直接解压即可。
因为Kafka
中的Broker
注册,Topic
注册,以及负载均衡都是在Zookeeper
中管理,所以需要先启动内置的Zookeeper
启动命令如下:
zookeeper-server-start.bat ..\..\config\zookeeper.properties
注意配置文件的路径哦,..\..\config\zookeeper.properties
使用了相对路径。
当看到绑定到IP
地址为0.0.0.0
、端口号为2181
的地址,表示ZooKeeper
服务器监听在该地址,启动成功,注意:命令行窗口不能关闭,关闭后无法执行后续操作
新开一个命令行窗口,在之前的目录中输入启动命令
kafka-server-start.bat ..\..\config\server.properties
启动成功后如下图:监听在9092
端口,注意:命令行窗口不能关闭,关闭后无法执行后续操作
在之前的目录中,新开一个命令行,进行创建名为“topic_test”
的主题 , 其包含一个分区,每个分区只设置一个副本,注意脚本是kafka-topics.bat
kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic topic_test
查看topics
kafka-topics.bat --list --bootstrap-server localhost:9092
命令如下:注意脚本是kafka-console-producer.bat
kafka-console-producer.bat --broker-list localhost:9092 --topic topic_test
继续新开一个命令行窗口,用于消费消息
命令如下:注意脚本是kafka-console-consumer.bat
kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic topic_test --from-beginning
提示:本次演示共开了四个终端,其中演示创建topic
,我们是在与生产端同一个窗口创建的。如下:
注意:生产端和消费端,对应kafka服务端(broker)而言,生产端和消费端都是kafka的客户端
Golang中连接kafka可以使用第三方库:github.com/IBM/sarama
生产消息包含以下步骤:配置生产者,封装消息,消息类型是 *sarama.ProducerMessage
,连接kafka
,默认端口是9092
,发送消息,返回消息存储的分区partition
和日志偏移量offset
。
package main
import (
"fmt"
"github.com/IBM/sarama"
"time"
)
func main() {
config := sarama.NewConfig()
config.Producer.RequiredAcks = sarama.WaitForAll // 发送完数据后,需要leader和follower都确认
config.Producer.Partitioner = sarama.NewRandomPartitioner // 写到随机分区中
config.Producer.Return.Successes = true // 成功交付的消息将在success channel返回
// 创建一个消息
msg := &sarama.ProducerMessage{
Topic: "topic_test", // 在broker中已经创建过的topic
Key: nil, // 用于控制消息发到哪个分区的,我们配置发到随机分区了
Value: nil, // 消息内容,类型为Encoder,所以传字符串时,需要将string转为Encoder
Headers: nil,
Metadata: nil,
Offset: 0,
Partition: 0,
Timestamp: time.Time{},
}
msg.Value = sarama.StringEncoder("produce kafka messages...")
// 连接kafka
producer, err := sarama.NewSyncProducer([]string{"localhost:9092"}, config)
if err != nil {
fmt.Println("连接kafka服务器出现错误,err:", err)
return
}
defer producer.Close()
// 发送消息
pid, offset, err := producer.SendMessage(msg)
if err != nil {
fmt.Println("发送消息出现错误,err:", err)
return
}
fmt.Printf("pid:%v,offset:%v", pid, offset)
}
运行结果如下:
进入我们开启的消费者窗口查看,是否消费到消息
package main
import (
"fmt"
"github.com/IBM/sarama"
"sync"
)
// 消费者代码,一般会是在其他的服务中启动
func main() {
var wg sync.WaitGroup
// 连接kafka
consumer, err := sarama.NewConsumer([]string{"localhost:9092"}, nil)
if err != nil {
fmt.Println("消费者连接kafka失败")
return
}
// 选择要消费的topic,通过topic获取到所有的分区
partitions, err := consumer.Partitions("topic_test")
if err != nil {
fmt.Println("获取指定topic的分区失败,err:", err)
return
}
fmt.Println(partitions)
// 遍历所有的分区
for partition := range partitions {
// 针对每个分区创建一个分区消费者
// 这里为了演示选的从头开始消费,实际工作中,一般是从最新的消息开始消费sarama.OffsetNewest
pt, err := consumer.ConsumePartition("topic_test", int32(partition), sarama.OffsetOldest)
if err != nil {
fmt.Printf("为当前分区创建消费者失败,分区:%d,err:%v\n", partition, err)
continue
}
wg.Add(1)
go func(pt sarama.PartitionConsumer) {
defer func() {
wg.Done()
if err := recover(); err != any(nil) {
fmt.Println("子协程中一定要使用defer对panic进行recover,避免导致整个程序退出")
}
}()
// 为每个分区开一个go协程取值
for msg := range pt.Messages() { // 阻塞直到有值发送过来,然后再继续等待
fmt.Printf("分区:%d,offset:%d,key:%s,value:%s\n", msg.Partition, msg.Offset, msg.Key, msg.Value)
}
defer pt.AsyncClose()
}(pt)
}
wg.Wait()
defer consumer.Close()
}
通过kafka
生产端窗口或者go-kafka
程序生产端继续发送一条消息,生产者接收到了最新的消息
可以看到最新的两条消息,空串和字符串也都消费到了