Kafka:保证消息不重复不丢失

4,359 阅读8分钟

类型

image.png

1/前言

首先要考虑这么几个问题:
    消息丢失是什么原因造成的,从生产端和消费端两个角度来考虑。
    消息重复是什么原因造成的,从生产端和消费端两个角度来考虑。
    如何保证消息有序?
    如果要保证消息不重不漏,需要付出的代价是什么?

下面是文章详情,这里先简单总结一下:
    消费端重复消费:很容易解决,建立去重表即可。
    消费端丢失数据:也很容易解决,关闭自动提交offset位移(enable.auto.commit=false),处理完消息之后手动提交位移。
    生产端重复发送:这个不重要,消费端消费之前从去重表中判重就可以。
    生产端丢失数据:这个是最麻烦、最头疼的情况。

解决策略:
  1/异步方式缓冲区满了,就阻塞在那,等着缓冲区可用,不能清空缓冲区
  2/发送消息之后回调函数,发送成功就发送下一条,发送失败就记在日志中,等着定时脚本来扫描错误日志。
(发送失败可能并不是真的发送失败,只是没收到反馈,定时脚本可能会重发)

2/kafka如何做到消息不丢失

分为3个方面:
  <1>生产者端不少生产消息
  <2>服务端(及kafka cluster集群)不丢失消息
  <3>消费者端不少消费消息

image.png

image.png

<1>如何保证生产者端不少生产消息(及在生产者端不丢失消息)

使用带有回调方法(callback)的api,并设置好参数acks和retries和retry.backoff.ms这几个参数
acks参数表示如何判断系统认为消息发送成功。
retries参数表示生产者生产消息的重试次数。
retry.backoff.ms参数表示消息生产超时失败后重试的间隔时间。
通过回调函数,我们可以知道消息是否发送成功。如果发送失败,我们需要进行异常处理。
比如把失败消息存入本地磁盘或者远程数据库,等服务正常了再发送。这样才能保证消息不丢失。

image.png

image.png

<2>如何保证服务端不丢失消息

设置好3个参数

image.png image.png image.png

<3>如何保证消费者端不少消费消息

 也容易解决,关闭自动提交offset位移(enable.auto.commit=false),处理完消息之后手动提交位移。

3/如何保证有序

如果生产者有一条消息发送失败了,那么后面的消息就不能继续发了,不然重发的那个肯定乱序了
生产者在收到发送成功的反馈之前,不能发下一条数据.
但我感觉生产者是一个流,是一个连续的过程,阻塞生产者感觉业务上不可行,怎么会因为一条消息发出去没收到反馈,就阻塞生产者呢?这样明显是不合理的。

同步发送模式:发出消息后,必须阻塞等待收到通知后,才发送下一条消息
异步发送模式:一直往缓冲区写,然后一把写到队列中去
两种都是各有利弊:
同步发送模式虽然吞吐量小,但是发一条收到确认后再发下一条,既能保证不丢失消息,又能保证顺序
异步发送消息,虽然吞吐量大,但是不能保证有序。

4/Kafka消息保证生产的信息不丢失和重复消费问题

1)使用同步模式的时候,有3种状态保证消息被安全生产,在配置为1(只保证写入leader成功)的话,如果刚好leader partition挂了,数据就会丢失。
2)还有一种情况可能会丢失消息,就是使用异步模式的时候,当缓冲区满了,如果配置为0(还没有收到确认的情况下,缓冲池一满,就清空缓冲池里的消息),数据就会被立即丢弃掉。

在数据生产时避免数据丢失的方法:
只要能避免上述两种情况,那么就可以保证消息不会被丢失。
1)在同步模式的时候,确认机制设置为-1,也就是让消息成功写入leader分区和所有的副本分区。
2)在异步模式下,如果消息发出去了,但还没有收到确认的时候,缓冲池满了,
   在配置文件中设置成不限制阻塞超时的时间,也就说让生产端一直阻塞,这样也能保证数据不会丢失。
   
在数据消费时,避免数据丢失的方法:如果使用了storm,要开启storm的ackfail机制;
如果没有使用storm,确认数据被完成处理之后,再更新offset值。低级API中需要手动控制offset值。


消息队列的问题都要从源头找问题,就是生产者是否有问题。

讨论一种情况,如果数据发送成功,但是接受response的时候丢失了,机器重启之后就会重发。

重发很好解决,消费端增加去重表就能解决,但是如果生产者丢失了数据,问题就很麻烦了。

数据重复消费的情况,如果处理
(1)去重:将消息的唯一标识保存到外部介质中,每次消费处理时判断是否处理过;
(2)不管:大数据场景中,报表系统或者日志信息丢失几条都无所谓,不会影响最终的统计分析结果

Kafka到底会不会丢数据(data loss)? 通常不会,但有些情况下的确有可能会发生。
下面的参数配置及Best practice列表可以较好地保证数据的持久性(当然是trade-off,牺牲了吞吐量)。笔者会在该列表之后对列表中的每一项进行讨论,有兴趣的同学可以看下后面的分析。
没有银弹,如果想要高吞吐量就要能容忍偶尔的失败(重发漏发无顺序保证)。

5/consumer端(消费者端)

consumer端丢失消息的情形比较简单:
    如果在消息处理完成前就提交了offset,那么就有可能造成数据的丢失。
由于Kafka consumer默认是自动提交位移offset的,所以在后台提交位移前一定要保证消息被正常处理了,
因此不建议采用很重的处理逻辑,如果处理耗时很长,则建议把逻辑放到另一个线程中去做。
为了避免数据丢失,现给出建议:
    enable.auto.commit=false,即关闭自动提交位移在消息被完整处理之后再手动提交位移

解决策略

1.异步方式缓冲区满了,就阻塞在那,等着缓冲区可用,不能清空缓冲区(一旦清空,数据就丢失了)
2.同步方式:发送消息之后回调函数(等待反馈),发送成功就发送下一条,发送失败就记在日志中,等着定时脚本来扫描(发送失败可能并不真的发送失败,只是没收到反馈,定时脚本可能会重发)

数据丢失情况:

1)使用同步模式的时候,有3种状态保证消息被安全生产,在配置为1(只保证写入leader成功)的话,如果刚好leader partition挂了,数据就会丢失。
2)还有一种情况可能会丢失消息,就是使用异步模式的时候,当缓冲区满了,如果配置为0(还没有收到确认的情况下,缓冲池一满,就清空缓冲池里的消息),数据就会被立即丢弃掉。

只要能避免上述两种情况,那么就可以保证消息不会被丢失。
1)就是说在同步模式的时候,确认机制设置为-1,也就是让消息写入leader和所有的副本。
2)还有,在异步模式下,如果消息发出去了,但还没有收到确认的时候,缓冲池满了,在配置文件中设置成不限制阻塞超时的时间,也就说让生产端一直阻塞,这样也能保证数据不会丢失。

ack应答机制

ack确认机制设置为0,表示不等待响应,不等待borker的确认信息,最小延迟,producer无法知道消息是否发生成功,消息可能丢失,但具有最大吞吐量。

ack确认机制设置为-1,也就是让消息写入leader和所有的副本,ISR列表中的所有replica都返回确认消息。
ack确认机制设置为1,leader已经接收了数据的确认信息,replica异步拉取消息,比较折中。
ack确认机制设置为2,表示producer写partition leader和其他一个follower成功的时候,broker就返回成功,无论其他的partition follower是否写成功。
ack确认机制设置为 "all" 即所有副本都同步到数据时send方法才返回, 以此来完全判断数据是否发送成功, 理论上来讲数据不会丢失。
min.insync.replicas=1 意思是至少有1个replica返回成功,否则product异常

<1>producer:

1、ack设置-1,及确保消息发送到leader和所有副本
2、设置副本同步成功的最小同步个数为副本数-1
3、加大重试次数
4、同步发送
5、对于单条数据过大,要设置可接收的单条数据的大小
6、对于异步发送,通过回调函数来感知丢消息,使用KafkaProducer.send(record, callback)方法而不是send(record)方法
7、配置不允许非ISR(In-Sync Replicas,副本同步队列)集合中的副本当leader。所有的副本(replicas)统称为 Assigned Replicas,即 AR
8、客户端缓冲区满了也可能会丢消息;或者异步情况下消息在客户端缓冲区还未发送,客户端就宕机
9、block.on.buffer.full = true