强可靠的Kafka配置
前段时间做了一次基于binlog的数据同步,由于binlog的场景需要严格的顺序性和可靠的消息存储,所以基于Kafka好好的研究了一下相关配置
1.强可靠的含义
我目前的场景需要MQ能够做到无消息丢失、严格顺序消费,但是Kafka的默认配置是走吞吐量的,所以在配置上牺牲了Kafka的可用性和性能来尽可能的增强了顺序消费的特点,最终使得Kafka集群的特性为以下四点:
可靠存储、严格顺序、低可用、低性能
我称之为强可靠Kafka,要做到强可靠,不仅需要Kafka集群的配置更改,同时也要生产者和消费者共同努力才能实现,接下来讲具体细节。
2.Kafka集群配置
更改在kafka集群中的配置(config/server.properties ),以下是对于强可靠比较重要的参数,其他的仍保持默认配置,总体来说就是牺牲了kafka本来的性能和可用性来换取消息不丢失,经过以下配置后,三台Kafka集群只能容忍一台broker发生故障。
- num.partitions=1
kafka只保障了单个分区内消息消费的有序性,所以这里分区数设为1 - unclean.leader.election.enable=false
非ISR的broker无法成为leader,损失了可用性来保障可靠的消息存储。 - offsets.topic.replication.factor=3
- min.insync.replicas=2
topic副本数设置为3,并且ISR设置最小两个存活,含义为最多允许有一个broker宕机,kafka仍能保证消息不会丢失并正常提供服务。
损失了可用性来保障可靠的消息存储。 - log.retention.hours=720
消息保留最大时间30天,防止意外情况发生时能够回溯 - log.flush.interval.ms=3000
消息每3S刷盘一次,kafka收到消息会先写入pageCache而不会直接落盘,每3S强制刷新一次,最极端的情况下也只会丢失3S的消息。损失了吞吐量来保障可靠的消息存储。
3.生产者配置
在生产端同样要进行配置项更改,这里主要是为了进行严格顺序的控制,严格顺序消费需要生产者和消费者的共同努力。
以下是总体的生产者代码配置
props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 1);
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
// 发送重试
props.put(ProducerConfig.RETRIES_CONFIG, 100);
props.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, 500);
// 发送缓存区配置
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 128);
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 1024 * 1024 * 50);
props.put(ProducerConfig.LINGER_MS_CONFIG, 500);
props.put(ProducerConfig.ACKS_CONFIG, "all");
每项的具体含义如下
- acks=all
所有副本收到消息后才认为消息发送成功,损失了吞吐量来保障可靠的消息存储。 - enable.idempotence=true
在消息发送端确保消息不会重发,防止消息重发带来消息顺序错乱的问题。 - max.in.flight.requests.per.connection=1
只能有一个批次在进行发送消息的操作,损失了吞吐量来严格顺序性。 - retries=0
消息发送的重试次数,这个不能设置重试,因为发生重试会将消息进行重新投递,打乱顺序性。 - retry.backoff.ms=500
每次重试的阻塞时间 - batch.size
- buffer.memory
- linger.ms
该三个参数修改了每个批次的消息容量和发送间隔,主要与消息发送的吞吐量有关,自行确认修改
4.消费者配置
消费者在消费时也要更改相关的操作,来进行消息顺序消费的保障,并且由于原生的kafka消费者没有消费重试和死信队列的配置,所以这里引入了spring-kafka。以下是消费端的代码配置
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
factory.setMissingTopicsFatal(false);
factory.setConcurrency(1);
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL);
// 设置重试
factory.setErrorHandler(new SeekToCurrentErrorHandler(new FixedBackOff(600L, 8)));
return factory;
}
@Bean
public ConsumerFactory<String, String> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerConfigs());
}
@Bean
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<>(16);
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "xxxx:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "ha-test");
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 30000);
// 消费者最重要参数 控制拉取消息的间隔
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 20000);
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 50);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
return props;
}
比较关键的地方就是要设置手动提交setAckMode(ContainerProperties.AckMode.MANUAL),手动提交是顺序消费的必要条件。
另一个就是spring-kafka附加的消费重试配置,可以解决一部分消息消费丢失的问题