文章目录

  • 重要概念
  • broker
  • topic
  • partition
  • segment
  • offset
  • replica
  • producer
  • consumer
  • Consumer group
  • leader 与 follower
  • controller
  • coordinator
  • kafka的重要配置
  • boker相关
  • producer(生产者相关)
  • consumer(消费者相关)
  • replica(副本相关)
  • log(日志相关)
  • controller
  • 总结


重要概念

broker

一个broker就是一个kafka实例,负责接收、转发、存储消息,kafka集群就是由多个broker组成。

topic

kafka的topic是一个逻辑概念,就是对消息分组、分类,便于区分处理不同业务逻辑的消息。topic和Elasticsearch中的索引概念比较像。

partition

kafka的partition是物理上的概念,对应的是文件系统中的一个文件夹,partition是针对topic的,主要是为了考虑到topic数据量很多的情况,通过对topic的数据拆分为partition可以并行处理,提高并发量。

kafka中的partition和Elasticsearch中的shard比较像,都是对统一逻辑分类的数据进行物理拆分。

segment

kafka的partition对应的是文件夹,稍微思考就会发现,消息存储应该是文件,那么kafka存储消息的文件叫什么呢?

答案是segment,很多地方将其翻译为段。

segment是对kafka的topic进一步物理拆分,通过根据实际的机器情况合理的配置segment大小,配合上kafka自己索引机制,可以更快的执行读取和写入操作。

offset

offset是消息偏移量,这个偏移量是消息条数,而不是字节

replica

replica就是副本,基本所有分布式中间件都有副本这个概念

kafka的副本是针对partition的,而不是针对topic的

在kafka集群中通过将partition的不同副本分布在不同的broker上来提高可用性,一个broker挂了,还有其他的副本可用。

副本都有两个重要的属性:LEO 和 HW

Log End Offset(LEO):log中的下一条消息的 offset
High Watermark(HW):所有副本中最小的LEO

为什么所有副本中最小的LEO,反而叫高水位(HW)呢?

主要是因为Kafka不允许消费者消费大于所有副本中最小的LEO消息,所以就叫高水位(HW)

这样做主要是为了数据出现不一致的情况,例如Leader中的LEO比较大,然后挂了,其他副本成为了Leader

producer

消息生产者,发布消息到kafka集群的服务

consumer

消息消费者,从kafka集群中消费消息的服务

Consumer group

Consumer group是high-level consumer API中的概念,每个consumer都属于一个 consumer group,每条消息只能被 consumer group 的一个 Consumer 消费,但可以被多个consumer group 消费。

通过设置Consumer group,可以实现一条消息被不同的组消费,非常实用,例如一个登录消息,可能数据统计业务和活动业务都需要,那么只需要设置不同的Consumer group,就都可以消费到同一个登录消息。

leader 与 follower

副本有2中角色,一种是leader,另一种是follower。

相同副本中只有一个leader,其他副本为follower。

producer和consumer只跟leader交互,然后由leader和follower交互。

例如producer发消息给leader,leader将消息转发给follower,会根据producer的ack配置执行不同的响应,后面详细介绍。

controller

controller是针对broker的,kafka集群中的broker会选举出一个leader用来控制partition的leader选举、failover等操作,broker选举出来的leader就是controller。

broker的选举依赖于Zookeeper,Broker节点一起去Zookeeper上注册一个临时节点,因为只有一个Broker会注册成功,其他的都会失败,成功在Zookeeper上注册临时节点的Broker会成为controller,其他的broker叫Broker follower。

controller会监听其他的Broker的所有信息,如果controller宕机了,在zookeeper上面的那个临时节点就会消失,此时所有的broker又会一起去Zookeeper上注册一个临时节点,因为只有一个Broker会注册成功,其他的都会失败,所以这个成功在Zookeeper上注册临时节点的这个Broker会成为新的Controller。

一旦有一个broker宕机了,controller会读取该宕机broker上所有的partition在zookeeper上的状态,并选取ISR列表中的一个replica作为partition leader。

如果ISR列表中的replica全挂,选一个幸存的replica作为leader;
如果该partition的所有的replica都宕机了,则将新的leader设置为-1,等待恢复,等待ISR中的任一个Replica恢复,并且选它作为Leader;或选择第一个活过来的Replica,不一定是ISR中的作为Leader。

broker宕机的事情,controller也会通知zookeeper,zookeeper会通知其他的broker。

broker的脑裂问题:
controller在Zookeeper上注册成功后,它和Zookeeper通信的timeout默认值是6s,也就是如果controller如果有6s中没有和Zookeeper做心跳,那么Zookeeper就认为controller已经死了。

就会在Zookeeper上把这个临时节点删掉,那么其他broker就会认为controller已经没了,就会再次抢着注册临时节点,注册成功的broker成为controller。

然后,之前的controller就需要各种shut down去关闭各种节点和事件的监听。但是当kafka的读写流量都非常巨大的时候,这个时候producer进来的message由于Kafka集群中存在两个controller而无法落地,导致数据淤积。

coordinator

Group Coordinator是一个服务,每个Broker在启动的时候都会启动一个该服务。

Group Coordinator的作用是用来存储Group的相关Meta信息,并将对应Partition的Offset信息记录到Kafka的__consumer_offsets这个topic中。

Kafka在0.9之前是基于Zookeeper来存储Partition的Offset信息(consumers/{group}/offsets/{topic}/{partition}),因为ZK并不适用于频繁的写操作,所以在0.9之后通过内置Topic的方式来记录对应Partition的Offset。

kafka的重要配置

boker相关

#在集群中的唯一标识broker,非负数
broker.id=1

#broker server服务端口
port=9091

#kafka数据的存放地址,多个地址的话用逗号分割D:\\data11,D:\\data12
log.dirs=D:\\kafkas\\datas\\data1

#ZK集群的地址,可以是多个,多个之间用逗号分割hostname1:port1,hostname2:port2
zookeeper.connect=localhost:2181

#ZK连接超时时间
zookeeper.connection.timeout.ms=6000

#ZK会话超时时间
zookeeper.session.timeout.ms=6000

#segment日志的索引文件大小限制,会被topic创建时的指定参数覆盖
log.index.size.max.bytes =10*1024*1024

#segment的大小,达到指定大小会新创建一个segment文件,会被topic创建时的指定参数覆盖
log.segment.bytes =1024*1024*1024

# broker接受的消息体的最大大小
message.max.bytes =	1000012

#broker处理消息的最大线程数
num.network.threads=3

#broker处理磁盘IO的线程数
num.io.threads=8

#socket的发送缓冲区
socket.send.buffer.bytes=102400
#socket的接受缓冲区
socket.receive.buffer.bytes=102400
#socket请求的最大数值,message.max.bytes必然要小于socket.request.max.bytes
socket.request.max.bytes=104857600

#topic默认分区个数,会被topic创建时的指定参数覆盖
num.partitions=1

#partition副本数量配置,默认1,表示没有副本,2表示除了leader还有一个follower
default.replication.factor =1

#是否允许自动创建topic,若是false,就需要手动创建topic
auto.create.topics.enable =true

producer(生产者相关)

# 0不管消息是否写入成功,1只需要leader写入消息成功,all需要leader和ISR中的follower都写入成功
acks = 1

#设置生产者内存缓冲区的大小,生产者用它缓冲要发送到服务器的消息。
#如果应用程序发送消息的速度超过发送到服务器的速度,会导致生产者空间不足。这个时候,send()方法调用要么被阻塞,要么抛出异常
buffer.memory = 10240

# 当buffer.memory不足,阻塞多久抛出异常
max.block.ms = 3000

# 默认消息发送时不会被压缩。可设置为snappy、gzip、lz4
compression.type = snappy

# 重试次数
retries = 0

# 重试时间间隔
retry.backoff.ms = 100

# 发向相同partition每个批次的大小,默认16384
batch.size = 10240

# batch.size要产生消息比发送消息快才会出现
# linger.ms可以控制让发送等n毫秒再发送,以达到批量发送的目的
linger.ms = 0

# 控制生产者每次发送的请求大小,默认1M
max.request.size = 	1048576

# 指定了生产者在收到服务器响应之前可以发送多少个消息
max.in.flight.requests.per.connection = 1

# tcp缓冲区大小
receive.buffer.bytes = 4096
send.buffer.bytes = 4096

snappy压缩算法占用较少的CPU,有较好的性能和压缩比
gzip压缩算法占用较多CPU,但会提供更高的压缩比

max.in.flight.requests.per.connection导致消息顺序问题,如果:retries>0 && max.in.flight.requests.per.connection >1:
那么,如果第一个批次消息写入失败,而第二个批次写入成功,会重试写入第一个批次,如果此时第一个批次也写入成功,那么两个批次的顺序就反过来了。

max.in.flight.requests.per.connection=1,即使发生了重试,也可以保证消息是按照发送的顺序写入。

consumer(消费者相关)

# broker服务器列表
bootstrap.servers=localhost:9092,localhost:9093,localhost:9094

# 消费者每次poll数据时的最大数量
max.poll.records = 500

# 为true则自动提交偏移量
enable.auto.commit = true

# 自动提交偏移量周期(时间间隔)
auto.commit.interval.ms = 5000

# 如果该配置时间内consumer没有响应Coordinator的心跳检测,就认为consumer挂了,rebalance
session.timeout.ms = 10000

# Coordinator的心跳检测周期
heartbeat.interval.ms = 2000

# 当没有初始偏移量时,怎么办,默认latest
# earliest: 自动重置为最早的offset
# latest: 自动重置为最后的offset
# none: 如果在消费者组中没有前置的offset,抛异常
auto.offset.reset=latest

# 一次最小拉取多少字节,默认1字节
fetch.min.bytes=1

# 一次最多拉取多少字节数据,默认50M
fetch.max.bytes=52428800

# 一次拉取最多等待多少毫秒,默认500
fetch.max.wait.ms=500

replica(副本相关)

#leader等待follower的最常时间,超过就將follower移除ISR(in-sync replicas)
replica.lag.time.max.ms =10000

#follower最大落后leader多少条消息,把此replicas迁移到其他follower中,在broker数量较少,或者网络不足的环境中,建议提高此值
replica.lag.max.messages =4000

#follower与leader之间的socket超时时间
replica.socket.timeout.ms=30*1000

#leader复制时候的socket缓存大小
replica.socket.receive.buffer.bytes=64*1024

#replicas每次获取数据的最大大小
replica.fetch.max.bytes =1024*1024

#replicas同leader之间通信的最大等待时间,失败了会重试
replica.fetch.wait.max.ms =500

#fetch的最小数据尺寸,如果leader中尚未同步的数据小于该值,将会阻塞,直到满足条件
replica.fetch.min.bytes =1

#leader进行复制的线程数,增大这个数值会增加follower的IO
num.replica.fetchers=1

log(日志相关)

#segment文件大小,会被topic创建时的指定参数覆盖
log.segment.bytes =1024*1024*1024

#segment滚动时间,没有达到log.segment.bytes也会强制新建一个segment,topic参数覆盖
log.roll.hours =24*7

#日志清理策略选择有:delete和compact主要针对过期数据的处理
log.cleanup.policy = delete

#数据存储的最大时间超过这个时间会根据log.cleanup.policy设置的策略处理数据
log.retention.minutes=6000

#topic每个分区大小,一个topic的大小限制=分区数*log.retention.bytes,-1没有大小
log.retention.bytes=-1
    
#文件大小检查的周期时间
log.retention.check.interval.ms=50000
    
#是否开启日志清理,默认true
log.cleaner.enable=true

#日志清理的线程数
log.cleaner.threads = 2

#日志清理时候处理的最大大小
log.cleaner.io.max.bytes.per.second=None

#日志清理去重时候的缓存空间,在空间允许的情况下,越大越好
log.cleaner.dedupe.buffer.size=500*1024*1024
    
#日志清理时候用到的IO块大小一般不需要修改
log.cleaner.io.buffer.size=512*1024

#值越大一次清理越多,hash冲突也越严重
log.cleaner.io.buffer.load.factor=0.9

#检查是否有需要清理的日志间隔
log.cleaner.backoff.ms =15000

#日志清理的频率控制,越大意味着更高效的清理,同时会存在一些空间上的浪费,topic参数覆盖
log.cleaner.min.cleanable.ratio=0.5

#对于压缩的日志保留的最长时间,会被topic创建时的指定参数覆盖
log.cleaner.delete.retention.ms =100000

#对于segment日志的索引文件大小限制,会被topic创建时的指定参数覆盖
log.index.size.max.bytes =10*1024*1024

#索引的offset间隔,设置越大,扫描速度越快,但是也更吃内存
log.index.interval.bytes =4096

#多少条消息,执行一次刷新到磁盘操作
log.flush.interval.messages=9223372036854775807

#多少毫秒之后刷新到磁盘一次,没有设置使用log.flush.scheduler.interval.ms
log.flush.interval.ms = null

#检查是否需要刷新到磁盘的时间间隔
log.flush.scheduler.interval.ms =3000

#文件在索引中清除后保留的时间一般不需要去修改
log.delete.delay.ms =60000

#控制上次落盘的时间点,以便于数据恢复
log.flush.offset.checkpoint.interval.ms =60000

log.cleanup.policy参数控制日志清楚,默认是删除,可以将log.cleanup.policy参数设置为"delete,compact"

这里的compact并不是压缩,而是针对每个消息的key进行整合,对于有相同key的的不同value值,只保留最后一个版本。

注意compact和compression的区别,compact更像是内存回收的标记整理,compression才是压缩的意思,kafka中的压缩是针对消息内容的。

kafka的删除可以基于3中:

  1. 基于时间
  2. 基于大小
  3. 基于偏移量

log.retention.hours、log.retention.minutes以及log.retention.ms来配置,其中
基于时间的配置,优先级从高到低:

  1. log.retention.ms
  2. log.retention.minutes
  3. log.retention.hours

默认情况下只配置了log.retention.hours参数,其值为168,故默认情况下日志分段文件的保留时间为7天。

基于大小的删除通过log.retention.bytes参数控制,默认是-1,没有大小限制。

log.retention.bytes和log.retention.minutes任意一个达到要求,都会执行删除,会被topic创建时的指定参数覆盖

kafka每次清理日志之后会对segment进行合并操作,合并之后大小不超过log.segments.bytes配置,默认1GB。

controller

#是否允许关闭broker,若是设置为true,会关闭所有在broker上的leader,并转移到其他broker
controlled.shutdown.enable=false

#控制器关闭的尝试次数
controlled.shutdown.max.retries=3

#每次关闭尝试的时间间隔
controlled.shutdown.retry.backoff.ms=5000
    
#partition leader与replicas之间通讯时,socket的超时时间
controller.socket.timeout.ms =30000

#partition leader与replicas数据同步时,消息的队列尺寸
controller.message.queue.size=10

controlled.shutdown.enable=true主要是为了优雅的关闭:

  1. 可以加速重新启动
  2. 让leader更快切换、并且将每个partition不可用的时间降低到几毫秒

总结

kafka中partition和broker的关系 kafka的partition和broker的配比_kafka