当前位置:首页 > 技术 > 正文内容

Spring Boot 环境下多 Kafka 集群配置与实战

访客 技术 2026年7月15日 2

1. 项目依赖管理

要在 Spring Boot 项目中启用消息队列功能,首先需要引入官方提供的 starter。请在构建脚本 pom.xml 中添加如下坐标:

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>

2. 集群连接参数设置

针对单一环境或多集群场景,配置文件(推荐 application.yml)的写法有所不同。若需对接多个 Kafka 实例,建议采用自定义前缀进行隔离配置。

单集群模式示例

适用于标准单点部署,直接复用原生配置项:

server:
  port: 8080

spring:
  kafka:
    bootstrap-servers: 192.168.1.100:9092
    producer:
      acks: 1
      retries: 3
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
    consumer:
      group-id: app-group-core
      auto-offset-reset: earliest
      enable-auto-commit: true
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer

双集群隔离配置

为了区分不同业务线或测试/生产环境,可以通过扩展属性前缀来管理多个数据源。以下演示了"主集群"与"辅集群"的配置差异:

app:
  messaging:
    # 默认主库
    primary-cluster-name: alpha
    
    # Alpha 集群配置
    clusters:
      alpha:
        bootstrap-servers: 10.0.0.1:9092
        producer:
          acks: 1
          retries: 5
        consumer:
          group-id: consumer-alpha
      beta:
        bootstrap-servers: 10.0.0.2:9092
        producer:
          acks: -1
          retries: 3
        consumer:
          group-id: consumer-beta

3. Java 配置类封装

基于上述属性,我们需要在代码中通过 @Configuration 手动绑定 Bean。为了避免硬编码,利用 KafkaProperties 结合 @ConfigurationProperties 进行解耦。以下是重构后的配置类逻辑:

package com.enterprise.msg.kafka.config;

import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.*;
import org.springframework.util.ObjectUtils;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class MultiKafkaPropertiesConfig {

    // 定义 Alpha 集群属性
    @ConfigurationProperties(prefix = "app.messaging.clusters.alpha")
    @Bean(name = "alphaKafkaProps")
    public KafkaProperties alphaConfig() {
        return new KafkaProperties();
    }

    // 定义 Beta 集群属性
    @ConfigurationProperties(prefix = "app.messaging.clusters.beta")
    @Bean(name = "betaKafkaProps")
    public KafkaProperties betaConfig() {
        return new KafkaProperties();
    }

    // —— 生产者模板 (Producer) ——

    @Bean("alphaTemplate")
    public KafkaTemplate<String, String> alphaTemplate(@Qualifier("alphaKafkaProps") KafkaProperties props) {
        Map<String, Object> cfg = buildProducerMap(props);
        return new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(cfg));
    }

    @Bean("betaTemplate")
    public KafkaTemplate<String, String> betaTemplate(@Qualifier("betaKafkaProps") KafkaProperties props) {
        Map<String, Object> cfg = buildProducerMap(props);
        return new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(cfg));
    }

    private Map<String, Object> buildProducerMap(KafkaProperties props) {
        Map<String, Object> map = new HashMap<>();
        map.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, props.getBootstrapServers());
        map.put(ProducerConfig.RETRIES_CONFIG, props.getProducer().getRetries());
        map.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        map.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        return map;
    }

    // —— 消费者工厂 (Consumer Factory) ——

    @Bean("alphaListenerFactory")
    public ConcurrentKafkaListenerContainerFactory<Object, Object> alphaListenerFactory(
            @Qualifier("alphaKafkaProps") KafkaProperties props) {
        return createListenerFactory(new DefaultKafkaConsumerFactory<>(props.buildConsumerProperties()));
    }

    @Bean("betaListenerFactory")
    public ConcurrentKafkaListenerContainerFactory<Object, Object> betaListenerFactory(
            @Qualifier("betaKafkaProps") KafkaProperties props) {
        return createListenerFactory(new DefaultKafkaConsumerFactory<>(props.buildConsumerProperties()));
    }

    private ConcurrentKafkaListenerContainerFactory<Object, Object> createListenerFactory(ConsumerFactory<Object, Object> factory) {
        ConcurrentKafkaListenerContainerFactory<Object, Object> container = new ConcurrentKafkaListenerContainerFactory<>();
        container.setConsumerFactory(factory);
        return container;
    }
}

同时,为了便于业务层调用,我们可以将具体的发送服务抽象出来:

package com.enterprise.msg.service;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.KafkaTemplate;

@Configuration
public class MessageQueueServiceRegistry {

    public static abstract class BaseMessageService {
        protected final KafkaTemplate<String, String> template;
        public BaseMessageService(KafkaTemplate<String, String> t) { this.template = t; }
    }

    @Bean("mqAlphaService")
    public BaseMessageService mqAlpha(@Qualifier("alphaTemplate") KafkaTemplate<String, String> tmpl) {
        return new BaseMessageService(tmpl) {};
    }

    @Bean("mqBetaService")
    public BaseMessageService mqBeta(@Qualifier("betaTemplate") KafkaTemplate<String, String> tmpl) {
        return new BaseMessageService(tmpl) {};
    }
}

4. 业务接口与服务实现

定义通用的消息投递契约:

package com.enterprise.msg.api;

import org.springframework.util.concurrent.ListenableFuture;

public interface MQPublisher {
    void dispatch(String targetTopic, String payloadJson);
}

对应实现类注入特定的 Template:

package com.enterprise.msg.impl;

import com.enterprise.msg.api.MQPublisher;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class KakaPublishImpl implements MQPublisher {
    private final KafkaTemplate<String, String> producer;
    
    public KakaPublishImpl(KafkaTemplate<String, String> p) {
        this.producer = p;
    }

    @Override
    public void dispatch(String targetTopic, String payloadJson) {
        // 这里简化处理,实际生产中应返回 Future 以便监控回调
        producer.send(targetTopic, payloadJson); 
    }
}

5. 控制器层验证

创建一个 REST 接口用于触发消息发送,模拟业务操作:

package com.enterprise.msg.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import com.enterprise.msg.impl.KakaPublishImpl;

@RestController
public class DemoController {
    // 假设已注入了三个不同的服务实例
    private final KakaPublishImpl defaultSvc;
    private final KakaPublishImpl alphaSvc;
    private final KakaPublishImpl betaSvc;

    public DemoController(KakaPublishImpl d, @Qualifier("mqAlphaService") KakaPublishImpl a, @Qualifier("mqBetaService") KakaPublishImpl b) {
        this.defaultSvc = d;
        this.alphaSvc = a;
        this.betaSvc = b;
    }

    @PostMapping("/api/kafka/test")
    public void triggerSend() {
        String msg1 = "{\"type\":\"default\",\"content\":\"Primary Cluster Msg\"}";
        String msg2 = "{\"type\":\"alpha\",\"content\":\"Secondary Alpha Msg\"}";
        String msg3 = "{\"type\":\"beta\",\"content\":\"Secondary Beta Msg\"}";
        
        defaultSvc.dispatch("test-topic", msg1);
        alphaSvc.dispatch("test-topic", msg2);
        betaSvc.dispatch("test-topic", msg3);
    }
}

6. 监听器接收端设计

消费者端需要指定对应的 ContainerFactory,以确保连接到正确的集群:

package com.enterprise.msg.listener;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class UnifiedMessageHandler {
    private static final Logger log = LoggerFactory.getLogger(UnifiedMessageHandler.class);

    @KafkaListener(topics = "test-topic")
    public void onDefaultMsg(ConsumerRecord<String, String> rec) {
        log.info("[Default Group] Received offset {}: {}", rec.offset(), rec.value());
    }

    @KafkaListener(topics = "test-topic", containerFactory = "alphaListenerFactory")
    public void onAlphaMsg(ConsumerRecord<String, String> rec) {
        log.info("[Alpha Group] Processing data: {}", rec.value());
    }

    @KafkaListener(topics = "test-topic", containerFactory = "betaListenerFactory")
    public void onBetaMsg(ConsumerRecord<String, String> rec) {
        log.info("[Beta Group] Handling payload: {}", rec.value());
    }
}

7. 启动日志与结果校验

应用启动后,控制台会打印 Kafka 客户端的版本信息及消费者组加入状态。关键日志片段如下所示:

INFO  c.e.msg.listener.UnifiedMessageHandler - [Default Group] Received offset 102: {"type":"default","content":"Primary Cluster Msg"}
INFO  c.e.msg.listener.UnifiedMessageHandler - [Alpha Group] Processing data: {"type":"alpha","content":"Secondary Alpha Msg"}
INFO  c.e.msg.listener.UnifiedMessageHandler - [Beta Group] Handling payload: {"type":"beta","content":"Secondary Beta Msg"}

从日志可以看出,三个不同的消费者方法分别监听了同一主题下的消息,且各自独立处理。这验证了多数据源配置(即多 ConsumerFactory)已成功生效,实现了消息流的路由隔离。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。