安装

官网下载kafka安装包

#上传:
rz
#解压:
tar -zxvf  包名
#在2.几之后包内是已经编译完成的,直接解压。bin下的为启动,config中为配置文件

启动zookeeper

#直接启动zookeeper,加-daemon参数,可以在后台启动Zookeeper,输出的信息在保存在执行目录的logs/zookeeper.out文件中。
/bin/zookeeper-server-start.sh -daemon config/zookeeper.properties

注意:此处zookeeper是kafka自带的,并不是重新下载配置的。

关闭zookeeper(这是测试步骤,起了就别乱动了)

bin/zookeeper-server-stop.sh -daemon config/zookeeper.properties

kafka配置文件:(位置在解压文件夹下的config文件夹中)

//当前机器在集群中的唯一标识,和zookeeper的myid性质一样
broker.id=0
//当前kafka对外提供服务的端口默认是9092
port=9092
//这个参数默认是关闭的,在0.8.1有个bug,DNS解析问题,失败率的问题。
host.name=hadoop1
//这个是borker进行网络处理的线程数
num.network.threads=3
//这个是borker进行I/O处理的线程数
num.io.threads=8
//发送缓冲区buffer大小,数据不是一下子就发送的,先回存储到缓冲区了到达一定的大小后在发送,能提高性能
socket.send.buffer.bytes=102400
//kafka接收缓冲区大小,当数据到达一定大小后在序列化到磁盘
socket.receive.buffer.bytes=102400
//这个参数是向kafka请求消息或者向kafka发送消息的请请求的最大数,这个值不能超过java的堆栈大小
socket.request.max.bytes=104857600
//消息存放的目录,这个目录可以配置为“,”逗号分割的表达式,上面的num.io.threads要大于这个目录的个数这个目录,
//如果配置多个目录,新创建的topic他把消息持久化的地方是,当前以逗号分割的目录中,那个分区数最少就放那一个
log.dirs=/home/hadoop/log/kafka-logs
//默认的分区数,一个topic默认1个分区数
num.partitions=1
//每个数据目录用来日志恢复的线程数目
num.recovery.threads.per.data.dir=1
//默认消息的最大持久化时间,168小时,7天
log.retention.hours=168
//这个参数是:因为kafka的消息是以追加的形式落地到文件,当超过这个值的时候,kafka会新起一个文件
log.segment.bytes=1073741824
//每隔300000毫秒去检查上面配置的log失效时间
log.retention.check.interval.ms=300000
//是否启用log压缩,一般不用启用,启用的话可以提高性能
log.cleaner.enable=false
//设置zookeeper的连接端口
zookeeper.connect=192.168.123.102:2181,192.168.123.103:2181,192.168.123.104:2181
//设置zookeeper的连接超时时间
zookeeper.connection.timeout.ms=6000

启动kafka

bin/kafaka-server-start.sh  config/server.properties

不查看日志可以后台启动:

bin/kafaka-server-start.sh -daemon config/server.properties

集群搭建:

步骤一:去config文件夹下,将server.properties赋值为server-1.properties和server-2.properties

broker.id=0
listeners=PLAINTEXT://:9092
advertised.listeners=PLAINTEXT://192.168.31.222:9092
log.dirs=/tmp/kafka-logs-0

server-1.properties

broker.id=1
listeners=PLAINTEXT://:9093
advertised.listeners=PLAINTEXT://192.168.31.222:9093
log.dirs=/tmp/kafka-logs-1

server-2.properties

broker.id=2
listeners=PLAINTEXT://:9094
advertised.listeners=PLAINTEXT://192.168.31.222:9094
log.dirs=/tmp/kafka-logs-2

按照启动方式,分别启动。

特别注意:

启动顺序:zookeeper->kafka

关闭顺序:kafka->zookeeper

否则会造成kafka陷入死循环。

原理

kafka是一种发布订阅模式。

订阅发布消息系统中,消息被持久化到一个topic中。与点对点不同的是,消费者可以订阅一个或者多个topic,消费者可以消费该topic中的所有数据,同一条数据可以被多个消费者消费,消费之后数据不会立刻被删除。

生产消息的--------》生产者

消费消息的---------》消费者

(模式上类似于redis的发布订阅,只要你订阅了我就给你推,发布到topic的消息,只有订阅了topic的订阅者才会收到消息,但是不同的是kafka要你自己消费,redis直接给关注频道的都推消息。)(一下是自己思考内容:那redis的发布订阅其实可以作为微信公众号,关注了的直接发送。)

思考:1.数据既然可以被多个消费者消费,那也可能会出现重复消费的情况,topic没有收到响应的情况下,认为没有消费怎么办?

2.kafka是这么保证数据不丢失的?

优点:

1.解耦和扩展

2.异步

3.削峰和缓冲:为了防止突发流量,大批量的数据可能击垮下游或者下游没有足够多的机器来保证冗余,kafka会起到缓冲作用。让下游服务可以按照自己的节奏慢慢进行处理。

4.健壮性,

zookeeper要和kafka放在同一路径下吗 kafka和zookeeper启动顺序_数据

一个典型的Kafka集群中包含若干Producer(可以是web前端产生的Page View,或者是服务器日志,系统CPU、Memory等),若干broker(Kafka支持水平扩展,一般broker数量越多,集群吞吐率越高),若干Consumer Group,以及一个Zookeeper集群。Kafka通过Zookeeper管理集群配置,选举leader,以及在Consumer Group发生变化时进行rebalance。Producer使用push模式将消息发布到broker,Consumer使用pull模式从broker订阅并消费消息。

Topic和Partition

topic在逻辑上可以被认为是一个队列,每条消费都必须指定他的topic,可以简单理解为必须指明把这条消息放进那一条消息队列中。

为了使kafka的吞吐率线性提高,物理上把一个topic划分为一个或者多个partition。每一个Partition在物理上对应一个文件夹。该文件夹下存储这个Partition的所有消息和索引文件。(数据的存储是存放到硬盘的)

在创建topic的时候,同时可以指定分区数目,分区数目越多,吞吐量越大。(但是需要资源 也越来越多,会导致更高的不可用性)

重点:kafka在接收到生产者发送到的消息之后,会根据均衡策略将消息存储到不同的分区中。因为每一条消息都是被append到partition 中的,***顺序写磁盘***因此效率很高(kafka高吞吐量的保证)

在kafka中会保存所有的信息,不管消费或者没有消费。

两种删除策略

由于磁盘限制,两种删除旧数据策略

1.基于时间 2. 基于partition的大小

# The minimum age of a log file to be eligible for deletion
log.retention.hours=168
# The maximum size of a log segment file. When this size is reached a new log segment will be created.
log.segment.bytes=1073741824
# The interval at which log segments are checked to see if they can be deleted according to the retention policies
log.retention.check.interval.ms=300000
# If log.cleaner.enable=true is set the cleaner will be enabled and individual logs can then be marked for log compaction.
log.cleaner.enable=false

kafka会为每一个ConsumerGroup保留一些metadata信息–当前消费为position也即offset。这个offset由Consumer控制。正常情况下Consumer会在消费完一条消息后递增该offset。当然,Consumer也可将offset设成一个较小的值,重新消费一些消息。因为offet由Consumer控制,所以Kafka broker是无状态的,它不需要标记哪些消息被哪些消费过,也不需要通过broker去保证同一个Consumer Group只有一个Consumer能消费某一条消息,因此也就不需要锁机制,这也为Kafka的高吞吐率提供了有力保障。

Producer消息路由

producer发送的消息发送到broker后,会根据Partition机制选择将其存储到哪一个Partition。

假设一个topic对应一个文件,磁盘I/O会成为限制Topic的性能瓶颈,,partition的出现,不同的消息可以并行写入不同broker的不同partition里。极大提高吞吐率

可以在$KAFKA_HOME/config/server.properties中通过配置项num.partitions来指定新建Topic的默认Partition数量,也可在创建Topic时通过参数指定,同时也可以在Topic创建之后通过Kafka提供的工具修改。

在发送一条消息时,可以指定这条消息的key,Producer根据这个key和Partition机制来判断应该将这条消息发送到哪个Parition。Paritition机制可以通过指定Producer的paritition. class这一参数来指定,该class必须实现kafka.producer.Partitioner接口。

Consumer Group

Consumer high level API时,同一个Topic的一条消息只能被同一个Consumer Group内的一个Consumer消费,但多个ConsumerGroup可同时消费这一消息。

同时kafka也可以用来实现广播和单播,单播只需要让所有得counsumer在一个组里。

push&pull

kafka中producer想broker push------>consumer从broker pull消息

push模式很难适应不同消费速率的消费者。因为消费者发送速率是broker决定的。

大白话就是:

**push模式,**不管你能不能消费了,我就光给你推消息,。

**缺点:**容易造成拒绝服务和网络拥塞

**pull模式:**consumer可以自主控制消费信息的速率,同时consumer可以自己控制消费方式,-------1.批量消费。2.逐条消费。

同时可以选择不同的提交方式从而实现不同的传输语义

Kafka delivery guarantee(卡夫卡投递保障)

主要有以下几种策略:

1.at most once 消息可能会走丢,但是绝对不会重复消费(最多一次)

2.at least once 消息绝对不会走丢,但是可能会重复消费(至少一次)

3.exactly once 每条消息肯定会被传输且仅会被传输一次。(精确一次)

Kafka默认保证AT least once,并且允许通过设置producer异步提交来实现At most once。而Exactly once要求与外部存储系统写作。

(查看原文中的kafka的官网,工程师NEHA NARKHEDE做出的解释)

在分布式的发布订阅系统中,组成系统的计算机总会由于各发生的故障不能正常工作。在Kafka中的一个单独的额broker,可能会在生产者发送消息到一个Topic而发生宕机,或者出现网络故障,从而导致生产者发送消息失败。根据生产者如何处理这样的失败,产生了不用的语义: 至少一次语义(At least once semantics):如果生产者收到了Kafka broker的确认(acknowledgement,ack),并且生产者的acks配置项设置为all(或-1),这就意味着消息已经被精确一次写入Kafka topic了。然而,如果生产者接收ack超时或者收到了错误,它就会认为消息没有写入Kafka topic而尝试重新发送消息。如果broker恰好在消息已经成功写入Kafka topic后,发送ack前,出了故障,生产者的重试机制就会导致这条消息被写入Kafka两次,从而导致同样的消息会被消费者消费不止一次。每个人都喜欢一个兴高采烈的给予者,但是这种方式会导致重复的工作和错误的结果。 至多一次语义(At most once semantics):如果生产者在ack超时或者返回错误的时候不重试发送消息,那么消息有可能最终并没有写入Kafka topic中,因此也就不会被消费者消费到。但是为了避免重复处理的可能性,我们接受有些消息可能被遗漏处理。 精确一次语义(Exactly once semantics):即使生产者重试发送消息,也只会让消息被发送给消费者一次。精确一次语义是最令人满意的保证,但也是最难理解的。因为它需要消息系统本身和生产消息的应用程序还有消费消息的应用程序一起合作。比如,在成功消费一条消息后,你又把消费的offset重置到之前的某个offset位置,那么你将收到从那个offset到最新的offset之间的所有消息。这解释了为什么消息系统和客户端程序必须合作来保证精确一次语义。

在多集群场景里面,总会出现一些必要的解决的故障:

1.broker出现故障。按照高可用,每一条写入得数据都会被持久化而且会备份多副本(假设有n个副本)。kafka会被容忍有n-1个broker故障,kafka的副本协议保证了只要消息被成功写入主副本就会被写入其他的副本。

2.producer到broker的RPC调用可能失败。kafka的持久性依赖于生产者接收broker的ACK,但是在通信过程中会出现两种情况,①:broker在接收消息进行存储之后,发送ACK之前挂了;②在接收消息还没来得及存储直接挂了。由于producer并不能知道是那种原因导致的,所以会重复发送

3.客户端也可能会出现故障。精准一次投递也必须考虑客户端故障,(永久故障和临时故障是非常重要的)。为了正确性,broker应该丢到僵住的broker的消息,同时也不应该向僵住的消费者发送消息、一旦一个新的客户端实例启动,它应该能够从失败的实例留下的任何状态中恢复,从一个安全点开始处理。这意味着,消费的偏移量必须始终与生产的输出保持同步。

kafka的幂等性操作

幂等性(idempotence):每个分区中精确一次且有序

一个幂等性操作就是一个操作多次执行所造成的影响和只执行一次造成的影响是一样的。

**(我个人理解)**当生产者发送的操作时幂等性操作,那当出现生产者重复发送故障的时候,生产者会重复发送消息,但是这条消息只会写到broker日志中一次。对于单个分区,幂等生产者不会因为生产者或者broker故障发送多次消息。

开启幂等性:enable.idemprotence=true

幂等性原理

和TCP协议类似,每一批发送到kafka的消息都会包含一个序列号,broker将使用这个序列号来删除重复的发送。

区别:TCP协议只能在瞬态内存的连接中保证不重复,kafka会把序列号持久化到副本日志。即使leader挂了,其它的broker接管broker也能直接判断出是否重复。

事务:

事务:跨分区原子写入

kafka现在支持新的kafkaAPI支持跨分区原子写入。允许一个生产者发送一批到不同分区的消息,这个消息要么对任何消费这可见,要不全部不可见。

producer.initTransactions();
try{
    producer.beginTransaction();
    producer.send(record1);
    producer.send(recode2);
    producer.commitTransaction();
}catch(ProducerFencedException e){
    producer.close();
}catch(KafkaException e){
    producer.abortTransaction();
}
//原子性的发送到消息到topic的多个partition
//在同一个topic中的数据其可以是在事务中也可以不在事务中

1.read_commited:除了读取不属于事务的消息之外,还可以读取事务提交之后的消息

2.read_uncommited:按照偏移位置读取所有消息,不需要等事务的提交。

将所有的复制品Replica均匀分配到整个集群所用到的算法

1.将所有的Broker的(假设N哥Broker)和待分配的Partition排序;

2.将第i个Partition分配到第(i mod n)个Broker上

3.将第i个Partition上的第j个replica飞陪到第((i+j)mod n)个Broker

消息同步策略

ISR:In-Sync Replicas 副本同步队列

AR:Assigned Replicas 所有副本

ISR是由leader维护,fllower从leader同步数据有一些延迟(包括延迟时间reolica.lag.time.max.ms和延迟条数eplica.lag.max.messages两个维度)意一个超过阈值都会把follower剔除出ISR, 存入OSR(Outof-Sync Replicas)列表,新加入的follower也会先存放在OSR中。AR=ISR+OSR。

Producer在发布消息到某个Partition时,先通过ZooKeeper找到该Partition的Leader,然后无论该Topic的Replication Factor为多少,Producer只将该消息发送到该Partition的Leader。Leader会将该消息写入其本地Log。每个Follower都从Leader pull数据。这种方式上,Follower存储的数据顺序与Leader保持一致。Follower在收到该消息并写入其Log后,向Leader发送ACK。一旦Leader收到了ISR中的所有Replica的ACK,该消息就被认为已经commit了,Leader将增加HW并且向Producer发送ACK。

为了提高性能,每个Follower在接收到数据后就立马向Leader发送ACK,而非等到数据写入Log中。因此,对于已经commit的消息,Kafka只能保证它被存于多个Replica的内存中,而不能保证它们被持久化到磁盘中,也就不能完全保证异常发生后该条消息一定能被Consumer消费。

Consumer读消息也是从Leader读取,只有被commit过的消息才会暴露给Consumer。

kafka的写入方式

采用push的模式将消息发布到broker,每条消息被直接append到partition中的,属于顺序写磁盘。(kafka吞吐率高的原因)

producer 发送消息到 broker 时,会根据分区算法选择将其存储到哪一个 partition。其路由机制为:

1、 指定了 patition,则直接使用;
2、 未指定 patition 但指定 key,通过对 key 的 value 进行hash 选出一个 patition
3、 patition 和 key 都未指定,使用轮询选出一个 patition。

命令行操作kafka

#列出当前的topic
[root@localhost kafka_2.12-2.7.0]# bin/kafka-topics.sh  --zookeeper localhost:2181 --list
OpenJDK 64-Bit Server VM warning: If the number of processors is expected to increase from one, then you should configure the number of parallel GC threads appropriately using -XX:ParallelGCThreads=N
__consumer_offsets
t_test
topic_test



#创建topic,指定副本数,指定分区
bin/kafka-topics.sh --create  --zookeeper localhost:2181 --topic topic_test --replication-factor 1 --partitions 1


#查看topic的详细信息
[root@localhost kafka_2.12-2.7.0]# bin/kafka-topics.sh --zookeeper localhost:2181 --describe --topic t_test
OpenJDK 64-Bit Server VM warning: If the number of processors is expected to increase from one, then you should configure the number of parallel GC threads appropriately using -XX:ParallelGCThreads=N
Topic: t_test	PartitionCount: 2	ReplicationFactor: 1	Configs: 
	Topic: t_test	Partition: 0	Leader: 0	Replicas: 0	Isr: 0
	Topic: t_test	Partition: 1	Leader: 0	Replicas: 0	Isr: 0

#修改分区数:
[root@localhost kafka_2.12-2.7.0]# bin/kafka-topics.sh --zookeeper localhost:2181 --alter --partitions 3 --config cleanup.policy=compact --topic topic_test
OpenJDK 64-Bit Server VM warning: If the number of processors is expected to increase from one, then you should configure the number of parallel GC threads appropriately using -XX:ParallelGCThreads=N
WARNING: Altering topic configuration from this script has been deprecated and may be removed in future releases.
         Going forward, please use kafka-configs.sh for this functionality
Updated config for topic topic_test.
WARNING: If partitions are increased for a topic that has a key, the partition logic or ordering of the messages will be affected
Adding partitions succeeded!

存储策略

无论消息是否被消费,kafka 都会保留所有消息。有两种策略可以删除旧数据:

1、 基于时间:log.retention.hours=168 
2、 基于大小:log.retention.bytes=1073741824

springboot整合kafka案例:

server.port=8888
#配置kafka地址
spring.kafka.bootstrap-servers=192.168.94.133:9092
#初始化生产者配置
spring.kafka.producer.retries=0
#应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
spring.kafka.producer.acks=1
#批量大小
spring.kafka.producer.batch-size=16384
#提交延时
spring.kafka.producer.properties.linger.ms=0
#当生产端积累的消息达到batch-size或者接收到消息的linger.ms后,生产者就会将消息提交给kafka
#linger.ms为0表示每接收到一条消息就提交给kafka,这时候batch-size就没用了

#生产端缓冲区的大小
spring.kafka.producer.buffer-memory=33554432
#kafka提供的序列化和反序列化类
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer

#初始化消费者配置
#默认的消费组ID
spring.kafka.consumer.properties.group.id=defaultConsumerGroup
#是否自动提交offset
spring.kafka.consumer.enable-auto-commit=true
#提交offset延时
spring.kafka.consumer.auto-commit-interval=1000
#当kafka中没有初始offset或者offset超出范围时,将自动重置offset
#earliest:重置为分区中最小的offset
#latest:重置为分区中最新的offset(消费分区中新产生的数据)
#none:只要有一个分区不存在已提交的offset就抛出异常
spring.kafka.consumer.auto-offset-reset=latest
# 消费会话超时时间(超过这个时间consumer没有发送心跳,就会触发rebalance操作)
spring.kafka.consumer.properties.session.timeout.ms=120000
# 消费请求超时时间
spring.kafka.consumer.properties.request.timeout.ms=180000
# Kafka提供的序列化和反序列化类
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
# 消费端监听的topic不存在时,项目启动会报错(关掉)
spring.kafka.listener.missing-topics-fatal=false
# 设置批量消费
# spring.kafka.listener.type=batch
# 批量消费每次最多消费多少条消息

生产者:

package com.example.kafkafirst.producer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Controller;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author: GG
 */
@Controller
public class KfkaProducer {
    @Autowired
    public KafkaTemplate<String,String> kafkaTemplate;

    @GetMapping("/kafka/{message}")
    public ListenableFuture<SendResult<String, String>> senMessagger(@PathVariable("message") String message){
        ListenableFuture<SendResult<String, String>> send = kafkaTemplate.send("t_test", "hello", message);
        return send;
    }
}

消费者:

package com.example.kafkafirst.consumer;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

/**
 * @Author: GG
 */
@Component
public class Consumer {
    @KafkaListener(topics = {"t_test"})
    public void getMessage(ConsumerRecord<?,?> record){
        System.out.println("消费的topic:"+record.topic()+",消费的分区:"+record.partition()
        +",消费的信息的key:"+record.key()+",消费信息的value:"+record.value());
    }
}

后续加强

kafka的单个分区是有序的,顺序读写磁盘,新生产的消息会追加到后面,所以单个分区是有序的

两个配置可能会对分区造成影响:
max.in.flight.requests.per.connection
#默认值是5,制定了生产者在接收到服务器响应之前可以发送多个消息,值越高,占用内存越大,可以提高吞吐量。但是发生错误,可能会造成数据的发生顺序改变。
retries
发送失败时,指定生产者可以重新发送数据的次数。默认情况下为生产者每次重试之间默认等待100ms,可以通过参数retry.backoff.ms参数来改变这这个时间间隔,retries的缺省值为0

kafka不是完全同步,也不是完全异步,是一种ISR机制:

  1. leader会维护一个与其基本保持同步的Replica列表,该列表称为ISR(in-sync Replica),每个Partition都会有一个ISR,而且是由leader动态维护
  2. 如果一个flower比一个leader落后太多,或者超过一定时间未发起数据复制请求,则leader将其重ISR中移除
  3. 当ISR中所有Replica都向Leader发送ACK时,leader才commit

思考:

kafka可以长期存储数据吗?

方式:把数据保留时间设置为永久;开启日志压缩

应用场景

(1)你有一个应用,使用了事件模式,并需要对变更日志进行存储,理论上可以使用很多系统来存储日志,但是 Kafka 直接解决了很多此类场景的问题,例如日志的不可变,纽约时报就使用 Kafka 来存储他们所有文章的数据

(2)在应用中有一个内存缓存,数据源于 Kafka,这时可以把 Kafka topic 中的日志压缩,应用重新启动时,从偏移量为0的位置重新读取数据到缓存

(3)需要对来自 Kafka 的流数据进行流计算,当流计算逻辑发生变化时,我们希望重新计算一遍,这时就可以把偏移量置为0,重头计算

(4)Kafka 常被用于捕获数据库的变更,关心数据变化的应用就可以从中获取变更记录,做相应的业务操作,这时出现了一个新的应用,需要全部的数据快照,如果对一个大型产品数据执行全量 dump 操作是不现实的,非常耗时,但我们可以对 Kafka 中的记录在0偏移量重新加载一遍

为什么可以?

数据在kafka是持久化到硬盘的,有数据检查,有多副本本来容错,并且持续累加的数据不会是性能变慢。

在kafka中的设置有以下特点:

1.kafka 存储可被重新读取的持久数据
    
2.kafka是一个分布式系统,以cluster形式运行,可以弹性扩展和所见,有容错复制系统,具有高可用性
    //cluster模式:Driver进程会在集群中的一个worker中启动,而且客户端进程在完成自己提交任务单额职责后就可以退出,而不用等到应用程序执行完毕
3.kafka允许实时的数据流处理,而不是一次处理一条数据

Kafka消息积压的典型场景和简单处理:

1.实时/消费任务挂掉

a.任务重启消费最新的消息,对于滞后历史数据采用离线程序进行“补漏”

b.任务启动从上次提交的offset进行处理

数据积压特别大,需要增加任务的处理能力。比如增加资源等

2.kafka的分区数设置不合理(太少)和消费者“消费力不足”

合理增加kafka分区数,如果利用的是Spark流和Kafka direct approach方式,也可以对KafkaRDD进行repartition重分区,增加并行度处理。

3.kafka消息的key不均匀,导致分区 间数据不均衡

可以在Kafka producer处,给key加随机后缀,使其均衡。

zookeeper在kafka中的作用

1.Broker注册

broker是分布式部署并且相互之间相互独立,注册系统管理整个集群中的Broker。在Zookeeper上有一个专门用来进行Broker服务器列表记录的节点

/brokers/ids

每个Broker在启动时,都会到Zookeeper上进行注册,即到/brokers/ids下创建自己的节点,如/brokers/ids/[0…N]

Kafka使用了全局唯一的数字来指代每个Broker服务器,不同的Broker必须使用不同的Broker ID进行注册,创建完节点后,每个Broker就会将自己的IP地址和端口信息记录到该节点中去。其中,Broker创建的节点类型是临时节点,一旦Broker宕机,则对应的临时节点也会被自动删除。

2.topic注册

在Kafka中,同一个Topic的消息会被分成多个分区并将其分布在多个Broker上,这些分区信息及与Broker的对应关系也都是由Zookeeper在维护,由专门的节点来记录,如:

/borkers/topics

Kafka中每个Topic都会以/brokers/topics/[topic]的形式被记录,如/brokers/topics/login和/brokers/topics/search等。Broker服务器启动后,会到对应Topic节点(/brokers/topics)上注册自己的Broker ID并写入针对该Topic的分区总数,如/brokers/topics/login/3->2,这个节点表示Broker ID为3的一个Broker服务器,对于"login"这个Topic的消息,提供了2个分区进行消息存储,同样,这个分区节点也是临时节点。

3.生产者负载均衡

由于同一个Topic消息会被分区并将其分布在多个Broker上,因此,生产者需要将消息合理地发送到这些分布式的Broker上,那么如何实现生产者的负载均衡,Kafka支持传统的四层负载均衡,也支持Zookeeper方式实现负载均衡。

(1) 四层负载均衡,根据生产者的IP地址和端口来为其确定一个相关联的Broker。通常,一个生产者只会对应单个Broker,然后该生产者产生的消息都发往该Broker。这种方式逻辑简单,每个生产者不需要同其他系统建立额外的TCP连接,只需要和Broker维护单个TCP连接即可。但是,其无法做到真正的负载均衡,因为实际系统中的每个生产者产生的消息量及每个Broker的消息存储量都是不一样的,如果有些生产者产生的消息远多于其他生产者的话,那么会导致不同的Broker接收到的消息总数差异巨大,同时,生产者也无法实时感知到Broker的新增和删除。

(2) 使用Zookeeper进行负载均衡,由于每个Broker启动时,都会完成Broker注册过程,生产者会通过该节点的变化来动态地感知到Broker服务器列表的变更,这样就可以实现动态的负载均衡机制。

4.消费者负载均衡

与生产者类似,Kafka中的消费者同样需要进行负载均衡来实现多个消费者合理地从对应的Broker服务器上接收消息,每个消费者分组包含若干消费者,每条消息都只会发送给分组中的一个消费者,不同的消费者分组消费自己特定的Topic下面的消息,互不干扰。

5、分区 与 消费者 的关系

消费组 (Consumer Group):
consumer group 下有多个 Consumer(消费者)。
对于每个消费者组 (Consumer Group),Kafka都会为其分配一个全局唯一的Group ID,Group 内部的所有消费者共享该 ID。订阅的topic下的每个分区只能分配给某个 group 下的一个consumer(当然该分区还可以被分配给其他group)。
同时,Kafka为每个消费者分配一个Consumer ID,通常采用"Hostname:UUID"形式表示。

在Kafka中,规定了每个消息分区 只能被同组的一个消费者进行消费,因此,需要在 Zookeeper 上记录 消息分区 与 Consumer 之间的关系,每个消费者一旦确定了对一个消息分区的消费权力,需要将其Consumer ID 写入到 Zookeeper 对应消息分区的临时节点上,例如:

/consumers/[group_id]/owners/[topic]/[broker_id-partition_id]

其中,[broker_id-partition_id]就是一个 消息分区 的标识,节点内容就是该 消息分区 上 消费者的Consumer ID

6、消息 消费进度Offset 记录

在消费者对指定消息分区进行消息消费的过程中,需要定时地将分区消息的消费进度Offset记录到Zookeeper上,以便在该消费者进行重启或者其他消费者重新接管该消息分区的消息消费后,能够从之前的进度开始继续进行消息消费。Offset在Zookeeper中由一个专门节点进行记录,其节点路径为:

/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id]

节点内容就是Offset的值。

7、消费者注册

消费者服务器在初始化启动时加入消费者分组的步骤如下

注册到消费者分组。每个消费者服务器启动时,都会到Zookeeper的指定节点下创建一个属于自己的消费者节点,例如/consumers/[group_id]/ids/[consumer_id],完成节点创建后,消费者就会将自己订阅的Topic信息写入该临时节点。

对 消费者分组 中的 消费者 的变化注册监听。每个 消费者 都需要关注所属 消费者分组 中其他消费者服务器的变化情况,即对/consumers/[group_id]/ids节点注册子节点变化的Watcher监听,一旦发现消费者新增或减少,就触发消费者的负载均衡。

对Broker服务器变化注册监听。消费者需要对/broker/ids/[0-N]中的节点进行监听,如果发现Broker服务器列表发生变化,那么就根据具体情况来决定是否需要进行消费者负载均衡。

进行消费者负载均衡。为了让同一个Topic下不同分区的消息尽量均衡地被多个 消费者 消费而进行 消费者 与 消息 分区分配的过程,通常,对于一个消费者分组,如果组内的消费者服务器发生变更或Broker服务器发生变更,会发出消费者负载均衡。