一、背景与目标
很多业务场景需要定时或批量把数据推送到 Kafka,比如报警系统、日志采集、数据同步等。为了让测试更简单、更可控,我们搭了一个"自动发送 Kafka 消息"的小型 Demo。它的目标非常明确:
-
通过配置文件指定 Kafka 地址、Topic、消息文件路径
-
支持一个文件里写多条 JSON,每条 JSON 都能按"段"发送
-
发送过程有简单日志,方便确认进度
-
代码清晰、容易扩展
本文基于该项目,讲清楚整体设计、关键代码和使用方式,让你能快速理解并复用。
二、项目结构概览
项目是 Maven 构建的 Java 工程,核心代码都在 src/main/java/com/example/kafka 下:
-
ProducerApp:程序入口
-
Config:读取 YAML 配置、加载消息内容
-
KafkaProducerRunner:发送执行器
-
KafkaConfig/MessageConfig/SendConfig:配置实体
资源文件在 src/main/resources:
- app.yaml:配置文件
消息文件是一个普通文本文件,比如 message.txt,可放在工程根目录或指定绝对路径。
三、关键设计思路
- 配置驱动:
所有运行参数都由 app.yaml 控制,避免命令行参数过多,便于批量测试与部署。
- 消息分段:
真实业务中的 JSON 经常跨行。单纯按"每行一条消息"会把一个 JSON 拆成多条。因此这里采用"空行分段"的策略:
-
空行作为消息边界
-
空行之间的内容作为一个完整 JSON
- 同步发送:
为了让测试结果更可控,使用 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` 表示重复发送整份文件
-
控制台日志可以帮助你确认发送进度
七、如何运行
-
修改 app.yaml 中的 Kafka 地址与 topic
-
准备好 JSON 消息文件,并确保空行分隔
-
运行即可
八、常见问题
- Maven 依赖下载失败(PKIX 证书错误)
通常是镜像证书问题,建议换 Maven 中央仓库或导入证书。
- 发送到 Kafka 时出现"空消息"
旧版本是按行读取,空行可能被发送;现在已经改成空行分段,空行只做分隔不会发送。
- JSON 跨行导致拆分
当前支持跨行 JSON,空行分段即可。
九、发送示例
以Offset Explorer为例看一下是否发送成功
这是我的json文件,由于信息机密问题需要打码,我们可以看出来是3条json,应该发送三条kafka消息。

我们回到项目启动发送

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

Offset Explorer显示15.25有三条记录,证明发送成功。
十、结语
这个 Demo 的目标不是"功能强大",而是"结构清晰、易理解、好修改"。当你需要批量测试 Kafka 消息、快速模拟业务数据时,它已经足够好用。如果要扩展到生产级别,也可以在这个基础上逐步加上日志框架、监控、重试策略等。
我把这个完整的demo和这个项目绑定了,大家可以自行去我主页下载,也可以在我主页加我vx向我要资源,祝各位事业顺利~