Kafka Consumer和Producer开发
Producer
Producer做的工作
- 确认消息发送到的
partition
,相同key
映射到相同分区,无key轮询 - 寻找分区的
leader
所在的Kafka broker,Producer可以选择不同的策略来发送消息- 直接发送成功
- leader响应写入操作后成功
- ...
Producer异常
Producer发布消息后,不论同步异步,发生异常时都会通知到你。
异常可以分为可恢复异常
和不可恢复异常
两种,前者一般是一些瞬时异常,稍后会自己恢复,所以只需要简单的重试即可:
LeaderNotAvailableException
:分区的leader
副本不可用,通常出现在leader
换届选举期间NotControllerException
:当前controller
不可用,可能是controller
在经历新一轮选举NetworkException
:网络异常
可恢复异常的父类是
RetriableException
,发生这些异常时,Producer会在配置的重试次数内进行重试,直到超出该次数才抛给你
不可重试异常比如:
RecordTooLongException
SerializationException
KafkaException
Producer主要参数
acks
acks 指定了在给 producer 发送响应前, leader broker 必须要确保己成功写入该消息的副本数。
acks=0
,不等待任何消息确认,甚至无法保证消息被broker接收到,客户端不知道是否故障,每条消息返回的offset都是-1
acks=1
,leader写入消息到本地日志后立即响应,不等待follower
应答,如果follower
同步完成前leader
故障,还可能丢消息acks=all/acks=-1
,leader等待所有副本同步后应答给Producer,只要还有一个副本消息就不会丢失
该参数是在吞吐量和消息持久性之间做权衡
compression.type
Producer消息压缩类型,默认是none
,即不压缩。
压缩可以降低网络I/O,但会带来额外的CPU压力。需要注意的是,如果压缩类型和broker设置不同,那么broker也需要对该消息进行解压-重压缩的操作。
那样不就让这些数据必须拷贝到Kafka服务器的用户空间,并且对它进行内存操作了吗??
batch.size
Producer的batch大小,即批量发送消息的批大小,默认是16384
linger.ms
消息发送延时,即消息进入batch
后,即使batch
未满,也要在linger.ms
毫秒后发送
batch.size
和linger.ms
是吞吐量和消息延时的权衡
其它参数
buffer.memory
:Producer发送缓冲区大小retries
:瞬时故障的重试次数- 重试可能造成消息的重复发送,Consumer手动去重
- 重试可能造成消息的乱序
max.request.size
:消息请求的最大大小request.timeout.ms
:Producer认为多少毫秒内没有得到broker的确认,就认为消息请求超时,抛出TimeoutException
多线程操作Producer
- 多线程操作同一个KafkaProducer实例
- 实现简单,性能好
- 一旦KafkaProducer被破坏,所有线程都无法工作
- 多线程操作多个KafkaProducer实例
- 线程之间使用不同的Producer实例,不互相影响
- 可以独立做更细粒度的配置
- 需要较大内存分配开销
Consumer
消费者组
消费者使用一个消费者组名(即 group.id)来标记自己, topic 的每条消息都只会被发送到每个订阅它的消费者组的一个消费者实例上。Kafka使用消费者组的概念实现基于队列和基于发布订阅的模型,即生产者和消费者的映射关系(基于队列是一对一映射,基于发布订阅是一对多映射)。
- 队列模式:所有consumer都放到一个group中
- 发布订阅:consumer放到不同的group中
Consumer组重平衡:组内多个consumer
可以同时读取Kafka消息,一旦其中一个挂了,consumer group
会立即将已崩溃的consumer
负责的分区转交给其它consumer
负责。
位移(Offset)
和大多数消息系统不一样,Kafka broker不会记录消费者消费了多少条消息,而是由消费者自己记录Offset,这带来了一些好处
- broker无状态,副本间同步成本低
- consumer消费成功后不再需要向broker发出ack,如消费失败,它不更新offset即可
- broker无需维护复杂的数据结构
需要注意:
- consumer会使用检查点机制将offset持久化
- consumer需要保存它所订阅的全部topic的分区的offset,可以用一个类似map的结构来存储
位移提交
consumer定期向Kafka集群汇报自己的位移,目前还不知道汇报是为了啥,因为都说了broker不保存这个状态。
旧版本的consumer将唯一信息提交到Zookeeper的固定节点上,而新版本的则提交到Kafka的内部topic——__consumer_offsets
上。__consumer_offset
有50个,不然的话,所有consumer都向同一个topic
写入自己的进度,负载全部堆到了一个topic上。
重平衡(rebalance)
Topic有很多partitions,消费者组中的consumer和这些partitions的对应关系是什么呢?
Kafka是本着平均分配的原则将分区分配给组中所有consumer进行消费的,这意味着如果当前分区数和消费者组中的消费者数量一致,那么每个消费者将消费一个分区。
当消费者组中的消费者数量变动,客户端就会启用重平衡算法,将分区尽量平均的分配到组内每个消费者上。
Consumer主要参数
必要参数group.id
除了bootstrap.servers
、key.deserializer
和value.deserializer
,还有一个group.id
是必须指定的。
session.timeout.ms
消费者会周期性的发送心跳到它的group coordinator,若到达这个时间还没有收到心跳,broker就将该消费者从它所在分组移除,并启动重平衡。
每一个Consumer Group都会选择一个broker作为自己的group coordinator
该参数在老版本的Kafka中好像还有其它的含义,但现在就是这样
max.poll.interval.ms
消费者两次poll之间的最大间隔,也就是你poll到一条消息之后执行业务所能够花费的最大时间。超过这个时间,消费者会被踢出它的group,并且group重平衡。
heartbeat.interval.ms
发送心跳间隔时间,在当前的网络配置情况下越快越好。
broker决定要让group重平衡时,它是通过heartbeat的response中的字段来下发这个通知的,所以说它越快就能让group更快的响应这次rebalance。
发送心跳的间隔时间(heartbeat.interval.ms
)必须小于心跳的超时时间session.timeout.ms
,否则consumer还没来得及发送心跳,broker就认为它已经死了。
auto.offset.reset
消费者消费时,若它没指定位移信息或者该位移信息不在当前消息日志的合理区间范围内,该选项决定了要执行的策略。
earliest
:自动将位移重置为最早的位移latest
:自动将位移重置为最新的位移none
:抛出异常
connections.max.idle.ms
Kafka会定期的关闭空闲的Socket,该参数是Socket被判定为空闲状态的依据,默认是9分钟。
其它参数
enable.auto.commit
:consumer是否自动提交位移fetch.max.bytes
:consumer单次获取的最大字节数max.poll.records
:consumer单次获取的最大消息条数(因为broker有批量返回的行为)