参考:
https://blog.csdn.net/wk52525/article/details/96985534
https://blog.csdn.net/fedorafrog/article/details/103957908
《深入理解Kafka:核心设计与实践原理总结》
Kafka作为当前热门的分布式消息队列,具有高性能、持久化、多副本备份、横向扩展能力的特点。
Kafka的基础不多,挤一挤,(然后弄完真的tm多)
Producer 生产者
Consumber 消费者
Broker 服务代理节点(kafka实例,类似于Hregion)
Topic
主题
kafka消息以topic为安慰进行归类
Partition
分区
将Topic中的数据拆分为多个分区进行存储
分区在存储层面可以看做为一个可追加的日志文件
消息在追加到分区时,会分配一个特定的偏移量offset,作为唯一key
Kafka通过Offset保证消息在分区内的顺序性,但只保证分区内有序
每条消息发送到broker前,会根据分区规则分配到具体的某个分区
Replica
多副本机制
一个分区会在多个副本中保存相同的消息
副本之间为一主多从
Leader副本负责读写操作,follower只会进行同步(主动拉取)
Leader副本故障时,会从follower中重新进行选举新的Leader
同步状态
分区中所有格副本都称为 AR Assigned Replicas
所有与Leader副本保持一定成都同步的副本(包括Leader)组成ISR(In-Sync Replicas)
同步失败,或者断开的副本组成 OSR(Out-of-Sync Replicas)
特殊偏移量
LEO
Log End Offset
标识当前分区下一条待写入消息的offset
HW
High Watermark
高水位,标识了一个特定的offset
消费者只能拉取这个offset之前的消费信息(不含HW)
所有副本都同步了的消息才能够被消费,所以HW取决于所有follower中同步最慢的分区的offset
消息发送步骤
配置生产者客户端参数及创建相应的生成者实例
Properties
KafkaProducer
构建待发送的信息
ProducerRecord
发送消息
send
flush
关闭生产者实例
close
参数
bootstrap.servers
设置kafka集群地址信息,并非需要所有的broker地址,它会在broker中获取到其他broker
key.serializer & value.serializer
将字节数组转换到对象的序列化器,填写全类名
发送模式
ack 0
发后即忘 fire-and-forget
只管往kafka发送而不关心消息是否正确到达,不对响应做任何处理
同步
sync
KafkaProducer.send()返回的是一个Future对象,使用Future.get来阻塞任务发送的结果,并对这个结果进行相应的处理。
异步
async
向send()返回的Future对象注册一个Callback回调函数
拦截器
实现ProducerInterceptor接口
configure() 在完成生产者配置时
onSend() 在调用send后,消息序列化和计算分区之前
onAcknowledgement() 消息被答应前,或者消息失败时
close() 生产者关闭时
序列化
自定义序列化器,使用Serializer接口实现
分区器
在消息发送Kafka之前,需要先计算出分区号,
默认使用DefaultPartitioner
采用MurmurHash2算法
自定义分区器:实现Partitioner接口,然后使用partitioner.class指定
整体架构
主线程KafkaProducer创建消息,通过可能的拦截器、序列化、分区器后缓存到消息累加器 RecordAccumulatro中。
消息在RecordAccumulator中被包装成ProducerBatch,以便Sender线程可以进行批量发送,缓存的消息发送过慢时,send()会被阻塞或抛出异常。
缓存的大小通过buffer.memory配置,阻塞时间通过max.block.ms配置
Kafka生产者客户端中,通过ByteBuffer实现消息内存的创建和释放,而RecordAccumulator内部有一个BufferPool来实现ByteBuffer的复用。
Sender从RecordAccumulator中获取缓存消息后,将ProducerBatch按Node进行分组,Node代表Broker节点。
也就是说sender指向具体broker节点进行消息的发送,从来不关注哪个分区,这里是应用逻辑层面到网络层面的转换。
Sender发往Kafka前,还会保存到InFlightRequests中,是为了缓存已经发送出去但还没收到相应的请求的,以Node进行分组。
每个链接最大缓存未相应的请求数通过max.in.flight.requests.per.connection 配置(默认5)
元数据的更新
InFlightRequests 可以获得 leastLoadedNode,即所有Node中负载最小的。leastLoadedNode一般用于元数据请求、消费者组传播协议等交互。
当客户端中没有需要使用到元数据信息或者超过metadata.max.age.ms没有更新元数据时,就会触发元数据更新操作。
重要的生产者参数
acks
用来制定分区中有多少个副本收到这条消息,生产者才会认为写入成功
1 leader写入即成功 默认
0 不需要等待服务器响应
-1/all ISR中所有副本都写入才算成功
max.request.size
限制客户端一次发送的消息最大值
1048576 1M
服务端也有相同配置
我看官网写的the number of 那就是一次性的条数?
retries
生产者重试次数 0
retry.backoff.ms
两次重试之间的间隔 100
compression.type
压缩格式 none
gzip、snappy、lz4
linger.ms
生产者发送ProducerBatch时,等待更多的消息加入时间 0
receive.buffer.bytes
Socket接受消息缓冲区的大小 32768 (32k)
send.buffer.bytes
Socket发送消息缓冲区的大小 131072 (128k)
request.timeout.ms
Producer等待请求响应的最长时间 30000ms
如果超时则会根据retries 和retry.backoff.ms 参数进行重试
这个值必须比replica.lag.time.max.ms大
消费者与消费组
每个分区只能被一个消费组的一个消费者在某一时刻进行消费
当同一个组中的消费者大于了分区数,则有的消费者干瞪眼
消费者是一个实际的应用实例,一般为一个进程
客户端开发
消费步骤
配置消费者客户端参数并创建KafkaConsumer实例
订阅主题
拉取消息并进行消费
提交消费位移
关闭实例
必要的参数配置
bootstrap.servers
集群broker地址清单
group.id
消费组名称
key.deserializer & value.deserializer
反序列化器
订阅主题和分区
subscribe() 订阅主题
assign() 订阅指定主题分区
通过partitionFor()方法先获取分区列表
unsubscribe
取消订阅
消息消费
poll()
返回的是所订阅的主题(分区)上的一组消息,可设定timeout参数来控制阻塞时间
位移提交
提交的offset 为 lastConsumedOffset +1
lastConsumedOffset 上一次poll拉取到的最后一条消息的offset
控制或关闭消费
pause() & resume()
暂停和回复某分区的消费
指定位移消费
seek()
指定offset消费
beginingOffsets() / endOffsets() / offsetesForTimes()
开头 、末尾、指定时间 的offset
再均衡
在subcribe()时,可以注册一个时间ConsumerRebalanceLisener接口的监听器
onPartionsRevoked()
消费者停止读取消息之后,再均衡开始之前
onPartitionsAssigned()
重新分配分区后,开始读取消费前
拦截器
实现ConsumerInterceptor接口
在poll()返回之前,会调用onConsume()方法,提交完offset后会调用onCommit()方法
多线程实现
kafkaProducer是线程安全的,但是kafkaConsumer是非线程安全的
acquire()方法可以检测当前是否只有一个线程在操作,否则抛出异常
推荐使用单线程进行消费,然后多线程处理
fetch.min.bytes
一次请求能拉取的最小数据量(默认1b)
fetch.max.bytes
一次请求能拉取的最大数据量(默认52428800b,50m)
fetch.max.wait.ms
与min.bytes有关,指定kafka拉取时的等待时间(默认500ms)
max.partition.fetch.bytes
从每个分区里返回Consumer的最大数据量(默认1048576b,1m)
max.poll.records
一次请求拉取的最大消息数(默认500)
connections.max.idle.ms
多久后关闭闲置连接,默认(540000,9分钟)
receive.buffer.bytes
Socket接收消息缓冲区的大小(默认65536,64k)
send.buffer.bytes
Socket发送消息缓冲区的大小(默认131072,128k)
request.timeout.ms
Consumer等待请求响应的最长时间(默认30000ms)
metadata.max.age.ms
元数据过期时间(默认30000,5分钟)
reconnect.backoff.ms
尝试重新连接指定主机前的等待时间(默认50ms)
retry.backoff.ms
尝试重新发送失败请求到指定主题分区的等待时间(默认100ms)
isolation.level
消费者的事务隔离级别(具体查看进阶篇:事务)
创建
broker设置auto.create.topics.enable=true时,生产者发送消息是会自动创建分区数为num.partitions (1),副本因子为default.replication.factor (1)的主题
可以通过 kafka-topics.sh 创建topic
kafka-topics.sh --zookeeper
手动分配副本
--replica-assignment 2:0:1,1:2:0,0:1:2
得到:
设定额外参数
--config
分区副本的分配
使用kafka-topics.sh 创建主题内部 分配逻辑按照机架信息划分两种策略:
未指定机架信息分配策略:
assignReplicasToBrokersRackUnaware()
指定机架分配策略:
assignReplicasToBrokersRackAware()
当创建一个Topics时,不管采用什么方式,其实都是在zk的broker/topics目录下创建与该主题对应的子节点,并写入分区副本的分配方案。
并且在/config/topics节点下创建爱你与该主题相关的子节点,并写入主题配置信息。
相关命令
查看:kafka-topics.sh脚本的 list、describe指令
修改:kafka-topics.sh脚本的 alter指令
配置管理:kafka-configs.sh脚本
删除:kafka-topics.sh脚本的 delete指令
KafkaAdminClient 可实现以调用API的方式对Kafka进行管理
Topics合法性
通过KafkaAdminClient创建主题可能不符合规范,可以在Broker端进行设置create.topics.policy.class.name 来制定一个验证类主题创建时的合法性,这个类需要实现CreateTopicPolicy接口,放入Kafka源码进行重新编译
优先副本 preferred replica/ preferred leader
优先副本即AR稽核中的第一个副本,当分区leader出现故障的时候,会直接使用有限副本左右新的leader
kafka-perferred-replica-election.sh可进行优先副本选举操作
分区重分配
将某节点上的分区副本迁移至其他节点:宕机迁移失效的副本、有计划下线节点迁移副本
下线前最好先关闭或重启此broker,保证不是leader节点,减少节点间的流量
向新增节点分配原有主题分区副本
集群中新增节点时,只有新创建的主题分区才有可能分配到新节点上,需要把老主题的分区分配到新节点上
使用 kafka-reassgin-partitions.sh
数据复制会占用额外的资源,如果重分配的量太大必然会产生严重的后果。
可以对副本间的流量加以限制来保证重分配期间整体服务不太受影响,可分别限制follower副本复制速度和leader副本传输速度
通过 kafka-config.sh 或者 kafka-reassign-paritions.sh 配置
broker级别:
follower/leader.replication.throttled.rate=N
topic级别:
follower/leader.replication.throttled.replicas=N
分区重分配过程中的临时限流策略
原AR会应用leader限流配置
分区移动的目的地会应用follower限流配置
重分配所需的数据复制完成后,临时限流策略会被移除
修改副本因子
通过kafka-reassign-partitions.sh配置
如何选择合适的分区数
性能测试工具
生产者性能测试:kafka-producer-perf-test.sh 脚本
消费者性能测试:kafka-consumer-perf-test.sh 脚本
分区数和吞吐量的关系
在一定限度内,吞吐量随分区数增加而上升,但由于磁盘、文件系统、I/O调度策略等影响,到一定程度时吞吐量会存在瓶颈或有所下降
考量因素
如果分区数过多,当集群中某个broker宕机,就会有大量分区需要进行leader角色切换,这个过程会耗费一定的时间,并且在此期间这些分区不可用。分区数越多,kafka的正常启动和关闭耗时也会越长,同时也会增加日志清理的耗时
建议将分区数设定为broker的倍数