300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > Kafka生产者 消费者的消息可靠性方案实现

Kafka生产者 消费者的消息可靠性方案实现

时间:2022-08-26 13:01:50

相关推荐

Kafka生产者 消费者的消息可靠性方案实现

以下代码基于SpringKafka 2.3.13.RELEASE + Boot 2.2.9.RELEASE 实现

Producer 消息的可靠性

实现方案:ack模式调整 + 重试机制 + 规避重试机制下带来的问题

spring.kafka:producer:#这个参数可以是任意字符串,它是broker用来识别消息是来自哪个客户端的。在broker进行打印日志、衡量指标或者配额限制时会用到。clientId: ${spring.application.name} #方便kafkaserver打印日志定位请求来源bootstrap-servers: 127.0.0.1:8080 #kafka服务器地址,多个以逗号隔开#acks=0:生产者把消息发送到broker即认为成功,不等待broker的处理结果。这种方式的吞吐最高,但也是最容易丢失消息的。#acks=1:生产者会在该分区的leader写入消息并返回成功后,认为消息发送成功。如果群首写入消息失败,生产者会收到错误响应并进行重试。这种方式能够一定程度避免消息丢失,但如果leader宕机时该消息没有复制到其他副本,那么该消息还是会丢失。另外,如果我们使用同步方式来发送,延迟会比前一种方式大大增加(至少增加一个网络往返时间);如果使用异步方式,应用感知不到延迟,吞吐量则会受异步正在发送中的数量限制。#acks=all:生产者会等待所有副本成功写入该消息,这种方式是最安全的,能够保证消息不丢失,但是延迟也是最大的。acks: all #默认值:1#当生产者发送消息收到一个可恢复异常时,会进行重试,这个参数指定了重试的次数。在实际情况中,这个参数需要结合retry.backoff.ms来使用,建议总的重试时间比集群重新选举leader的时间长,这样可以避免生产者过早结束重试导致失败。#另外需注意,当开启重试时,若未设置max.in.flight.requests.per.connection=1,则可能出现发往同一个分区的两批消息的顺序出错,比如,第一批发送失败了,第二批成功了,然后第一批重试成功了,此时两者的顺序就颠倒了。retries: 2 #发送失败时重试多少次,0=禁用重试(默认值)properties:retry-backoff-ms: 1000 #重试等待间隔#默认情况下消息是不压缩的,此参数可指定采用何种算法压缩消息,可取值:none,snappy,gzip,lz4。snappy压缩算法由Google研发,这种算法在性能和压缩比取得比较好的平衡;相比之下,gzip消耗更多的CPU资源,但是压缩效果也是最好的。通过使用压缩,我们可以节省网络带宽和Kafka存储成本。compressionType: "none" #如果不开启压缩,可设置为none(默认值),比较大的消息可开启。#当多条消息发送到一个分区时,Producer会进行批量发送,这个参数指定了批量消息大小的上限(以字节为单位)。当批量消息达到这个大小时,Producer会一起发送到broker;但即使没有达到这个大小,生产者也会有定时机制来发送消息,避免消息延迟过大。batch-size: 16384 #默认16K,值越小延迟越低,但是吞吐量和性能会降低。0=禁用批量发送#这个参数设置Producer暂存待发送消息的缓冲区内存的大小,如果应用调用send方法的速度大于Producer发送的速度,那么调用会阻塞一定(max.block.ms)时间后抛出异常。buffer-memory: 33554432 #缓冲区默认大小32M

生产者:

@Component//该类需要为多列类型@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public class ProducerFuture implements FailureCallback, SuccessCallback<SendResult<String, Object>> {private static final Logger logger = LoggerFactory.getLogger(ProducerFuture.class);@Resourceprivate KafkaTemplate<String, Object> kafkaTemplate;private String uniqueId;/*** 异步发送消息* @param topicLcs* @param value*/public void async(String topicLcs, String value) {uniqueId = MDC.get("UNIQUE_ID");logger.info("send {} data:{}", topicLcs, value);ListenableFuture<SendResult<String, Object>> listenableFuture = kafkaTemplate.send(topicLcs, value);listenableFuture.addCallback(this, this);}@Overridepublic void onFailure(Throwable ex) {MDC.put(UNIQUE_ID, uniqueId);//重试策略失败后,将进入该方法。在该重试方法中,可以做其他响应的业务逻辑,如告警、投放其他队列或落地失败的消息内容等logger.error("sendFailure:", ex);MDC.remove(UNIQUE_ID);}@Overridepublic void onSuccess(SendResult<String, Object> result) {MDC.put(UNIQUE_ID, uniqueId);logger.info("sendSuccess {} ", result.getRecordMetadata().topic() + result.getRecordMetadata().offset());MDC.remove(UNIQUE_ID);}}

启用重试机制后带来的问题

重试过程中,一条消息只会向同一个分区进行重试发送,所以在重试的机制下,也能保证消息的全局幂等性由于重试,可能导致消息在 Node 中的顺序和 Producer 发送时的顺序不一致。可以对max.in.flight.requests.per.connectio(限制每个连接(指客户端与 Node 之间的 per.connection 连接)最多缓存已发送但未收到响应的请求数,默认为5)设置为1,即可保证在重试机制下的消息顺序。

Consumer 消息的可靠性

实现方案:手动提交 offset + 重试机制 + 死信队列(告警) + 死信队列消息采用其他策略去处理消息

kafka配置:

#kafka配置,更多配置请参考:KafkaPropertiesspring.kafka:#消费者的配置,可参考:org.apache.kafka.clients.consumer.ConsumerConfigconsumer:#暂不用提供clientId,2.x版本可放出来,1.x有多个topic且concurrency>1会出现JMX注册时异常#clientId: ${spring.application.name} #方便kafkaserver打印日志定位请求来源# kafka集群bootstrap-servers: 127.0.0.1:8080 #kafka服务器地址,多个以逗号隔开#指定了当消费者第一次读取分区或者无offset时拉取那个位置的消息,可以取值为latest(从最新的消息开始消费),earliest(从最早的消息开始消费),none(如果无offset就抛出异常)autoOffsetReset: latest #默认值:latest#指定了消费者是否自动提交消费位移,默认为true。如果为true,你可能需要关注自动提交的时间间隔,该间隔由mit.interval.ms设置。enable-auto-commit: false#周期性自动提交的间隔,单位毫秒auto-commit-interval: 2000 #默认值:5000#参数允许消费者指定从broker读取消息时最小的Payload的字节数。当消费者从broker读取消息时,如果数据字节数小于这个阈值,broker会等待直到有足够的数据,然后才返回给消费者。对于写入量不高的主题来说,这个参数可以减少broker和消费者的压力,因为减少了往返的时间。而对于有大量消费者的主题来说,则可以明显减轻broker压力。fetchMinSize: 1 #默认值: 1#上面的fetch.min.bytes参数指定了消费者读取的最小数据量,而这个参数则指定了消费者读取时最长等待时间,从而避免长时间阻塞。这个参数默认为500ms。fetchMaxWait: 500 #默认值:500毫秒#这个参数控制一个poll()调用返回的记录数,即consumer每次批量拉多少条数据。maxPollRecords: 500 #默认值:500

配置类:

@Configurationpublic class Config {Logger logger = LoggerFactory.getLogger(Config.class);private final String error_topic="error_topic";@Beanpublic ConcurrentKafkaListenerContainerFactory listenerContainerFactory(ConsumerFactory consumerFactory, KafkaTemplate<String,Object> template) {ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory();factory.setConsumerFactory(consumerFactory);// 最大重试次数5次,每次间隔5s(该配置需要参考 Broker.leader 切换的平均时间去设置)SeekToCurrentErrorHandler seekToCurrentErrorHandler = new SeekToCurrentErrorHandler((consumerRecord, e) -> {logger.error("重试机制后异常,consumerRecord:{}", consumerRecord.toString(), e);//做其他业务操作,如记录异常信息到表,发送信息到其他的队列人工核对处理等template.send(error_topic,consumerRecord.toString());}, new FixedBackOff(5000, 5));// 批量获取消息的时候,使用该方式// SeekToCurrentBatchErrorHandler batchErrorHandler = new SeekToCurrentBatchErrorHandler();factory.setErrorHandler(seekToCurrentErrorHandler);//设置提交偏移量的方式 ,否则出现异常的时候, 会报错No Acknowledgment available as an argument, the listener container must have a MANUAL AckMode to populate the Acknowledgment.factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);return factory;}}

消费者:

@Componentpublic class ConsumerKafka {private Logger logger = LoggerFactory.getLogger(getClass());private static final String CONSUMER_GROUP_PREFIX = "MOCK-A-GROUP";@KafkaListener(topics = {"${kafka.topic.topic4Test}"}, groupId = CONSUMER_GROUP_PREFIX, containerFactory = "listenerContainerFactory")public void onMessage(ConsumerRecord<String,Object> consumerRecord, Acknowledgment acknowledgeMode) {//处理业务逻辑......acknowledgeMode.acknowledge();}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。