文章目录
- 重要概念
- 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中:
- 基于时间
- 基于大小
- 基于偏移量
log.retention.hours、log.retention.minutes以及log.retention.ms来配置,其中
基于时间的配置,优先级从高到低:
- log.retention.ms
- log.retention.minutes
- 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主要是为了优雅的关闭:
- 可以加速重新启动
- 让leader更快切换、并且将每个partition不可用的时间降低到几毫秒
总结