Kafka的消息架构以及go连接

Kafka的消息架构

主题层

Topic(主题) ,比如用户消息,命名为’user_message’;支付消息,命名为’pay_message’。两者互不干扰,等于是两条道.

注意这里的Topic是逻辑概念,落到硬件上,应该叫partition(分区),为了提升吞吐量,Kafka将一个主题分成了多个区,就像MongoDB 和 Elasticsearch 中的 Sharding、HBase 中的 Region,这是分布式的前提。

image-20220331212731193

值得注意的是,kafka只保证单个partition上的顺序。谈到顺序,我们了解一下offest,它标记了消费者在这个partiotion上读到了哪一条。

那么我们想要顺序消费,也要提升消费速度,怎么办?

  • 如果两个消费者同时消费同一个topic下的同一个partition,很显然,他们会重复消费。因为每个消费者的offest是独立保存的。
  • 如果我们分成两个partition,假设topic的数据是123456, 采用随机分配的策略,partition1上的可能是135,2上面是246,消费者A读取1,B读取2,这样就不会重复消费了,但是如果A的速度很快,可能A都到5了,B的2还没读完。这就导致了乱序消费。
  • 很简单,在上面的方案中,我们将随机分配改成哈希分配,从业务层将一个业务逻辑的消息发送到同一个partition上,比如用户ID。如果你的运气足够不好,可能会出现一个partition消息多,另一个少的情况。

分区层

在实际应用中, 我们往往将partition分配在不同的磁盘上,利用多磁盘来增加读写效率。但是既然是分布式,必然需要多个机器,而一个机器,我们常常称为一个broker(节点)

image-20220401101521752

多节点不一定要再不同的机器上,只是我们之所以需要多节点就是为了防止意外宕机,如果都在同一台上,一死就全死了,毫无意义。

每个broker都有一套冗余数据,也叫做 repliaction(副本)

那么如果我们有三个节点,客户端怎么知道需要连接哪个呢?这就引入了两个概念,leader(领导者)follower(跟随者)。对了,还有个管家,叫zookeeper,它负责管理所有broker的IP地址,是否存活,然后怎么选取领导者,怎么换领导者。

总之,zookeeper会选取leader,然后生产者和消费者只和leader交互。那么follower做啥?就是跟着跑,把leader的消息不断拉到本地,准备有一天等领导挂了自己成为新的领导。

消息层

这一层主要是存储信息和消费者的offest。值得注意的是,消息是可以压缩的,上一篇也提到了,这样可以大大减少网络带宽。但是具体细节后面再说。

总结

kafka的陌生词汇还是挺多的,自己在脑海中多过两遍,总整体,到部分,有个基本概念就好,后面谈到的时候能更好地理解。

  • broker,简单的理解,一个节点就是一个broker,大多数时候一台服务器一个节点。
  • topic,逻辑概念,用于业务隔离,给消费者订阅,生产者发布。
  • partition,真正存放消息的地方,单partition中的消息是顺序的。通常一个盘一个partition,最大化读写优势。
  • 组合起来大概就是一台机器上(broker1),A硬盘存储topic1的partition0 + topic2的partition0 ;然后B硬盘存储topic1的partition1 + topic2的partition1 。

为了高可用:

  • 首先我们需要冗余备份,每个partition最好有2个以上的副本,其中带头大哥就是leader,跟着的小弟就是follower。大哥前面跑,接受消息和发送消息,小弟后面追,追不上的可能就会被抛弃再同步一个小弟。(从“in sync replicas”(ISR)列表中删除)。
  • 大哥挂了,跟的最紧的小弟成为大哥, 如果都一样紧,就选举,由zookeeper决定。

为了使用:

  • 发布消息的叫producer,读取消息的叫consumer。
  • 消息在partition中的位置叫offest,消费者读的位置也叫offest。前者是不变的,叫分区位移或者绝对位移;后者是消费者自己决定要从哪里读,记录读到哪里,也叫消费者位移或者相对位移。其实就是书的页码和你的书签。
  • 通常生产者是很快的,只用写一条数据进去就好了,而消费者需要处理业务逻辑,肯定慢很多,所以就加入了消费者组,一个人打不过就一群人打,逻辑上他们就是一个人。
  • 消费者里面加入人或者有人挂了,都会触发重平衡,请记住这个名字,它是kafka最大的诟病。

image-20220401102634323

image-20220401102651868

安装

kafka是由scala语言写成的,后面用Java重构了,但是不管怎样,都要编译到jvm虚拟机中执行。

下载kafka

下载 wget https://archive.apache.org/dist/kafka/2.0.0/kafka_2.11-2.0.0.tgz 解压 tar -zxvf kafka_2.11-2.2.0.tgz cd kafka_2.11-2.2.0

简单配置下

vim  config/server.properties


#broker 的全局唯一编号,不能重复
broker.id=0
#能否删除 topic
delete.topic.enable=true
#处理网络请求的线程数量
num.network.threads=3
#用来处理磁盘 IO 的现成数量
num.io.threads=8
#发送套接字的缓冲区大小
socket.send.buffer.bytes=102400
#接收套接字的缓冲区大小
socket.receive.buffer.bytes=102400
#请求套接字的缓冲区大小
socket.request.max.bytes=104857600
#kafka 运行日志存放的路径
log.dirs=/opt/module/kafka/logs
#topic 在当前 broker 上的分区个数
num.partitions=1
#用来恢复和清理 data 下数据的线程数量
num.recovery.threads.per.data.dir=1
#segment 文件保留的最长时间,超时将被删除
log.retention.hours=168

作者:快乐的提千万
链接:https://juejin.cn/post/7039990029506052126
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

启动

建议使用screen

启动zk【默认端口2181】
bin/zookeeper-server-start.sh config/zookeeper.properties

启动Kafka
bin/kafka-server-start.sh config/server.properties

测试使用

#创建单分区单副本的 topic demo:
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic demo

#查看 topic 列表:
bin/kafka-topics.sh --list --zookeeper localhost:2181

#发送消息【生产者】
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic demo

#接收消息并在终端打印:
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic demo --from-beginning
 
#查看描述 topics 信息
bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic demo

Topic:demo      PartitionCount:1        ReplicationFactor:1     Configs:
        Topic: demo     Partition: 0    Leader: 1       Replicas: 1     Isr: 1


第一行给出了所有分区的摘要,每个附加行给出了关于一个分区的信息。 由于我们只有一个分区,所以只有一行。

“Leader”: 是负责给定分区的所有读取和写入的节点。
“Replicas”: 是复制此分区日志的节点列表,无论它们是否是领导者,或者即使他们当前处于活动状态。
“Isr”: 是一组“同步”副本。这是复制品列表的子集,当前活着并被引导到领导者

集群配置

Kafka 支持两种模式的集群搭建:可以在单机上运行多个 broker 实例来实现集群,也可在多台机器上搭建集群,下面介绍下如何实现单机多 broker 实例集群,其实很简单,只需要如下配置即可。

单机多broker 集群配置 利用单节点部署多个 broker。 不同的 broker 设置不同的 id,监听端口及日志目录。 例如:

cp config/server.properties config/server-2.properties
cp config/server.properties config/server-3.properties
vim config/server-2.properties
vim config/server-3.properties
复制代码

修改 :

broker.id=2
listeners = PLAINTEXT://your.host.name:9093
log.dir=/data/kafka/logs-2
复制代码

broker.id=3
listeners = PLAINTEXT://your.host.name:9094
log.dir=/data/kafka/logs-3
复制代码

启动Kafka服务:(也可以用screen)

bin/kafka-server-start.sh config/server-2.properties &
bin/kafka-server-start.sh config/server-3.properties &
复制代码

至此,单机多broker实例的集群配置完毕。

多机多 broker 集群配置

分别在多个节点按上述方式安装 Kafka,配置启动多个 Zookeeper 实例。

假设三台机器 IP 地址是 : 192.168.153.135, 192.168.153.136, 192.168.153.137

分别配置多个机器上的 Kafka 服务,设置不同的 broker id,zookeeper.connect 设置如下:

vim config/server.properties
复制代码

里面的 zookeeper.connect

修改为:

zookeeper.connect=192.168.153.135:2181,192.168.153.136:2181,192.168.153.137:2181

外网访问

配置:server.properties


修改成内网地址
listeners=PLAINTEXT://0.0.0.0:9092
改成外网地址
advertised.listeners=PLAINTEXT://
复制代码

本地查看

可以下载一个

image.png

Go接入kafka

需要借助的库

github.com/Shopify/sarama // kafka主要的库*
github.com/bsm/sarama-cluster // kafka消费组

生产者

package main

import (
	"fmt"

	"github.com/Shopify/sarama"
)

// 基于sarama第三方库开发的kafka client

func 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)
}
package producer

import (
	"fmt"
	"github.com/HappyTeemo7569/teemoKit/tlog"
	"github.com/Shopify/sarama"
	"kafkaDemo/define"
)

var (
	ProducerId = 1
)

type Producer struct {
	Producer   sarama.SyncProducer
	Topic      string //主题
	ProducerID int    //生产者Id
	MessageId  int
}

func (p *Producer) InitProducer() {
	config := sarama.NewConfig()
	config.Producer.RequiredAcks = sarama.WaitForAll          // 发送完数据需要leader和follow都确认
	config.Producer.Partitioner = sarama.NewRandomPartitioner // 新选出一个partition
	config.Producer.Return.Successes = true                   // 成功交付的消息将在success channel返回

	// 连接kafka
	client, err := sarama.NewSyncProducer([]string{define.SERVER_LIST}, config)
	if err != nil {
		tlog.Error("producer closed, err:", err)
		return
	}

	p.Producer = client
	p.Topic = define.TOPIC
	p.ProducerID = ProducerId
	p.MessageId = 1

	ProducerId++
}

func (p *Producer) SendMessage() {
	// 构造一个消息
	msg := &sarama.ProducerMessage{}
	msg.Topic = p.Topic
	txt := fmt.Sprintf("ProducerID:%d  this is a test log %d",
		p.ProducerID, p.MessageId)
	msg.Value = sarama.StringEncoder(txt)

	// 发送消息
	pid, offset, err := p.Producer.SendMessage(msg)
	//_, _, err := client.SendMessage(msg)
	if err != nil {
		fmt.Println("send msg failed, err:", err)
		return
	}
	tlog.Info(fmt.Sprintf("ProducerID:%d pid:%v offset:%v msg:%s",
		p.ProducerID, pid, offset, txt))

	p.MessageId++
}

func (p *Producer) Close() {
	p.Producer.Close()
}


消费者

package main

import (
	"fmt"
	"sync"

	"github.com/Shopify/sarama"
)

// kafka consumer

func main() {
	var wg sync.WaitGroup
	consumer, err := sarama.NewConsumer([]string{"127.0.0.1:9092"}, nil)
	if err != nil {
		fmt.Println("Failed to start consumer: %s", err)
		return
	}
	partitionList, err := consumer.Partitions("web_log") //获得该topic所有的分区
	if err != nil {
		fmt.Println("Failed to get the list of partition:, ", err)
		return
	}
	fmt.Println(partitionList)

	for partition := range partitionList {
		pc, err := consumer.ConsumePartition("web_log", int32(partition), sarama.OffsetNewest)
		if err != nil {
			fmt.Println("Failed to start consumer for partition %d: %s\n", partition, err)
			return
		}
		wg.Add(1)
		go func(sarama.PartitionConsumer) { //为每个分区开一个go协程去取值
			for msg := range pc.Messages() { //阻塞直到有值发送过来,然后再继续等待
				fmt.Printf("Partition:%d, Offset:%d, key:%s, value:%s\n", msg.Partition, msg.Offset, string(msg.Key), string(msg.Value))
			}
			defer pc.AsyncClose()
			wg.Done()
		}(pc)
	}
	wg.Wait()
	consumer.Close()
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值