在需要 ClientId 鉴权的 Kafka 集群中,高效使用 Producer 和 Consumer 的方法_kafka clientid

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

@Configuration
@EnableKafka
public class KafkaConsumerConfig {
    @Bean
    public ConsumerFactory<String, Object> consumerFactory() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP\_SERVERS\_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.GROUP\_ID\_CONFIG, "your-consumer-group-id");
        props.put(ConsumerConfig.VALUE\_DESERIALIZER\_CLASS\_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        props.put(ConsumerConfig.KEY\_DESERIALIZER\_CLASS\_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        return new DefaultKafkaConsumerFactory<>(props);
    }
    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory =
                new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL\_IMMEDIATE);
        return factory;
    }
}
                  
@Component
public class MyKafkaMessageListener {
    @KafkaListener(topics = "your-topic-name", concurrency = "4", clientIdPrefix = "your-client-id")
    public void listen(ConsumerRecord<String, Object> record, Acknowledgment acknowledgment) {
        // your business logic 
        //……
      
        acknowledgment.acknowledge();
    }
}

同样,我们遇到了鉴权失败的问题。错误信息如下:

ERROR[org.springframework.kafka.KafkaListenerEndpointContainer#0-4-C-1] o.s.k.l.KafkaMessageListenerContainer.error(149): Authentication/Authorization Exception and no authExceptionRetryInterval set
org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [your-topic-name]

通过跟踪源码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们发现在容器 ConcurrentMessageListenerContainerdoStart 方法中,会根据并行度 concurrency 创建多个 KafkaMessageListenerContainer 子容器,然后调用 configureChildContainer 方法配置子容器,并根据 concurrencyalwaysClientIdSuffix 参数对 ClientId 添加后缀。如果并行度设置为 1,那么只需要在 KafkaConsumerConfig 中添加如下代码来设置 alwaysClientIdSuffix

factory.setContainerCustomizer(container -> container.setAlwaysClientIdSuffix(false));

这样就能确保 ClientId 不会被改变,从而完成鉴权操作。但是为了提高消费能力,我们总归需要设置并行度。因此,根据重写生产者的经验,我们重写 DefaultKafkaConsumerFactorycreateKafkaConsumer 方法:

public class FixedClientIdKafkaConsumerFactory<K, V> extends DefaultKafkaConsumerFactory<K, V> {
    public FixedClientIdKafkaConsumerFactory(Map configs) {
        super(configs);
    }
    @Override
    protected Consumer<K, V> createKafkaConsumer(String groupId, String clientIdPrefixArg, String clientIdSuffixArg, Properties properties) {
        return super.createKafkaConsumer(groupId, clientIdPrefixArg, null, properties);
    }
}

然后在 KafkaConsumerConfig 中使用 FixedClientIdKafkaConsumerFactory 替换 DefaultKafkaConsumerFactory。然而,这次却出现了如下错误:

WARN [main] o.a.k.c.u.AppInfoParser.registerAppInfo(68): Error registering AppInfo mbean
javax.management.InstanceAlreadyExistsException: kafka.consumer:type=app-info,id=your-client-id

在自定义 ConsumerFactory 以确保在多线程环境下共用相同的 ClientId 时,我们必须考虑到启用 JMX 监控时,MBean 的唯一性问题。JMX MBean 是线程级别的,因此如果出现冲突,可能会影响监控功能。

尽管上述日志只是一个 WARN 级别的记录,不会直接影响消费功能,但如果我们希望通过 JMX 实现精准监控,那么必须要解决这个问题。另外,长期看到一系列的 WARN 日志也会令人不安,因此我们决定继续采用自增后缀的 ClientId 策略。

然而,在申请多个 ClientId 时,需要权衡数量。我们需要考虑 Kafka 集群的鉴权能力,同时也要避免后续扩容时频繁申请 ClientId 的问题。

Kafka 的高级 API 确保在同一个消费者组(consumer group)下,每个分区(partition)只能由一个 Consumer 线程消费(1Consumer 线程可以消费多个 partition)。在这里插入图片描述
如上,当一个消费者组包含 5 个消费者并且有 4 个分区时,无论这 5 个消费者是以单实例方式部署还是分布式方式部署,都可能出现某个消费者未被分配到分区的情况,这样会造成线程空跑,占据着资源。为了解决这个问题,我们只需确保总消费者数量小于或等于分区数量。

通常情况下,我们的应用中 Kafka Consumer 仅占据一部分流量。因此,我们是否可以约定单个实例的 Kafka Consumer 的最大并发数为某个固定值呢?比如 6 这样一来,在申请 Topic 时,我们可以一并申请 6ClientId,其命名格式为 -0-5。例如,如果我们申请了一个名为 my-topic-testTopic,那么除了默认生成一个 ClientIdmy-topic-test-GeMp 之外,我们只需再申请 my-topic-test-GeMp-0my-topic-test-GeMp-1my-topic-test-GeMp-2my-topic-test-GeMp-3my-topic-test-GeMp-4my-topic-test-GeMp-5 即可。

在后续需要提升消费能力时,我们可以扩展分区数量。根据分区数量进行横向水平扩容,以保证一个消费者组内的总消费者数量等于分区数量。这样,就不需要再担心 ClientId 的鉴权和 JMX 注册问题了。此外,这也确保了在未来需要提升消费能力并进行分区扩容时,无需再次申请 ClientId

这种做法可以有效地优化 Kafka Consumer 的管理和扩展,以满足我们的需求。

结论

综上所述,我们总结了在使用 ClientId 进行 Kafka 集群环境下的身份验证时,Kafka 生产者和消费者的一种高效使用方式。

申请ClientId

Kafka 平台上申请 Topic 时,请根据自动生成的 ClientId 作为前缀,然后使用 “-0” 至 “-5” 作为后缀,额外申请 6ClientId(数量只是建议,可自行根据应用情况设置)。

Maven配置
<dependency>
	<groupId>org.springframework.kafka</groupId>
	<artifactId>spring-kafka</artifactId>
	<version>2.8.8</version> <!-- 建议不配置,默认使用父pom中的版本 -->
</dependency>

生产者
@Configuration
public class KafkaProducerConfig {
    @Bean
    public ProducerFactory<String, Object> producerFactory() {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ProducerConfig.BOOTSTRAP\_SERVERS\_CONFIG, "localhost:9092");
        configProps.put(ProducerConfig.KEY\_SERIALIZER\_CLASS\_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        configProps.put(ProducerConfig.VALUE\_SERIALIZER\_CLASS\_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        configProps.put(ProducerConfig.CLIENT\_ID\_CONFIG, "your-client-id");
        return new DefaultKafkaProducerFactory<>(configProps);
    }
    
    @Bean
    public KafkaTemplate<String, Object> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }
}

@Service
public class BusinessServiceImpl implements BusinessService {
    @Autowired
    private KafkaTemplate<String, Object> kafkaTemplate;

    void send(String msg) {
        ProducerRecord<String, Object> record = new ProducerRecord<>("your-topic-name", message);
        kafkaTemplate.send(record);
    }
}

由于 KafkaProducer 是线程安全的,如果要使用多线程的生产者,建议使用单例生产者,然后使用线程池来包装。

消费者
@Configuration
@EnableKafka
public class KafkaConsumerConfig {
    @Bean
    public ConsumerFactory<String, Object> consumerFactory() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP\_SERVERS\_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.GROUP\_ID\_CONFIG, "your-consumer-group-id");
        props.put(ConsumerConfig.VALUE\_DESERIALIZER\_CLASS\_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        props.put(ConsumerConfig.KEY\_DESERIALIZER\_CLASS\_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        return new DefaultKafkaConsumerFactory<>(props);
    }

    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, Object> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, Object> factory =
                new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL\_IMMEDIATE);
        return factory;
    }
}



![img](https://img-blog.csdnimg.cn/img_convert/325a1317664cbe39729cd9b77df9fdd8.png)
![img](https://img-blog.csdnimg.cn/img_convert/723a5901486a2def80c3e005b05caaa4.png)
![img](https://img-blog.csdnimg.cn/img_convert/cba344f8858e6b64e8869d589b937da9.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

715023969370)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值