Go 连接Kafka消息队列 | 青训营

105 阅读5分钟

消息队列(Message Queue,简称MQ),指保存消息的一个容器,本质是个队列。许多人认为 MQ 通过消息的发送和接受来实现程序的异步和解耦,mq主要用于异步操作,这个不是 MQ 的真正目的,只不过是 MQ 的应用,MQ 真正的目的是为了通讯。

一、Kafka介绍

  • Kafka 本质上是⼀个消息队列,一个高吞吐量、持久性、分布式的消息系统。
  • 包含生产者(producer)和消费者(consumer),每个consumer属于一个特定的消费者组(Consumer Group)。
  • 生产者生产消息(message)写入到kafka服务器(broker,kafka集群的节点),消费者从kafka服务器(broker)读取消息。
  • 消息可分为不同的类型即不同的主题(topic)。
  • 同一主题(topic)的消息可以分散存储到不同的服务器节点(partition)上,一个分区(partition)只能由一个消费者组内的一个消费者消费。
  • 每个partition可以有多个副本,一个Leader和若干个Follower,Leader发生故障时,会选取某个Follower成为新的Leader。

二、kafka的安装(win端)

1、安装jdk环境,下载地址,然后根据解压或者安装目录配置jdk的环境变量,这里不再详细介绍

2、kafka下载安装,下载地址,我这里选择 kafka_2.13-3.5.1,由于kafka里面自带了zookeeper,所以我们不需要单独去下载zookeeper

3、解压kafka至合适目录,然后编辑 \kafka_2.13-3.5.1\config 里面的配置文件

  • 首先编辑 zookeeper.properties文件 修改里面的dataDir为dataDir=D:\kafka\kafka_2.13-3.5.1\zookeeper 目录根据实际情况来修改,然后在文件最后添加 audit.enable=true
  • 然后编辑 server.properties 文件,修改log.dirs 为 log.dirs=D:\kafka\kafka_2.13-3.5.1\kafka-logs 目录也是根据实际情况修改

4、配置文件修改完成后,首先启动 zookeeper,需要回到 kafka 的安装目录,然后在cmd中执行:

.\bin\windows\zookeeper-server-start.bat .\config\zookeeper.properties

jTlTQ1Cw6l.png

5、zookeeper 启动成功后,接下来启动 kafka,在cmd中执行:

.\bin\windows\kafka-server-start.bat .\config\server.properties

6、Kafka的默认端口是9092,用于在Kafka Broker之间传输消息。使用TCP协议,客户端通过连接Broker的IP地址和端口号,发送请求和接收返回的消息。

三、go连接kafka

1、安装kafka驱动

go get github.com/segmentio/kafka-go

2、使用生产者代码发送信息

下面代码实现了模拟生产者向kafka发送消息的过程

我们首先创建了一个sarama的配置对象"config"。设置了生产者的一些属性,例如"RequiredAcks"表示发送完数据需要等待leader和follow都确认;"Partitioner"表示新选出一个partition;"Return.Successes"为true表示成功交付的消息将在success channel返回。

然后,构造了一个ProducerMessage对象"msg",设置了要发送的消息的topic和value。

接着,使用NewSyncProducer方法创建了一个同步生产者"client",并传入Kafka broker的地址以及之前创建的配置对象。如果出现错误,会打印错误信息并退出程序。最后,使用SendMessage方法发送消息,并返回发送的分区号(pid)、消息在分区中的偏移量(offset)以及可能的错误信息(err)。

最后,打印发送结果(pid和offset),并在结束时关闭生产者连接。

package main
​
import (
   "fmt"
   "github.com/Shopify/sarama"
)
​
// 基于sarama第三方库开发的kafka clientfunc main() {
   config := sarama.NewConfig()
   config.Producer.RequiredAcks = sarama.WaitForAll          // 发送完数据需要leader和follow都确认
   config.Producer.Partitioner = sarama.NewRandomPartitioner // 新选出一个partition
   config.Producer.Return.Successes = true                   // 成功交付的消息将在success channel返回
​
   // 构造一个消息
   msg := &sarama.ProducerMessage{}
   msg.Topic = "web_log"
   msg.Value = sarama.StringEncoder("this is a test log")
   // 连接kafka
   client, err := sarama.NewSyncProducer([]string{"127.0.0.1:9092"}, config)
   if err != nil {
      fmt.Println("producer closed, err:", err)
      return
   }
   defer client.Close()
   // 发送消息
   pid, offset, err := client.SendMessage(msg)
   if err != nil {
      fmt.Println("send msg failed, err:", err)
      return
   }
   fmt.Printf("pid:%v offset:%v\n", pid, offset)
}

3、查看生产者发送的消息

使用kafka自带的命令行消费者客户端查看kafka中的数据,在kafka目录下打开cmd,输入下面命令,这里的 topic 和代码中的topic一致,均为 web_log,运行目录后可以看到输出了之前我们在代码中定义的字符串 "this is a test log"

.\bin\windows\kafka-console-consumer.bat --bootstrap-server 127.0.0.1:9092 --topic web_log --from-beginning

FJAl8Af1oi.png

4、使用消费者代码获取消息

首先创建了一个sarama的配置对象"config",并设置"Return.Errors"为true以便处理消费错误。

然后,使用NewConsumer方法创建了一个消费者"consumer",并传入Kafka broker的地址以及配置对象。如果创建失败,将打印错误信息并退出程序。在函数结束时,记得关闭消费者连接。

接下来,指定要消费的topic和分区,并使用ConsumePartition方法创建一个分区消费者"partitionConsumer"。如果创建失败,同样会打印错误信息并退出程序。在函数结束时,也要关闭分区消费者连接。

最后,通过循环从分区消费者的Messages通道中读取消息,并打印出消息的key和value。同时,在处理完所有消息后,还需要检查是否有消费错误,如果有则打印错误信息。

package main
​
import (
   "fmt"
   "github.com/Shopify/sarama"
)
​
// 基于sarama第三方库开发的kafka clientfunc main() {
   config := sarama.NewConfig()
   config.Producer.RequiredAcks = sarama.WaitForAll          // 发送完数据需要leader和follow都确认
   config.Producer.Partitioner = sarama.NewRandomPartitioner // 新选出一个partition
   config.Producer.Return.Successes = true                   // 成功交付的消息将在success channel返回
​
   // 构造一个消息
   msg := &sarama.ProducerMessage{}
   msg.Topic = "web_log"
   msg.Value = sarama.StringEncoder("this is a test log")
   // 连接kafka
   client, err := sarama.NewSyncProducer([]string{"127.0.0.1:9092"}, config)
   if err != nil {
      fmt.Println("producer closed, err:", err)
      return
   }
   defer client.Close()
   // 发送消息
   pid, offset, err := client.SendMessage(msg)
   if err != nil {
      fmt.Println("send msg failed, err:", err)
      return
   }
   fmt.Printf("pid:%v offset:%v\n", pid, offset)
}

运行结果