kafka消息可靠性问题


这里涉及两个方面,一个是消息的发送可靠性,另一个是消息的接收可靠性。

给出列表之后,我们从两个方面来探讨一下数据为什么会丢失:

1. Producer端

  目前比较新版本的Kafka正式替换了Scala版本的old producer,使用了由Java重写的producer。新版本的producer采用异步发送机制。KafkaProducer.send(ProducerRecord)方法仅仅是把这条消息放入一个缓存中(即RecordAccumulator,本质上使用了队列来缓存记录),同时后台的IO线程会不断扫描该缓存区,将满足条件的消息封装到某个batch中然后发送出去。显然,这个过程中就有一个数据丢失的窗口:若IO线程发送之前client端挂掉了,累积在accumulator中的数据的确有可能会丢失。

  Producer的另一个问题是消息的乱序问题。假设客户端代码依次执行下面的语句将两条消息发到相同的分区

producer.send(record1);
producer.send(record2);

如果此时由于某些原因(比如瞬时的网络抖动)导致record1没有成功发送,同时Kafka又配置了重试机制和max.in.flight.requests.per.connection大于1(默认值是5,本来就是大于1的),那么重试record1成功后,record1在分区中就在record2之后,从而造成消息的乱序。很多某些要求强顺序保证的场景是不允许出现这种情况的。

  鉴于producer的这两个问题,我们应该如何规避呢??对于消息丢失的问题,很容易想到的一个方案就是:既然异步发送有可能丢失数据, 我改成同步发送总可以吧?比如这样:

producer.send(record).get();

这样当然是可以的,但是性能会很差,不建议这样使用。因此特意总结了一份配置列表。个人认为该配置清单应该能够比较好地规避producer端数据丢失情况的发生:(特此说明一下,软件配置的很多决策都是trade-off,下面的配置也不例外:应用了这些配置,你可能会发现你的producer/consumer 吞吐量会下降,这是正常的,因为你换取了更高的数据安全性)

  • block.on.buffer.full = true  尽管该参数在0.9.0.0已经被标记为“deprecated”,但鉴于它的含义非常直观,所以这里还是显式设置它为true,使得producer将一直等待缓冲区直至其变为可用。否则如果producer生产速度过快耗尽了缓冲区,producer将抛出异常
  • acks=all  很好理解,所有follower都响应了才认为消息提交成功,即"committed"
  • retries = MAX 无限重试,直到你意识到出现了问题:)
  • max.in.flight.requests.per.connection = 1 限制客户端在单个连接上能够发送的未响应请求的个数。设置此值是1表示kafka broker在响应请求之前client不能再向同一个broker发送请求。注意:设置此参数是为了避免消息乱序
  • 使用KafkaProducer.send(record, callback)而不是send(record)方法   自定义回调逻辑处理消息发送失败
  • callback逻辑中最好显式关闭producer:close(0) 注意:设置此参数是为了避免消息乱序
  • unclean.leader.election.enable=false   关闭unclean leader选举,即不允许非ISR中的副本被选举为leader,以避免数据丢失
  • replication.factor >= 3   这个完全是个人建议了,参考了Hadoop及业界通用的三备份原则
  • min.insync.replicas > 1 消息至少要被写入到这么多副本才算成功,也是提升数据持久性的一个参数。与acks配合使用
  • 保证replication.factor > min.insync.replicas  如果两者相等,当一个副本挂掉了分区也就没法正常工作了。通常设置replication.factor = min.insync.replicas + 1即可

2. Consumer端

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

  • enable.auto.commit=false  关闭自动提交位移
  • 在消息被完整处理之后再手动提交位移

一、消息发送的可靠性

kafka 新版java client使用的是异步方式发送消息,即消息提交给KafkaProducer的send方法后,实际上是将该消息放入了它本身的一个后台发送队列,然后再有一个后台线程不断地从队列中取出消息进行发送,发送成功后会回调send方法的callback(如果没有,就不用回调了)。

所以从以上的流程来看,kafka客户端的发送流程是一个异步化的流程。

kafka客户端会累积一定量的消息后统一组装成一个批量消息发出,这个的触发条件是: 消息量达到了batch.size的大小或者等待批量的时间超过了linger.ms时间。

此外还要注意一下发送方消息的堆积问题,当程序的发送速率大于发送到broker的速率时,会产生消费在发送方堆积,堆积的策略控制主要由参数buffer.memory 以及max.block.ms,buffer.memory设置了可使用的buffer内存,max.block.ms是指在buffer满的情况下可以阻塞多长时间,超过这个时间则抛出异常。

所以综上所述要达到消息发送可靠性需要特别注意以下几点:

1、消息失败重试

设置失败重试的次数为一个很大的数值,如Integer.MAX_VALUE,对应properties的设置为:

配置    默认值  建议值

retries 0            Integer.MAX_VALUE 

2、消息异步转同步

对于消息异步转同步:使用future.get()等待消息发送返回结果,如:

Future future = producer.send(newProducerRecord("test.testTopic", "key","value"));

RecordMetadata metadata = future.get(); //等待发送结果返回

这种用法可能会导致性能下降比较厉害,也可以通过send(message,callback)的方式,在消息发送失败时通过callback记录失败并处理

3、消息批量转单条及发送等待

kafka默认情况下是批量发送,批量发送存在消息积累再发送的过程,为了达到消息send后立刻发送到broker的要求,对应properties设置:

配置                                                            默认值            建议值

max.in.flight.requests.per.connection   5                       1

其中max.in.flight.requests.per.connection以及retries主要应用于顺序消息场景,顺序场景中需要设置为:max.in.flight.requests.per.connection = 1 

综合以上配置示例:

Properties props = new Properties();

props.put("bootstrap.servers", "localhost:9092");

props.put("acks", "1"); //这里是只要求leader响应就OK,更高的要求则应该设置成"all"

props.put("retries", Integer.MAX_VALUE);

props.put("max.in.flight.requests.per.connection",1);

props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); //这里是key的序列化类

props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");//这里是value的序列化类

Producer producer = new KafkaProducer(props);

for (int i = 0; i < 1000000; i++) { 

                  Future future = producer.send(new ProducerRecord("test.testTopic","key","value")); 

                  RecordMetadata metadata = future.get(); //关键的这一步,同步等待发送完成

}

producer.close();

二、消息的可靠接收

新版的java客户端(0.10.0.0)已经变更接收线程为单线程接收处理。

同时客户端默认情况下是自动提交offset,这样可能存在消息丢失的可能性,比如客户端接收到一批消息并进行处理,在处理过程中达到了客户端offset定时提交的时间点,这批数据的offset被提交,但是可能这批数据的处理还没有结束,甚至这些数据可能还存在一些数据处理不了或者处理出错,甚至出现宕机的可能性,这时未处理的消息将会丢失,因为offset已经提交,下次读取会从新的offset处读取。

所以要保证消息的可靠接收,需要将enable.auto.commit设置为false,防止程序自动提交,应该由应用程序处理完成后手动提交。

示例:

Properties props = new Properties();

props.put("bootstrap.servers", "localhost:9092");

props.put("group.id", "testsub1");

props.put("enable.auto.commit", "false");

props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

KafkaConsumer consumer = new KafkaConsumer(props);

consumer.subscribe(Arrays.asList("test.testTopic","topic.test"));

while (true) { 

              ConsumerRecords records = consumer.poll(100); 

              for (ConsumerRecord record : records) {

                   //logger.info("Topic:{}, count:{}", record.topic(),counterMap.get(record.topic()).incrementAndGet()); 

                   logger.info(record.value()); 

               }

}

三、Broker消息的存储可靠性

1、刷盘时机 

broker的刷盘时机主要是以下两个参数控制: 

log.flush.interval.ms 日志刷盘的时间间隔,每隔多少时间将消息刷到磁盘上

 log.flush.interval.messages 日志刷盘的消息量,每积累多少条消息将消息刷到磁盘上 

2、副本数 

在创建消息Topic的时候需要指定消息的副本数 replicas 

一般建议设置成3保证消息的可靠,再结合客户端发送方的ack参数,当ack参数设置为0表示不等待broker响应就发送下一条消息,当ack设置为1则表示需要等待leader响应,当ack设置为all则表示需要等待所有的replicas都响应后才返回响应,其中all是最高可靠级别了,但是同时也降低了吞吐率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值