Kafka 自动发送消息 Demo 实战:从配置到发送的完整流程(java)

一、背景与目标

很多业务场景需要定时或批量把数据推送到 Kafka,比如报警系统、日志采集、数据同步等。为了让测试更简单、更可控,我们搭了一个"自动发送 Kafka 消息"的小型 Demo。它的目标非常明确:

  1. 通过配置文件指定 Kafka 地址、Topic、消息文件路径

  2. 支持一个文件里写多条 JSON,每条 JSON 都能按"段"发送

  3. 发送过程有简单日志,方便确认进度

  4. 代码清晰、容易扩展

本文基于该项目,讲清楚整体设计、关键代码和使用方式,让你能快速理解并复用。

二、项目结构概览

项目是 Maven 构建的 Java 工程,核心代码都在 src/main/java/com/example/kafka 下:

  • ProducerApp:程序入口

  • Config:读取 YAML 配置、加载消息内容

  • KafkaProducerRunner:发送执行器

  • KafkaConfig/MessageConfig/SendConfig:配置实体

资源文件在 src/main/resources:

  • app.yaml:配置文件

消息文件是一个普通文本文件,比如 message.txt,可放在工程根目录或指定绝对路径。

三、关键设计思路

  1. 配置驱动:

所有运行参数都由 app.yaml 控制,避免命令行参数过多,便于批量测试与部署。

  1. 消息分段:

真实业务中的 JSON 经常跨行。单纯按"每行一条消息"会把一个 JSON 拆成多条。因此这里采用"空行分段"的策略:

  • 空行作为消息边界

  • 空行之间的内容作为一个完整 JSON

  1. 同步发送:

为了让测试结果更可控,使用 producer.send(...).get() 同步等待结果。这样每条消息能确认发送成功再进入下一条。

四、配置文件说明(app.yaml)

使用 YAML 作为配置文件格式,结构清晰、容易扩展:

yaml

kafka:

bootstrap: localhost:9092

topic: warnTest2

key: ""

message:

file: message.txt //这里改成你要读取发送到kafka的json路径(例D:\sxyWorkSpace\kafka.txt)

send:

count: 1

intervalMs: 0

appendIndex: false

字段解释:

  • kafka.bootstrap:Kafka Broker 地址

  • kafka.topic:发送目标 Topic

  • kafka.key:消息 key,可为空

  • message.file:消息文件路径(支持相对路径)

  • send.count:整份文件重复发送次数

  • send.intervalMs:每条消息的间隔(毫秒)

  • send.appendIndex:是否在消息尾部追加递增序号

如果你想用外部配置文件,可以在启动时指定:

java -Dapp.config=D:\path\to\app.yaml -jar target/kafka-producer-demo-1.0.0.jar

五、消息文件格式(多 JSON,空行分隔)

示例:

{"alarmResultType":"END_ALARM","key":"491&&&1267587&&&2419492","alarmResult":{"id":"94872ebd-0a64-4beb-9067-d30fea5ed7a9"}}

{"alarmResultType":"NEW_ALARM","key":"491&&&1267587&&&2419492","alarmResult":{"id":"96872ebd-0a31-4beb-9067-d30fea5ed7a9"}}

{"alarmResultType":"UPP_MULTIPLE","key":"621&&&2081409&&&2372192","alarmResult":{"id":"14f680d9-5ef1-415f-bb8d-f09f54a3c25d"}}

注意:每条消息之间有一个空行,空行就是分隔符。

六、核心代码解读

1) 程序入口

java 复制代码
public class ProducerApp {
    /**
     * 应用入口:加载配置并执行发送。
     */
    public static void main(String[] args) throws Exception {
        // 从配置文件加载运行参数
        Config config = Config.load();
        // 按配置执行发送逻辑
        KafkaProducerRunner runner = new KafkaProducerRunner(config);
        runner.run();
    }
}

入口非常简洁:加载配置 -> 执行发送。

2) 配置加载(Config.load)

java 复制代码
public static Config load() throws Exception {
    // 从 app.yaml 读取配置,支持 -Dapp.config 指向外部文件
    String configPath = System.getProperty("app.config");

    Map<String, Object> data;
    try (InputStream input = openConfigStream(configPath)) {
        Yaml yaml = new Yaml();
        data = yaml.load(input);
    }

    String bootstrap = require(data, "kafka", "bootstrap");
    String topic = require(data, "kafka", "topic");
    String key = trimToNull(getString(data, "kafka", "key"));
    String messageFile = require(data, "message", "file");
    int count = parseInt(getString(data, "send", "count"), 1);
    long intervalMs = parseLong(getString(data, "send", "intervalMs"), 0L);
    boolean appendIndex = Boolean.parseBoolean(defaultString(getString(data, "send", "appendIndex"), "false"));

    if (count < 1) {
        throw new IllegalArgumentException("send.count 必须 >= 1");
    }
    if (intervalMs < 0) {
        throw new IllegalArgumentException("send.intervalMs 不能小于 0");
    }

    KafkaConfig kafka = new KafkaConfig(bootstrap, topic, key);
    MessageConfig message = new MessageConfig(messageFile);
    SendConfig send = new SendConfig(count, intervalMs, appendIndex);
    return new Config(kafka, message, send);
}

说明:

  • 通过 SnakeYAML 解析 YAML 文件

  • `require(...)` 保障必须配置项存在

  • 解析后拆分为 `KafkaConfig/MessageConfig/SendConfig` 三个实体

3) 多 JSON 分段读取

java 复制代码
public List<String> loadMessages() throws Exception {
    // 从文本文件读取,空行分隔,每一段 JSON 作为一条消息
    Path path = Paths.get(message.getFile());
    if (!path.isAbsolute()) {
        // 相对路径时,以运行时目录为基准
        path = Paths.get(System.getProperty("user.dir")).resolve(path).normalize();
    }
    List<String> messages = new ArrayList<>();
    StringBuilder buffer = new StringBuilder();
    List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
    for (String line : lines) {
        String trimmed = line == null ? "" : line.trim();
        if (trimmed.isEmpty()) {
            // 空行表示一段 JSON 结束
            if (buffer.length() > 0) {
                messages.add(buffer.toString().trim());
                buffer.setLength(0);
            }
            continue;
        }
        if (buffer.length() > 0) {
            buffer.append('\n');
        }
        buffer.append(line);
    }
    if (buffer.length() > 0) {
        messages.add(buffer.toString().trim());
    }
    if (messages.isEmpty()) {
        throw new IllegalArgumentException("message.file 为空,至少需要一段 JSON");
    }
    return messages;
}

这一段逻辑的核心就是"空行分段",这样就能支持跨行 JSON。

4) 发送执行器

java 复制代码
public void run() throws Exception {
    // Kafka Producer 的基础配置
    Properties props = new Properties();
    props.put("bootstrap.servers", config.getBootstrap());
    props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
    props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
    props.put("acks", "all");

    // 读取文件中的多条 JSON 消息
    List<String> messages = config.loadMessages();
    long total = (long) config.getCount() * (long) messages.size();
    System.out.println("准备发送消息,总数: " + total + ",topic: " + config.getTopic());

    // 同步发送,确保每条消息都发送成功
    try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
        long index = 0;
        for (int round = 0; round < config.getCount(); round++) {
            for (String message : messages) {
                String value = message;
                if (config.isAppendIndex()) {
                    // 可选地在消息尾部追加序号,便于区分
                    value = value + " " + index;
                }
                producer.send(new ProducerRecord<>(config.getTopic(), config.getKey(), value)).get();
                System.out.println("已发送: " + (index + 1) + "/" + total);
                if (config.getIntervalMs() > 0) {
                    // 发送间隔
                    Thread.sleep(config.getIntervalMs());
                }
                index++;
            }
        }
    }
    System.out.println("发送完成");
}

这段代码体现了几个点:

  • `Properties` 是 Kafka Producer 的基本配置

  • `send(...).get()` 是同步发送

  • `count` 表示重复发送整份文件

  • 控制台日志可以帮助你确认发送进度

七、如何运行

  1. 修改 app.yaml 中的 Kafka 地址与 topic

  2. 准备好 JSON 消息文件,并确保空行分隔

  3. 运行即可

八、常见问题

  1. Maven 依赖下载失败(PKIX 证书错误)

通常是镜像证书问题,建议换 Maven 中央仓库或导入证书。

  1. 发送到 Kafka 时出现"空消息"

旧版本是按行读取,空行可能被发送;现在已经改成空行分段,空行只做分隔不会发送。

  1. JSON 跨行导致拆分

当前支持跨行 JSON,空行分段即可。

九、发送示例

以Offset Explorer为例看一下是否发送成功

这是我的json文件,由于信息机密问题需要打码,我们可以看出来是3条json,应该发送三条kafka消息。

我们回到项目启动发送

可以看到发送了三条,显示发送成功,并且时间为15.25,我们去Offset Explorer看一下是否接收到了。

Offset Explorer显示15.25有三条记录,证明发送成功。

十、结语

这个 Demo 的目标不是"功能强大",而是"结构清晰、易理解、好修改"。当你需要批量测试 Kafka 消息、快速模拟业务数据时,它已经足够好用。如果要扩展到生产级别,也可以在这个基础上逐步加上日志框架、监控、重试策略等。

我把这个完整的demo和这个项目绑定了,大家可以自行去我主页下载,也可以在我主页加我vx向我要资源,祝各位事业顺利~

相关推荐
Lansonli1 小时前
大数据Spark(七十七):Action行动算子first、collect和collectAsMap使用案例
大数据·分布式·spark
Remember_9931 小时前
【数据结构】Java数据结构深度解析:栈(Stack)与队列(Queue)完全指南
java·开发语言·数据结构·算法·spring·leetcode·maven
期待のcode1 小时前
JVM 中对象进入老年代的时机
java·开发语言·jvm
派大鑫wink2 小时前
【Day37】MVC 设计模式:原理与手动实现简易 MVC 框架
java·设计模式·mvc
毕设源码-赖学姐2 小时前
【开题答辩全过程】以 基于java的医院床位管理系统的设计与开发 为例,包含答辩的问题和答案
java·开发语言
曹轲恒2 小时前
SpringBoot的热部署
java·spring boot·后端
Remember_9932 小时前
深入理解 Java String 类:从基础原理到高级应用
java·开发语言·spring·spring cloud·eclipse·tomcat
马达加斯加D2 小时前
缓存 --- Redis缓存的一致性
分布式·spring·缓存
程序员侠客行2 小时前
Mybatis插件原理及分页插件
java·后端·架构·mybatis