Kafka学习笔记(二)

118 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情

实战

了解Kafka基本知识点后,下面就来尝试一下SpringBoot+kafka的整合。

首先需要本机需要安装一下Kafka,在这里选择的是2.8.1的版本,下载解压到对应的文件夹即可。

image.png 随后便先启动一下Zookeeper和Kafka

// 启动Zookeepr
E:\kafka\kafka_2.12-2.8.2>bin\windows\zookeeper-server-start.bat config\zookeeper.properties
​
// 启动Kafka
E:\kafka\kafka_2.12-2.8.2>bin\windows\kafka-server-start.bat config\server.properties

开发环境

依赖版本
spring-boot-starter-parent2.7.4
JDK11

Kafka配置信息

spring:
  kafka:
    bootstrap-servers:
      - 127.0.0.1:9092 // kafka服务器地址
    consumer:
      group-id: defaultConsumerGroup // 默认消费者名
      enable-auto-commit: false // 消费者是否自动提交 默认为true
    listener:
      ack-mode: manual // 配置消费者监听器手动提交
kafka:
  topic:
    name: spring-kafka-demo1 // 自定义配置-主题名

创建Topic

@Configuration
public class KafkaInitialConfiguration {
​
    @Value( "${kafka.topic.name}" )
    private String topicName;
​
    @Bean
    public NewTopic initTopic(){
        return new NewTopic( topicName,3, (short) 3 );
    }
}

image.png

在这里创建了一个分区数和副本数都为3的主题。

创建简单生产者

@RestController
@RequestMapping("/kafka")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Slf4j
public class KafkaProducerController {
​
    private final KafkaTemplate<String,String> kafkaTemplate;
​
    @Value( "${kafka.topic.name}" )
    private String topicName;
​
​
    @GetMapping("/send")
    public ResponseEntity sendMessage(@RequestParam String message){
        kafkaTemplate.send( topicName, message );
        return ResponseEntity.ok().build();
    }
}

在这里创建一个简单的生产者,在这里没有指定key和partition,因此将会使用默认的分区策略:轮询选择一个分区

创建简单消费者

@Component
public class KafkaConsumer {
​
    private static final Logger log = LoggerFactory.getLogger(KafkaConsumer.class);
​
    @KafkaListener(topics = "${kafka.topic.name}")
    public void handleMessage(ConsumerRecord<String,String> consumerRecord, Acknowledgment acknowledgment) {
        log.info( "key={}  value={}",consumerRecord.key(),consumerRecord.value() );
        acknowledgment.acknowledge(); // 手动提交ack
    }
}
​

由于配置里关闭了自动提交ack,因此在handleMessage方法里就需要手动进行提交

创建回调生产者

    @GetMapping("/send-callback")
    public ResponseEntity sendCallBackMessage(@RequestParam String message){
        kafkaTemplate.send( topicName,message ).addCallback( success -> {
            String topic = success.getRecordMetadata().topic();
            int partition = success.getRecordMetadata().partition();
            long offset = success.getRecordMetadata().offset();
            log.info( "topic={}\tpartition={}\toffset={}",topic,partition,offset );
        },failure->{
            log.info( "发送失败:{}",failure.getMessage() );
        });
        return ResponseEntity.ok().build();
    }

在大部分情况下,我们都需要知道消息是否成功发送到对应的Kafka服务器去,所以我们可以使用回调来获得监控信息是否发送成功或者失败,以此可以做出相关的恢复操作。

创建指定分区生产者

    @GetMapping("/send-partition")
    public ResponseEntity sendMessageByPartition(@RequestParam(defaultValue = "1") Integer num, @RequestParam String message){
        kafkaTemplate.send( topicName, num, "just key",message );
        return ResponseEntity.ok().build();
    }

随后我们创建两个指定分区的消费者,随后我们调用对应发送接口,就可以看到都进入到对应的分区啦

​
    @KafkaListener(topicPartitions = {
        @TopicPartition( topic = "${kafka.topic.name}",partitions = "1")
    })
    public void handleMessageByPartition1(ConsumerRecord<String,String> consumerRecord, Acknowledgment acknowledgment) {
        log.info( "分区1:key={}  value={}",consumerRecord.key(),consumerRecord.value() );
        acknowledgment.acknowledge();
    }
​
    @KafkaListener(topicPartitions = {
        @TopicPartition( topic = "${kafka.topic.name}",partitions = "2")
    })
    public void handleMessageByPartition2(ConsumerRecord<String,String> consumerRecord, Acknowledgment acknowledgment) {
        log.info( "分区2:key={}  value={}",consumerRecord.key(),consumerRecord.value() );
        acknowledgment.acknowledge();
    }

kafka消息丢失问题

通过上面一些简单的例子,就可以发现在某些情况下消息是会丢失的,例如:

  1. 生产者发送消息时,由于网络原因或者系统故障,消息并没有发送到对应的Kafka服务器
  2. Kafka服务器收到信息后还没有写入磁盘,系统就宕机了
  3. 消费者收到消息后,准备进行处理,但系统又宕机了

生产者防止丢失

可以使用上面的发送回调方法来防止,当监听到发送失败时就进行相关的消息补偿机制,同时也可以加入多次发送重试。

# 重试次数
spring.kafka.producer.retries=3

kafka防止丢失

这个我们可以通过多副本和acks配合来实现,例如将acks设为all,来最大程度上防止消息的丢失。

消费者防止丢失

取消ack的自动提交,而改为处理完业务后,手动提交,但需要注意避免多次消费的问题。