spring-kafka几种AckMode模式介绍,MANUAL_IMMEDIATE和MANUAL区别

kafka每个partition都有自己的offset,消费端处理完后要向kafka服务器提交offset。
spring-kafka组件有下面几种AckMode提交模式:都是针对.enable-auto-commit设置为false才适用,如果true是kafka根据自身配置来提交的。默认AckMode是BATCH
在这里插入图片描述
在这里插入图片描述

模式描述
MANUALpoll()拉取一批消息,处理完业务后,手动调用Acknowledgment.acknowledge()先将offset存放到map本地缓存,在下一次poll之前从缓存拿出来批量提交
MANUAL_IMMEDIATE每处理完业务手动调用Acknowledgment.acknowledge()后立即提交
RECORD当每一条记录被消费者监听器(ListenerConsumer)处理之后提交
BATCH当每一批poll()的数据被消费者监听器(ListenerConsumer)处理之后提交
TIME当每一批poll()的数据被消费者监听器(ListenerConsumer)处理之后,距离上次提交时间大于TIME时提交
COUNT当每一批poll()的数据被消费者监听器(ListenerConsumer)处理之后,被处理record数量大于等于COUNT时提交
COUNT_TIMETIME或COUNT满足其中一个时提交

MANUAL和MANUAL_IMMEDIATE模式要将spring.kafka.consumer.enable-auto-commit设置为false

@KafkaListener注解

@KafkaListener(
            topics = "test",
            groupId = "testGroup"
    )
    public void listener(ConsumerRecord<String, String> record, Acknowledgment ack) {
    // 处理业务
    ack.acknowledge();
    }

如果spring.kafka.consumer.enable-auto-commit=true,方法参数不能包含Acknowledgment ack,否则会报错。

@KafkaListener(
            topics = "test",
            groupId = "testGroup"
    )
    public void listener(ConsumerRecord<String, String> record) {
    // 处理业务
    // 不用手动提交
    }

org.springframework.kafka.listener.KafkaMessageListenerContainer.ListenerConsumer#run
项目启动后这个run方法会不停从kafka服务器broke拉取(poll)消息回来消费

while (isRunning()) {
				try {
					if (!this.autoCommit && !this.isRecordAck) {
						processCommits();
					}
					processSeeks();
					ConsumerRecords<K, V> records = this.consumer.poll(this.containerProperties.getPollTimeout());
					this.lastPoll = System.currentTimeMillis();

					if (records != null && this.logger.isDebugEnabled()) {
						this.logger.debug("Received: " + records.count() + " records");
					}
					if (records != null && records.count() > 0) {
						if (this.containerProperties.getIdleEventInterval() != null) {
							lastReceive = System.currentTimeMillis();
						}
						invokeListener(records);
					}

其中this.consumer.poll()方法拉取消息,invokeListener方法是执行处理消息逻辑。

private void invokeListener(final ConsumerRecords<K, V> records) {
			if (this.isBatchListener) {
				invokeBatchListener(records);
			}
			else {
				invokeRecordListener(records);
			}
		}
private void invokeRecordListener(final ConsumerRecords<K, V> records) {
			if (this.transactionTemplate != null) {
				innvokeRecordListenerInTx(records);
			}
			else {
				doInvokeWithRecords(records);
			}
		}
private void doInvokeWithRecords(final ConsumerRecords<K, V> records) throws Error {
			Iterator<ConsumerRecord<K, V>> iterator = records.iterator();
			while (iterator.hasNext()) {
				final ConsumerRecord<K, V> record = iterator.next();
				if (this.logger.isTraceEnabled()) {
					this.logger.trace("Processing " + record);
				}
				doInvokeRecordListener(record, null);
			}
		}

最终会走到doInvokeRecordListener方法

private RuntimeException doInvokeRecordListener(final ConsumerRecord<K, V> record,
				@SuppressWarnings("rawtypes") Producer producer) throws Error {
			try {
				if (this.acknowledgingMessageListener != null) {
					this.acknowledgingMessageListener.onMessage(record,
							this.isAnyManualAck
									? new ConsumerAcknowledgment(record)
									: null);
				}
				else {
					this.listener.onMessage(record);
				}
				ackCurrent(record, producer);
			}

最终会调用onMessage方法,其中第二个参数就是Acknowledgment,有可能是null,看设置的auto.commit值决定。

public void ackCurrent(final ConsumerRecord<K, V> record, @SuppressWarnings("rawtypes") Producer producer) {
			if (this.isRecordAck) {
				Map<TopicPartition, OffsetAndMetadata> offsetsToCommit =
						Collections.singletonMap(new TopicPartition(record.topic(), record.partition()),
								new OffsetAndMetadata(record.offset() + 1));
				if (producer == null) {
					if (this.logger.isDebugEnabled()) {
						this.logger.debug("Committing: " + offsetsToCommit);
					}
					if (this.containerProperties.isSyncCommits()) {
						this.consumer.commitSync(offsetsToCommit);
					}
					else {
						this.consumer.commitAsync(offsetsToCommit, this.commitCallback);
					}
				}
				else {
					this.acks.add(record);
				}
			}
			else if (!this.isAnyManualAck && !this.autoCommit) {
				this.acks.add(record);
			}
			if (producer != null) {
				try {
					sendOffsetsToTransaction(producer);
				}
				catch (Exception e) {
					this.logger.error("Send offsets to transaction failed", e);
				}
			}
		}

当我们设置了手动提交,也就是MANUAL或者MANUAL_IMMEDIATE,是通过调用ack.acknowledge()方法

@Override
			public void acknowledge() {
				Assert.state(ListenerConsumer.this.isAnyManualAck,
						"A manual ackmode is required for an acknowledging listener");
				for (ConsumerRecord<K, V> record : getHighestOffsetRecords(this.records)) {
					processAck(record);
				}
			}
private void processAck(ConsumerRecord<K, V> record) {
			if (!Thread.currentThread().equals(this.consumerThread)) {
				try {
					this.acks.put(record);
				}
				catch (InterruptedException e) {
					Thread.currentThread().interrupt();
					throw new KafkaException("Interrupted while storing ack", e);
				}
			}
			else {
				if (this.isManualImmediateAck) {
					try {
						ackImmediate(record);
					}
					catch (WakeupException e) {
						// ignore - not polling
					}
				}
				else {
					addOffset(record);
				}
			}
		}
private void processAck(ConsumerRecord<K, V> record) {
			if (!Thread.currentThread().equals(this.consumerThread)) {
				try {
					this.acks.put(record);
				}
				catch (InterruptedException e) {
					Thread.currentThread().interrupt();
					throw new KafkaException("Interrupted while storing ack", e);
				}
			}
			else {
				if (this.isManualImmediateAck) {
					try {
						ackImmediate(record);
					}
					catch (WakeupException e) {
						// ignore - not polling
					}
				}
				else {
					addOffset(record);
				}
			}
		}

MANUAL_IMMEDIATE模式直接提交offset,MANUAL模式则先把要提交的offset放到map中,然后返回。

再看回最上面的run方法,其中processCommits()就是从map中拿出要提交的offset,然后批量提交。也就是在下一次poll之前做了提交,相当于处理完了上一次poll回来的所有消息后,然后再一起提交。

他们区别:MANUAL_IMMEDIATE是消费完一个消息就提交,MANUAL是处理完一批消息,在下一次拉取消息之前批量提交。拉取批量消息可以通过max.poll.record设置最大,默认是500条。前提是消息大小满足最大限制,否则一批也拉取不到最大的500条。

  • 9
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring-Kafka整合是将Spring框架与Kafka消息系统进行整合,使得开发者能够方便地使用Spring框架进行Kafka消息的生产和消费。 Spring-Kafka整合提供了以下功能: 1. 自动配置Kafka生产者和消费者。 2. 提供KafkaTemplate用于发送消息。 3. 提供@KafkaListener注解用于监听Kafka主题。 4. 提供KafkaListenerContainerFactory用于创建Kafka监听器容器。 5. 提供KafkaAdmin用于管理Kafka集群。 Spring-Kafka整合的使用步骤如下: 1. 添加Spring-Kafka依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>${spring-kafka.version}</version> </dependency> ``` 2. 配置Kafka连接 在application.properties文件中添加Kafka连接相关配置: ``` spring.kafka.bootstrap-servers=localhost:9092 ``` 3. 编写Kafka生产者 使用KafkaTemplate发送消息: ``` @Autowired private KafkaTemplate<String, String> kafkaTemplate; public void sendMessage(String topic, String message) { kafkaTemplate.send(topic, message); } ``` 4. 编写Kafka消费者 使用@KafkaListener注解监听Kafka主题: ``` @KafkaListener(topics = "test-topic") public void receiveMessage(String message) { //消费消息 } ``` 5. 配置Kafka监听器容器 使用KafkaListenerContainerFactory创建Kafka监听器容器: ``` @Bean public KafkaListenerContainerFactory<?> kafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(consumerFactory()); factory.setConcurrency(1); factory.getContainerProperties().setPollTimeout(3000); return factory; } ``` 6. 配置Kafka管理器 使用KafkaAdmin创建Kafka管理器: ``` @Bean public KafkaAdmin kafkaAdmin() { Map<String, Object> configs = new HashMap<>(); configs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); return new KafkaAdmin(configs); } ``` Spring-Kafka整合的使用可以使得开发者更加方便地使用Kafka消息系统,提高消息的生产和消费效率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值