消息队列,kafka+golang,基础

前言

kafka官网:http://kafka.apachecn.org/

消息队列

消息队列在如今的软件架构中,地位非比寻常。优点如下:
1)、解耦。2)、冗余。3)、扩展性。4)、灵活性and峰值处理能力。5)、可恢复性。6)、顺序保证。(ps:kafka保证一个partition内的数据是有序的)7)、缓冲。8)、异步通信。

kafka

Kafka是一个分布式的、可分区的、可复制的消息系统。
其基本术语:

  • 1)无论是kafka集群还是consumer,都依赖zookeeper集群来保存一些meta信息。
  • Producer:消息生产者,向kafka broker发消息的客户端。
  • consumer:消息消费者,向kafka broker取消息的客户端。
  • Topic:可以理解是一个队列,一个topic里有很多个partition。
  • consumer group:这是kafka用来实现topic广播的和单播的手段。topic的消息会复制(不是真正的复制)到所有的CG,但是每个partition只会把消息发给该CG中的一个consumer。广播的实现方法是:只要每个consumer有一个独立的CG就行了。单播的实现方法是:只要所有的consumer在同一个CG。
  • Broker:一台kafka服务器就是一个broker,一个集群由多个broker。一个broker可以容纳多个topic。
  • partition:一个非常大恶的topic可以分布在多个broker上,一个topic可以分成多个partition,每个partition是一个有序队列。partition中的每条消息都会被分配一个有序的ID,kafka只保证按一个partition中的顺序将消息发给consumer,不保证一个topic的整体(多个partition)的顺序。

安装

这里使用docker安装,首先安装zookeeper

安装zookeeper

docker run -itd --name zookeeper -p 2181:2181 -v /etc/localtime:/etc/localtime zookeeper:3.6
  • 指定端口 2181
  • /etc/localtime:同步本地时间和容器时间
  • 这里指定版本3.6,也可以不指定

安装docker

docker run -itd --name kafka -p 9092:9092 -e KAFKA_BROKER_ID=0 \
-e KAFKA_ZOOKEEPER_CONNECT=192.168.254.172:2181/kafka \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.254.172:9092 \
-e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 \
-v /etc/localtime:/etc/localtime wurstmeister/kafka
  • -e KAFKA_BROKER_ID=0 在kafka集群中,每个kafka都有一个BROKER_ID来区分自己
  • -e KAFKA_ZOOKEEPER_CONNECT=192.168.254.172:2181/kafka
    配置zookeeper管理kafka的路径192.168.254.172:2181/kafka
  • KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.254.172:9092
    把kafka的地址端口注册给zookeeper
  • -e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 配置kafka的监听端口
  • -v /etc/localtime:/etc/localtime 容器时间同步虚拟机的时间

验证

略。。。

go简单栗子

下载包

文档地址:https://godoc.org/github.com/Shopify/sarama#Broker

go get github.com/Shopify/sarama

//集群也可以用下面这个
go get github.com/bsm/sarama-cluster

异步生产者

func InitKafkaProducer()(sarama.AsyncProducer,error){
	config := sarama.NewConfig()
	config.Producer.RequiredAcks = sarama.WaitForAll //等待服务器所有副本都保存成功后的响应
	config.Producer.Partitioner = sarama.NewRandomPartitioner  //随机的分区类型
	//是否等待成功和失败后的响应,只有上面的RequireAcks设置不是NoReponse这里才有用
	config.Producer.Return.Successes = true
	config.Producer.Return.Errors = true
	config.Version = sarama.V2_5_0_0 //设置使用的kafka版本,如果低于V0_10_0_0版本,消息中的timestrap没有作用.需要消费和生产同时配置
	return sarama.NewAsyncProducer([]string{"192.168.254.172:9092"},config)
}
//ProducerMsg 调用该函数生产消息
func ProducerMsg(){
	prod,err := InitKafkaProducer()
	if err!=nil{
		panic(err)
	}
	defer prod.Close()
	msg := &sarama.ProducerMessage{
		Topic:     "log",
		Key:        sarama.StringEncoder("test") ,
		Partition   :1,
	}
	msgchan := prod.Input()

	for i:= 0;i<100;i++{
		msg.Value = sarama.StringEncoder("msg id is :" + strconv.Itoa(rand.Intn(100)))
		msgchan <- msg
		select {
		case suc := <-prod.Successes():
			fmt.Println(suc)
		case err := <-prod.Errors():
			fmt.Println(err)
		}
	}
}

消费者

func InitKafkaConsumer()(sarama.Consumer,error) {
	config := sarama.NewConfig()
	config.Consumer.Return.Errors = true
	config.Version = sarama.V0_11_0_0
	return sarama.NewConsumer([]string{"192.168.254.172:9092"}, config)
}
//ConsumerMsg 调用该函数消费消息
func ConsumerMsg(){
	cons,err := InitKafkaConsumer()
	if err!=nil{
		panic(err)
	}
	defer cons.Close()
	//获取分区数
	res,_ := cons.Partitions("log")
	fmt.Println(res)
	
	pc,err := cons.ConsumePartition("log",0,sarama.OffsetNewest)
	if err!=nil{
		panic(err)
	}
	msgchan := pc.Messages()
	errchan := pc.Errors()
	for {
		select {
		case msg  :=<- msgchan :
			fmt.Println("msg:",msg.Partition,string(msg.Key),string(msg.Value),msg.Timestamp.String())
		case err =<- errchan :
			fmt.Println(err)
		}
	}
}

cluster管理

需求:为一个主题添加两个分区,并指定副本

更多管理方法参考 https://godoc.org/github.com/Shopify/sarama#Broker

func ClusterMge(){
	config := sarama.NewConfig()
	config.Version = sarama.V2_5_0_0 //版本要高于2.4
	admin,err := sarama.NewClusterAdmin([]string{"192.168.254.172:9092"},config)
	if err!=nil{
		panic(err)
	}
	//这里添加两个分区,也可以做其他操作
	err = admin.CreatePartitions ("log",3,[][]int32{{0},{0}},false)
	if err!=nil{
		panic(err)
	}
	admin.Close()
}

消费者组

上面的消费者例子中,如何同时启用两个消费者,就会发现,每一条消息会被两个接受者接收到。
如果想让一条消息只被一个消费者接收到,就要设置消费者组,同组消费者对于一条消息,只有一个人能够接收到。例子如下:

//先实现接口
type exampleConsumerGroupHandler struct{}

func (exampleConsumerGroupHandler) Setup(_ sarama.ConsumerGroupSession) error   { return nil }
func (exampleConsumerGroupHandler) Cleanup(_ sarama.ConsumerGroupSession) error { return nil }
func (h exampleConsumerGroupHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
	for msg := range claim.Messages() {
		fmt.Printf("Message topic:%q partition:%d offset:%d value=%s\n", msg.Topic,msg.Partition, msg.Offset,string(msg.Value))
		sess.MarkMessage(msg, "")
	}
	return nil
}
//启用消费者组,将程序打包后,运行同样的两个实例,会发现接收到的消息不相同
func StartConsumeGroup(){
	config := sarama.NewConfig()
	config.Version = sarama.V2_0_0_0 // specify appropriate version
	config.Consumer.Return.Errors = true

	group, err := sarama.NewConsumerGroup([]string{"192.168.254.172:9092"}, "my-group", config)
	if err != nil {
		panic(err)
	}
	defer func() { _ = group.Close() }()

	go func() {
		for err := range group.Errors() {
			fmt.Println("ERROR", err)
		}
	}()

	ctx := context.Background()
	for {
		topics := []string{"log"}
		handler := exampleConsumerGroupHandler{}
		err := group.Consume(ctx, topics, handler)
		if err != nil {
			panic(err)
		}
	}
}

未完待续

猜你喜欢

转载自blog.csdn.net/qq_25490573/article/details/107229554