我们经常会遇到kafka数据丢失的问题,所以将遇到过的或有可能造成数据丢失的问题进行个小总结。
其实在kafka处理数据的流程有很多,把这些流程梳理一遍,有助于分析数据丢失的情况,从这个图中可以看出数据流向,图中涉及的所以过程都可能造成数据的丢失。
- 首先要确定是否有业务数据写入
- 再明确数据是在kafka之前就已经丢失还是消费端丢失数据的?
2.1 如果是在写入端丢失数据,那么每次结果应该完全一样(在写入端没有问题的前提下)。
2.2 如果是在消费端丢失数据,那么换个消费group重新消费,多次消费结果完全一模一样的几率很低。
生产阶段
丢失问题:若生产时是同步模式,那么消息一旦生产,就会阻塞到直到收到server端的确认。但异步模式下,消息不会立刻server端,而是在客户端的缓冲区中进行缓存,缓存到指定大小或指定时间后,再发送给server。
所以,若在异步模式下,业务已经生产了数据,但还没来得及发送给server端时,server端就crash了,并且在重传周期内,server端一直未恢复,那么此消息就会丢失。
性能问题:选择同步模式,虽然很稳妥,但是每次都需要leader再向客户端发送确认。这个会降低发送速度。但选用异步模式,虽然速度是加快了,但是无法保证准确到达,缓存的越多,到达时间会变的久一些,并且缓存越大,发送也可能会变更慢。
优化:在producer端有几个参数会控制发送大小和重传的参数。
- batch.num.messages:每次批量发送给每个paritition消息的数量(只对 asyc 起作用)
- request.required.acks:0 表示 producer 不需要等待 leader 的确认,1 代表需要 leader 确认写入它的本地 log 并立即确认,-1 代表所有的备份都完成后确认(只对 async 起作用)
- queue.buffering.max.ms,默认值:5000,在 producer queue 的缓存的数据最大时间(只对 async 起作用)
- queue.buffering.max.message,默认值:10000,producer 缓存的消息的最大数量(只对 async 起作用)
- queue.enqueue.timeout.ms,默认值:-1,0 当 queue 满时丢掉,负值是 queue 满时 block, 正值是 queue 满时 block 相应的时间(只对 async 起作用)
- message.send.max.retries,默认值:3,消息发送最大尝试次数。
- retry.backoff.ms,默认值:300,每次尝试增加的额外的间隔时间。这个参数不建议在生产环境设的如此小。
broker处
- 即使kafka收到了消息,仍然可能丢失,因为kafka收到消息后,并不是立刻落盘,而是存在了缓存中,若在此阶段kafka异常或磁盘坏掉,那么此消息仍会丢失。
解决:修改kafka的配置参数,调整flush到文件的时间和条数
log.flush.interval.messages=10000
log.flush.interval.ms=3000 - 单批数据的长度超过kafka的限制时,会丢失数据,报kafka.common.MessageSizeTooLargeException异常。
message.max.bytes=20000000(broker能接收消息的最大字节数,这个值应该比消费端的fetch.message.max.bytes更小才对,否则broker就会因为消费端无法使用这个消息而挂起)
fetch.message.max.bytes=20485760
这条配置要符合 client的生产大小<server端可接受的大小<server端可发送的大小<client的消费大小
消费逻辑
- 在此阶段,会批量从server端读取数据,如果设置自动提交位移,那么有可能存在还未被业务侧读取,但offset已更新的情况,那么数据就会丢失:
enable.auto.commit:是否自动提交位移,如果为false,则需要在程序中手动提交位移。 - 关于性能优化
- 启动的消费线程数
- fetch.max.bytes:单次拉取数据的最大字节数量
- metadata_max_age_ms:强制进行metadata刷新的周期
- max.poll.records:单次 poll 调用返回的最大消息数,如果处理逻辑很轻量,可以适当提高该值。但是max.poll.records条数据需要在 metadata_max_age_ms这个时间内处理完
总结如下:
在考虑性能问题时,根据数据的特点和要求,需要考虑:
- 是单进程还是多进程生产
- 是同步还是异步生产,是否需要全部副本都确认
- 调整batch_size的大小,适当增大batch 大小可以来减小网络IO和磁盘IO的请求
- 是否需要多个partiton,分区是kafka进行并行读写的单位,是提升kafka速度的关键。
- 是否需要几个副本,副本越多,代价就是需要更多资源,尤其是磁盘资源,需要在副本数和可靠性之间平衡
- 是否需要开启压缩
- 消费时,是否需要多进程消费
Rebalance问题
在项目中时常遇到rebalance问题,单独小结一下:
触发rebalance的条件有三种:
- 组成员发生变更(新 Consumer 加入组、已有 Consumer 主动离开组或已有 Consumer 崩溃了)
- 订阅主题数发生变更
- 订阅主题的分区数发生变更
我们常遇到的就是consumer没有和server没有保持活跃,导致server认为此consumer已退出,所以需要rebalance。而此时offset还未提交,所以会有重复消费的问题。
而没有保持活跃的原因有多种:
1)处理的线程被kill
2)消费者消费太慢,导致超时
3)消费太快,导致消费者处于空poll的状态,阻塞发送心跳线程;
以上原因都会让server认为需要rebalance。
优化方案一个是:降低max_poll_records(默认500)或提高metadata_max_age_ms(默认5分钟强制刷新metadata)
另一个解决方案是修改heartbeat_interval_ms(默认3秒)和metadata_max_age_ms(默认5分钟)为差不多的大小,例如将metadata_max_age_ms修改为3s