Spring Boot集成Kafka:最佳实践与详细指南

文章目录

一、生产者

1.引入库

引入需要依赖的jar包,引入POM文件:

yaml 复制代码
 <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
</dependency>

2.配置文件

配置Kafka的相关参数(或者你项目的cacos或者yaml文件里添加)

以下是一个示例配置:application.properties

yaml 复制代码
ccm.kafka.servers:192.168.1.95:9092,192.168.1.96:9092,192.168.1.97:9092
ccm.kafka.topics.xxx:xxx_content_dev

Tip:建议topic命名规则:租户简称+项目关键词+系统环境的方式,更容易区分

3.配置类

PublicConfig.java

java 复制代码
@Data
@Configuration
@ConfigurationProperties(prefix = "ccm.kafka")
//配置信息nacos中配置
public class PublicConfig {

    private String servers;

    private String alertTopic;

}

MessageProducer.java

java 复制代码
@Slf4j
@Component
public class MessageProducer {

    private Producer producerKafka;

    @Autowired
    PublicConfig publicConfig;

    /**
     * 初始化方法
     */
    @PostConstruct
    public String init() {
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, publicConfig.getServers());
        props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "PLAINTEXT");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        props.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, String.valueOf(30 * 1000));
        props.put(ProducerConfig.ACKS_CONFIG, "all");
        producerKafka = new KafkaProducer(props);
        log.info("kafka message channel created successfully");
        return "OK";
    }

    public ResponseData send(String content, String topic) {

        long startTime = System.currentTimeMillis();
        try {
            String key = UUID.randomUUID().toString().replace("-", "");
            ProducerRecord<String, String> kafkaMessage = new ProducerRecord<>(topic, key, content);
            log.info("MessageProducer send key {},message{}", key, content);
            Future<RecordMetadata> send = producerKafka.send(kafkaMessage);
            send.get();
            log.info("MessageProducer send cost time:{}", System.currentTimeMillis() - startTime);
        } catch (Exception e) {
            log.error("MessageProducer Failed to push message:{}", e.getMessage());
            return ResponseData.errorWithMsg("MessageProducer Failed to push message:" + e.getMessage());
        }
        return null;
    }

}

4.业务处理类

示例代码的业务场景:定时生成预警消息发送给下游系统调用。

java 复制代码
//启动类注意增加定时注解的支持
@SpringBootApplication
@MapperScan(basePackages = {"com.xx.xx.mapper","com.xx.xx.crawler.mapper"})
@EnableScheduling
public class CATApp {
    public static void main(String[] args) {
        SpringApplication.run(CATApp.class,args);
    }}

@Service
@Slf4j
public class CrawlerService {
     @Scheduled(cron = "${crawler.scheduled.cron:0 */1 * * * ?}") // 每5分钟执行一次
     //   @Scheduled(cron = "${crawler.scheduled.cron:0 0 0/1 * * ?}") // 每小时执行一次
    public void crawlAndSaveAlertInfos() {
        log.info(">>>>>>>>>>>>> crawlAndSaveAlertInfos  ");
        //替换成具体的业务场景 
        List<AlertInfo> alertInfos = fetchAlertInfoList();
        if (!alertInfos.isEmpty()) {
            for (AlertInfo alertInfo : alertInfos) {
                //发送预警信息到kafka供下游调用
                crawlerAlertSyncService.sendCrawlerAlertMsgKafka(alertInfo);
            }
        }
    }
/**
 *
 * 预警消息通过Kafka异步同步其他应用
 */
public interface CrawlerAlertSyncService {

     void sendCrawlerAlertMsgKafka(AlertInfo alertInfo) ;

}

@Slf4j
@Service
public class CrawlerAlertSyncServiceImpl implements CrawlerAlertSyncService {
    @Autowired
    private MessageProducer messageProducer;

    @Resource
    private PublicConfig publicConfig;

    @Override
    public void sendCrawlerAlertMsgKafka(AlertInfo alertInfo) {
        String topic = publicConfig.getAlertTopic();
        String servers = publicConfig.getServers();
        log.info("send publish msg to kafka  ,topic:{},bizId:{}", topic, alertInfo.getAlertid());
        log.info("send publish msg to kafka  ,servers:{}", servers);
        String content = JSON.toJSONString(alertInfo);
        log.info("send publish msg to kafka  ,content:{}", content);
        if (StringUtils.isNotBlank(topic)) {
            messageProducer.send(content, topic);
        }
    }
}

三、消费者

1.引入库

在消费者工程pom文件中配置依赖

yaml 复制代码
 <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
</dependency>

2.配置类

同样根据该项目情况编写配置类,示例代码中仍为读取naco配置

PublicConfig.java

java 复制代码
@Data
@Configuration
@Slf4j
@ConfigurationProperties(prefix = "xman.kafka")
public class PublicConfig {

    private String servers;

    private Map<String,String> topics;

    public String getTopic(String appCode) {
        if(Objects.isNull(topics) || topics.isEmpty()){
            return null;
        }

        return topics.get(appCode);
    }

    private String alertTopic;
    private String group;
}

MessageConsumer.java

java 复制代码
@Slf4j
@Component
public abstract class MessageConsumer {

    // 用于持续监听kafka消息的专用线程池
    private ExecutorService threadPool;
    // 用于持续消费kafka消息的专用线程池
    private ExecutorService consumerThreadPool;

    @Resource
    private PublicConfig publicConfig;

    /**
     * 初始化方法
     */
    @PostConstruct
    public String init() {
        MessageConfigField messageConfig = MessageConfigField.builder()
                .servers(publicConfig.getServers())
                .topic(publicConfig.getAlertTopic())
                .group(publicConfig.getGroup())
                .build();

        if (StringUtils.isBlank(messageConfig.getServers())) {
            //没有配置kafka信息
            return "OK";
        }
        initThreadPool();

        KafkaConsumer<String, String> instance = kafkaInstance(messageConfig.getServers(),
                messageConfig.getGroup(), messageConfig.getTopic(), messageConfig.getClientName(),
                messageConfig.getUsername(), messageConfig.getPassword());
        startListen(instance);
        log.info("ccm kafka消息订阅成功:clientId:" + messageConfig.getClientName());
        return "OK";

    }

    private void initThreadPool() {
        if (null == threadPool) {
            log.info("initThreadPool start");
            threadPool = Executors.newFixedThreadPool(1);
            log.info("initThreadPool done");
        }
    }
    

    private void startListen(KafkaConsumer<String, String> consumer) {
        threadPool.submit(() -> {
            TenantContext.setContextCode(CommonConstants.TENANT_CODE);
            while (true) {
                try {
                    ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(10));
                    if (records == null || records.isEmpty()) {
                        continue;
                    }
                    for (ConsumerRecord<String, String> record : records) {
                        Optional<String> kafkaMessage = Optional.ofNullable(record.value());
                        if (kafkaMessage.isPresent()) {
                            String msg = kafkaMessage.get();
                            if (StringUtils.isNotBlank(msg)) {
                                log.info("msgJson:" + msg);
                                consumeMsg(msg);
                            }
                        }
                    }
          
                } catch (Exception e) {
                    TimeUnit.SECONDS.sleep(1);
                    log.error("consume error", e);
                }
            }
        });
    }

    public static KafkaConsumer<String, String> kafkaInstance(String servers, String group,
                                                              String topic, String clientId, String username, String password) {
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
        if (StringUtils.isNotBlank(group)) {
            props.put(ConsumerConfig.GROUP_ID_CONFIG, group);
        }
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
        props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
                "org.apache.kafka.common.serialization.StringDeserializer");
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
                "org.apache.kafka.common.serialization.StringDeserializer");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        List<String> subscribedTopics = new ArrayList<>();
        subscribedTopics.add(topic);
        consumer.subscribe(subscribedTopics);
        return consumer;
    }

    /**
     * 核心逻辑,由子类继承实现
     *
     * @param msgData msg
     */
    public abstract void consumeMsg(String msgData) throws Exception;

}

3.业务类

java 复制代码
@Slf4j
@Service
@RefreshScope
public class CmsInfoConsumer extends MessageConsumer {
    
    @Resource
    private InfoService infoService;

    @Override
    public void consumeMsg(String msgData) throws Exception {
        log.info("CmsWeatherConsumer收到mq消息message:{}", msgData);
        CcmAlertInfoDTO alertInfoDTO = JSONObject.parseObject(msgData, CcmAlertInfoDTO.class);
        try {
            //to_do 处理消费内容
            infoService.saveInfoContent(alertInfoDTO);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("同步用户消息失败:" + e);
        }
    }
}

至此,一个简单的通过kafka同步预警消息的应用就开发完了。

相关推荐
蓝田~3 小时前
大模型本地部署与远程调用 — 从 API 到 Agent
java·人工智能·claude·claude code
码农进化录3 小时前
Java 程序员的 AI 进化论 | Spring Boot 接入 OpenAI 的六个坑,全帮你踩了
java·spring boot·openai
她说彩礼65万3 小时前
Asp.net core 模型绑定
后端·asp.net
用户34232323763173 小时前
BLE 传感器节点实战
后端
疯狂成瘾者4 小时前
Java 常见集合方法
java·windows·python
从此以后自律4 小时前
ConcurrentHashMap 超详细详解
java
fliter4 小时前
Go 1.26 新增 `bytes.Buffer.Peek`:只看数据,不移动读取位置
后端
羑悻4 小时前
我受够了 Coding Agent 的“假聪明,真串行”,于是做了 Meta-Orchestrator!
后端
神奇小汤圆4 小时前
字节二面挂了!被问“订单超时自动取消”
后端
饼干哥哥4 小时前
每晚300+个爆款疯狂跑,0人值守TikTok工厂开张!!
后端·操作系统·产品