Storm 与 Kafka 集成完全指南:从原理到性能优化
-
- 前言
- 一、Storm 与 Kafka 的集成架构
-
- 1.1 整体架构图
- 1.2 核心组件
- 二、KafkaSpout:从 Kafka 读取数据
-
- 2.1 基础配置
- 2.2 关键配置参数详解
- 2.3 起始偏移量策略
- 2.4 多主题订阅
- 2.5 自定义 Tuple 转换
- 三、KafkaBolt:写入数据到 Kafka
-
- 3.1 基础配置
- 3.2 字段映射策略
- 3.3 动态主题选择
- 四、旧版本集成方式(0.8.x)
- 五、消息可靠性保障
-
- 5.1 KafkaSpout 的可靠性机制
- 5.2 偏移量管理
- 5.3 消息重试机制
- 六、性能优化策略
-
- 6.1 并行度优化
- 6.2 批量拉取优化
- 6.3 流控配置
- 6.4 缓冲区调优
- 6.5 序列化优化
- 七、常见问题与解决方案
-
- 7.1 消息重复处理
- 7.2 消费滞后问题
- 7.3 偏移量丢失问题
- 7.4 性能瓶颈定位
- 八、完整示例:实时日志处理
- 总结
|
🌺The Begin🌺点点关注,收藏不迷路🌺 |
前言
在实时流处理领域,Kafka + Storm 是一对黄金搭档。Kafka 作为高吞吐的分布式消息队列,负责数据的缓冲和持久化;Storm 作为实时计算引擎,负责数据的处理和分析。两者的结合,构成了无数实时数据管道的核心。
本文将深入剖析 Storm 与 Kafka 的集成原理,从基础配置到高级优化,帮助读者构建一个既可靠又高效的实时数据处理系统。
一、Storm 与 Kafka 的集成架构
1.1 整体架构图
#mermaid-svg-Xq4SRedMmlEMC130{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Xq4SRedMmlEMC130 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Xq4SRedMmlEMC130 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Xq4SRedMmlEMC130 .error-icon{fill:#552222;}#mermaid-svg-Xq4SRedMmlEMC130 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Xq4SRedMmlEMC130 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Xq4SRedMmlEMC130 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Xq4SRedMmlEMC130 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Xq4SRedMmlEMC130 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Xq4SRedMmlEMC130 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Xq4SRedMmlEMC130 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Xq4SRedMmlEMC130 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Xq4SRedMmlEMC130 .marker.cross{stroke:#333333;}#mermaid-svg-Xq4SRedMmlEMC130 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Xq4SRedMmlEMC130 p{margin:0;}#mermaid-svg-Xq4SRedMmlEMC130 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-Xq4SRedMmlEMC130 .cluster-label text{fill:#333;}#mermaid-svg-Xq4SRedMmlEMC130 .cluster-label span{color:#333;}#mermaid-svg-Xq4SRedMmlEMC130 .cluster-label span p{background-color:transparent;}#mermaid-svg-Xq4SRedMmlEMC130 .label text,#mermaid-svg-Xq4SRedMmlEMC130 span{fill:#333;color:#333;}#mermaid-svg-Xq4SRedMmlEMC130 .node rect,#mermaid-svg-Xq4SRedMmlEMC130 .node circle,#mermaid-svg-Xq4SRedMmlEMC130 .node ellipse,#mermaid-svg-Xq4SRedMmlEMC130 .node polygon,#mermaid-svg-Xq4SRedMmlEMC130 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Xq4SRedMmlEMC130 .rough-node .label text,#mermaid-svg-Xq4SRedMmlEMC130 .node .label text,#mermaid-svg-Xq4SRedMmlEMC130 .image-shape .label,#mermaid-svg-Xq4SRedMmlEMC130 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Xq4SRedMmlEMC130 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Xq4SRedMmlEMC130 .rough-node .label,#mermaid-svg-Xq4SRedMmlEMC130 .node .label,#mermaid-svg-Xq4SRedMmlEMC130 .image-shape .label,#mermaid-svg-Xq4SRedMmlEMC130 .icon-shape .label{text-align:center;}#mermaid-svg-Xq4SRedMmlEMC130 .node.clickable{cursor:pointer;}#mermaid-svg-Xq4SRedMmlEMC130 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Xq4SRedMmlEMC130 .arrowheadPath{fill:#333333;}#mermaid-svg-Xq4SRedMmlEMC130 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Xq4SRedMmlEMC130 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Xq4SRedMmlEMC130 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Xq4SRedMmlEMC130 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Xq4SRedMmlEMC130 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Xq4SRedMmlEMC130 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Xq4SRedMmlEMC130 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Xq4SRedMmlEMC130 .cluster text{fill:#333;}#mermaid-svg-Xq4SRedMmlEMC130 .cluster span{color:#333;}#mermaid-svg-Xq4SRedMmlEMC130 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Xq4SRedMmlEMC130 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Xq4SRedMmlEMC130 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Xq4SRedMmlEMC130 .icon-shape,#mermaid-svg-Xq4SRedMmlEMC130 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Xq4SRedMmlEMC130 .icon-shape p,#mermaid-svg-Xq4SRedMmlEMC130 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Xq4SRedMmlEMC130 .icon-shape rect,#mermaid-svg-Xq4SRedMmlEMC130 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Xq4SRedMmlEMC130 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Xq4SRedMmlEMC130 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Xq4SRedMmlEMC130 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
结果存储层
Storm 计算层
数据源层
消息队列层
Producer
Kafka 集群Topic: input
Producer
KafkaSpout
Bolt1
Bolt2
KafkaBolt
Kafka 集群Topic: output
1.2 核心组件
Storm 与 Kafka 的集成主要通过两个核心组件实现 :
| KafkaSpout | 从 Kafka 主题读取数据,发射到 Storm 拓扑 | Kafka → Storm |
| KafkaBolt | 将 Storm 处理结果写入 Kafka 主题 | Storm → Kafka |
二、KafkaSpout:从 Kafka 读取数据
2.1 基础配置
KafkaSpout 的核心配置类是 KafkaSpoutConfig,它使用 Builder 模式构建 :
import org.apache.storm.kafka.spout.KafkaSpout;
import org.apache.storm.kafka.spout.KafkaSpoutConfig;
public class KafkaSpoutExample {
public static KafkaSpout<String, String> createKafkaSpout() {
// 1. 基本配置(必须项)
KafkaSpoutConfig<String, String> config =
KafkaSpoutConfig.builder("localhost:9092", "input-topic")
.setGroupId("storm-consumer-group") // 消费者组ID
.setFirstPollOffsetStrategy( // 起始消费位置
KafkaSpoutConfig.FirstPollOffsetStrategy.UNCOMMITTED_EARLIEST)
.setProcessingGuarantee( // 处理语义
KafkaSpoutConfig.ProcessingGuarantee.AT_LEAST_ONCE)
.build();
return new KafkaSpout<>(config);
}
// 在拓扑中使用
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("kafka-spout", createKafkaSpout(), 3);
}
2.2 关键配置参数详解
| bootstrap.servers | Kafka 集群地址 | "kafka1:9092,kafka2:9092" | 连接 Kafka |
| setGroupId | 消费者组 ID | 应用名 + 拓扑名 | 偏移量管理 |
| setFirstPollOffsetStrategy | 初始消费位置 | UNCOMMITTED_EARLIEST | 从最早的未提交偏移量开始 |
| setProcessingGuarantee | 处理语义 | AT_LEAST_ONCE | 至少一次语义 |
| setMaxPollRecords | 单次拉取最大记录数 | 500 | 控制拉取批次大小 |
| setOffsetCommitPeriodMs | 偏移量提交周期 | 10000 (10秒) | 控制提交频率 |
2.3 起始偏移量策略
FirstPollOffsetStrategy 决定了 Spout 启动时从哪里开始消费 :
public enum FirstPollOffsetStrategy {
// 从最早可用的偏移量开始(忽略已提交的偏移量)
EARLIEST,
// 从最新偏移量开始(忽略已提交的偏移量)
LATEST,
// 如果 ZooKeeper 中有已提交偏移量,则从此开始;否则从最早开始
UNCOMMITTED_EARLIEST,
// 如果 ZooKeeper 中有已提交偏移量,则从此开始;否则从最新开始
UNCOMMITTED_LATEST
}
推荐使用:UNCOMMITTED_EARLIEST,这样既能从断点续传,又能在第一次启动时从最早数据开始,避免遗漏。
2.4 多主题订阅
KafkaSpout 支持同时订阅多个主题 :
// 订阅固定主题列表
KafkaSpoutConfig<String, String> config =
KafkaSpoutConfig.builder("localhost:9092", "topic1", "topic2", "topic3")
.build();
// 使用正则表达式订阅(自动匹配新创建的主题)
KafkaSpoutConfig<String, String> config =
KafkaSpoutConfig.builder("localhost:9092", Pattern.compile("logs.*"))
.build();
2.5 自定义 Tuple 转换
默认情况下,KafkaSpout 会将 Kafka 记录转换为包含 topic、partition、offset、key、value 的 Tuple 。可以通过 RecordTranslator 自定义转换逻辑:
// 自定义转换器:只提取 value,并输出到指定流
KafkaSpoutConfig<String, String> config =
KafkaSpoutConfig.builder("localhost:9092", "input-topic")
.setRecordTranslator((record) -> {
// 记录包含:record.topic(), record.partition(), record.offset(), record.key(), record.value()
return new Values(record.value());
}, new Fields("message"))
.build();
// 多流输出:根据主题分流
ByTopicRecordTranslator<String, String> translator = new ByTopicRecordTranslator<>(
// 默认流
(r) -> new Values(r.value()), new Fields("value"), "default-stream"
);
// 特定主题分流
translator.forTopic("error-logs",
(r) -> new Values(r.value()), new Fields("error"), "error-stream");
config = KafkaSpoutConfig.builder("localhost:9092", ".*")
.setRecordTranslator(translator)
.build();
三、KafkaBolt:写入数据到 Kafka
3.1 基础配置
import org.apache.storm.kafka.bolt.KafkaBolt;
import org.apache.storm.kafka.bolt.mapper.FieldNameBasedTupleToKafkaMapper;
import org.apache.storm.kafka.bolt.selector.DefaultTopicSelector;
public class KafkaBoltExample {
public static KafkaBolt<String, String> createKafkaBolt() {
// 1. 配置生产者属性
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("acks", "1"); // 确认机制
props.put("key.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
props.put("retries", 3); // 重试次数
// 2. 创建 KafkaBolt
KafkaBolt<String, String> bolt = new KafkaBolt<String, String>()
.withProducerProperties(props) // 生产者配置
.withTopicSelector(new DefaultTopicSelector("output-topic")) // 目标主题
.withTupleToKafkaMapper( // 字段映射
new FieldNameBasedTupleToKafkaMapper("key", "message"));
return bolt;
}
}
3.2 字段映射策略
FieldNameBasedTupleToKafkaMapper 负责将 Tuple 中的字段映射为 Kafka 消息的 key 和 value :
// 默认构造器:使用字段 "key" 和 "message"
FieldNameBasedTupleToKafkaMapper<String, String> mapper =
new FieldNameBasedTupleToKafkaMapper<>();
// 自定义字段名:使用 "userId" 作为 key,使用 "event" 作为 value
FieldNameBasedTupleToKafkaMapper<String, String> mapper =
new FieldNameBasedTupleToKafkaMapper<>("userId", "event");
// 在 Bolt 中使用
KafkaBolt<String, String> bolt = new KafkaBolt<>()
.withTupleToKafkaMapper(mapper);
3.3 动态主题选择
如果需要在运行时根据数据内容决定写入哪个主题,可以实现 KafkaTopicSelector :
public class DynamicTopicSelector implements KafkaTopicSelector {
@Override
public String getTopics(Tuple tuple) {
String eventType = tuple.getStringByField("type");
// 根据事件类型选择不同主题
switch(eventType) {
case "ERROR":
return "error-topic";
case "WARN":
return "warning-topic";
default:
return "info-topic";
}
}
}
// 使用自定义选择器
KafkaBolt<String, String> bolt = new KafkaBolt<>()
.withProducerProperties(props)
.withTopicSelector(new DynamicTopicSelector());
四、旧版本集成方式(0.8.x)
对于使用 Kafka 0.8.x 版本的遗留系统,Storm 提供了不同的集成方式 :
// 1. 使用 ZkHosts 获取 broker 信息
BrokerHosts hosts = new ZkHosts("zk1:2181,zk2:2181,zk3:2181");
// 2. 创建 SpoutConfig
String topic = "input-topic";
String zkRoot = "/kafka-spout"; // ZooKeeper 中存储偏移量的根路径
String spoutId = "consumer-id"; // 唯一标识
SpoutConfig spoutConfig = new SpoutConfig(hosts, topic, zkRoot, spoutId);
// 3. 配置序列化方案
spoutConfig.scheme = new SchemeAsMultiScheme(new StringScheme());
// 4. 配置起始偏移量
spoutConfig.startOffsetTime = kafka.api.OffsetRequest.EarliestTime();
// 5. 创建 Spout
KafkaSpout kafkaSpout = new KafkaSpout(spoutConfig);
重要提示:旧版本 API 与 Storm 1.0+ 存在兼容性问题,使用时需确保 storm-kafka 版本与 Storm 版本一致 。
五、消息可靠性保障
5.1 KafkaSpout 的可靠性机制
KafkaSpout 提供了三种处理语义 :
KafkaSpoutConfig<String, String> config =
KafkaSpoutConfig.builder("localhost:9092", "topic")
.setProcessingGuarantee(KafkaSpoutConfig.ProcessingGuarantee.AT_LEAST_ONCE)
.build();
// 可选值:
// – AT_MOST_ONCE: 最多一次,可能丢数据
// – AT_LEAST_ONCE: 至少一次,可能重复(默认)
// – EXACTLY_ONCE: 精确一次,不丢不重(需配合事务)
5.2 偏移量管理
KafkaSpout 会自动管理消费偏移量,通过定期提交到 Kafka 的 __consumer_offsets 主题(新版)或 ZooKeeper(旧版)。
// 配置偏移量提交周期
KafkaSpoutConfig<String, String> config =
KafkaSpoutConfig.builder("localhost:9092", "topic")
.setOffsetCommitPeriodMs(10000) // 10秒提交一次
.build();
5.3 消息重试机制
当消息处理失败时,KafkaSpout 支持可配置的重试策略 :
SpoutConfig spoutConfig = new SpoutConfig(hosts, topic, zkRoot, id);
// 使用指数退避重试
spoutConfig.failedMsgRetryManagerClass =
ExponentialBackoffMsgRetryManager.class.getName();
spoutConfig.retryInitialDelayMs = 1000; // 初始延迟 1秒
spoutConfig.retryDelayMultiplier = 2.0; // 每次翻倍
spoutConfig.retryDelayMaxMs = 60000; // 最大延迟 60秒
spoutConfig.retryLimit = 5; // 最大重试次数
六、性能优化策略
6.1 并行度优化
KafkaSpout 的并行度不应超过 Kafka 主题的分区数 :
// 假设 Kafka 主题有 8 个分区
int partitionCount = 8;
// ✅ 推荐:并行度等于分区数
builder.setSpout("kafka-spout", kafkaSpout, partitionCount);
// ❌ 不推荐:并行度大于分区数(多余线程空闲)
builder.setSpout("bad-spout", kafkaSpout, 12);
// ❌ 不推荐:并行度小于分区数(部分分区未被消费)
builder.setSpout("waste-spout", kafkaSpout, 3);
6.2 批量拉取优化
通过调整 Kafka 消费者的批量参数,可以显著提升吞吐量 :
KafkaSpoutConfig<String, String> config =
KafkaSpoutConfig.builder("localhost:9092", "topic")
.setMaxPollRecords(500) // 每次拉取最大记录数
.setProp(ConsumerConfig.FETCH_MIN_BYTES_CONFIG, 1024 * 1024) // 最小拉取 1MB
.setProp(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG, 100) // 最大等待 100ms
.build();
6.3 流控配置
通过 topology.max.spout.pending 控制 Spout 的发射速率,避免压垮下游 :
Config conf = new Config();
// 设置最大待处理消息数
conf.setMaxSpoutPending(2000);
// 当系统负载过高时,自动降低 Spout 发射速率
6.4 缓冲区调优
调整 Netty 缓冲区大小,优化网络传输 :
Config conf = new Config();
// Netty 缓冲区大小(默认 5MB)
conf.put(Config.STORM_MESSAGING_NETTY_BUFFER_SIZE, 10485760); // 10MB
// Worker 接收缓冲区
conf.put(Config.TOPOLOGY_EXECUTOR_RECEIVE_BUFFER_SIZE, 16384);
conf.put(Config.TOPOLOGY_EXECUTOR_SEND_BUFFER_SIZE, 16384);
6.5 序列化优化
使用 Kryo 序列化器,减少序列化开销 :
Config conf = new Config();
// 注册自定义类的序列化器
Config.registerSerialization(conf, User.class);
// 禁用 Java 序列化回退
conf.put(Config.TOPOLOGY_FALL_BACK_ON_JAVA_SERIALIZATION, false);
七、常见问题与解决方案
7.1 消息重复处理
问题:在 At-Least-Once 语义下,由于超时或重试可能导致消息重复处理 。
解决方案:在 Bolt 中添加幂等性检查
public class IdempotentBolt extends BaseRichBolt {
private Jedis jedis; // Redis 客户端
@Override
public void execute(Tuple tuple) {
String msgId = tuple.getStringByField("offset"); // 使用 offset 作为唯一 ID
// 1. 检查是否已处理
if (jedis.exists("processed:" + msgId)) {
collector.ack(tuple); // 已处理,直接确认
return;
}
// 2. 业务处理
processData(tuple);
// 3. 标记已处理
jedis.setex("processed:" + msgId, 3600, "1");
collector.ack(tuple);
}
}
7.2 消费滞后问题
问题:当 Kafka 堆积大量数据时,Spout 可能超时,导致消息重发 。
解决方案:
7.3 偏移量丢失问题
问题:重新部署拓扑时,如果修改了 zkRoot 或 id,会导致偏移量丢失 。
重要警告:重新部署拓扑时,务必保持 SpoutConfig.zkRoot 和 SpoutConfig.id 不变,否则 Spout 将无法从 ZooKeeper 读取之前的消费状态,可能导致数据丢失或重复。
7.4 性能瓶颈定位
使用 Storm UI 监控以下指标 :
| Capacity | < 0.8 | > 1 表示处理能力不足 |
| Execute latency | < 10ms | 持续上升说明有瓶颈 |
| Emitted/Transferred | 接近 | 差异大说明有过滤或丢数据 |
| Failed | 0 | > 0 需要检查异常 |
八、完整示例:实时日志处理
public class KafkaStormLogProcessor {
public static void main(String[] args) throws Exception {
TopologyBuilder builder = new TopologyBuilder();
// 1. 配置 KafkaSpout
KafkaSpoutConfig<String, String> spoutConfig =
KafkaSpoutConfig.builder("kafka1:9092,kafka2:9092", "raw-logs")
.setGroupId("storm-log-processor")
.setFirstPollOffsetStrategy(
KafkaSpoutConfig.FirstPollOffsetStrategy.UNCOMMITTED_EARLIEST)
.setProcessingGuarantee(
KafkaSpoutConfig.ProcessingGuarantee.AT_LEAST_ONCE)
.setMaxPollRecords(500)
.setOffsetCommitPeriodMs(10000)
.setRecordTranslator(
(r) -> new Values(r.value()),
new Fields("log"))
.build();
builder.setSpout("kafka-spout", new KafkaSpout<>(spoutConfig), 8);
// 2. 解析 Bolt
builder.setBolt("parse-bolt", new LogParseBolt(), 12)
.shuffleGrouping("kafka-spout");
// 3. 过滤 Bolt
builder.setBolt("filter-bolt", new LogFilterBolt(), 10)
.fieldsGrouping("parse-bolt", new Fields("level"));
// 4. 聚合 Bolt
builder.setBolt("agg-bolt", new LogAggBolt(), 6)
.fieldsGrouping("filter-bolt", new Fields("service"));
// 5. 配置 KafkaBolt
Properties producerProps = new Properties();
producerProps.put("bootstrap.servers", "kafka1:9092,kafka2:9092");
producerProps.put("acks", "1");
producerProps.put("key.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
producerProps.put("value.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
KafkaBolt<String, String> kafkaBolt = new KafkaBolt<String, String>()
.withProducerProperties(producerProps)
.withTopicSelector(new DefaultTopicSelector("processed-logs"))
.withTupleToKafkaMapper(
new FieldNameBasedTupleToKafkaMapper<>("service", "count"));
builder.setBolt("kafka-bolt", kafkaBolt, 4)
.shuffleGrouping("agg-bolt");
// 6. 配置
Config conf = new Config();
conf.setNumWorkers(10);
conf.setMaxSpoutPending(2000);
conf.setMessageTimeoutSecs(120);
// 7. 提交拓扑
if (args.length > 0) {
StormSubmitter.submitTopology("log-processor", conf,
builder.createTopology());
} else {
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("log-processor", conf,
builder.createTopology());
Thread.sleep(60000);
cluster.shutdown();
}
}
}
总结
Storm 与 Kafka 的集成为构建可靠的实时数据管道提供了强大的基础:
| KafkaSpout | 数据输入 | bootstrap.servers、groupId、processingGuarantee |
| KafkaBolt | 数据输出 | bootstrap.servers、serializer、topicSelector |
| 可靠性 | 不丢消息 | AT_LEAST_ONCE + ACK 机制 |
| 性能优化 | 高吞吐 | 并行度匹配、批量拉取、缓冲区调优 |
核心原则:
思考题:在金融交易系统中,要求 Exactly-Once 语义,但性能也很关键。你会如何配置 KafkaSpout 和 Bolt 来平衡可靠性和吞吐量?欢迎在评论区分享你的方案!

|
🌺The End🌺点点关注,收藏不迷路🌺 |



