官网地址:https://storm.apache.org/index.html
Apache Storm 是一个开源的、分布式的实时计算系统,专为处理流式数据而设计。它能够处理大量数据流并在极低的延迟下提供实时的结果。相比于传统的批处理系统,Storm 具有处理无限数据流的能力,支持非常高的可扩展性和容错机制。Storm 可以适用于多种编程语言,具有高度的灵活性。
Apache Storm 最早由 Nathan Marz 于2011年开发,最初作为 BackType 公司的一部分,旨在解决社交媒体分析中的实时数据处理问题。后来,Storm 被 Twitter 收购并用于其内部的数据处理需求。随着 Storm 的快速发展和成功,Twitter 于2013年将 Storm 开源,并捐赠给了 Apache 基金会,成为了 Apache 顶级项目之一。
自开源以来,Storm 一直在不断改进,逐渐成为大规模流数据处理系统中的佼佼者。它被广泛应用于许多互联网公司和传统企业中,用于处理实时分析、监控、在线推荐系统等任务。
Apache Storm 被广泛应用于需要处理连续流数据并作出快速决策的领域,以下是几个典型的应用场景:
Apache Storm 的分布式架构要求在集群环境中运行。
Storm 通常需要与 Zookeeper 协同工作,用于节点之间的协调与同步。
在开始搭建单节点 Storm 集群之前,确保系统已经安装好 Java(JDK 8 或以上)和 Zookeeper。
# 下载
[appuser@localhost app]$ curl -O https://dlcdn.apache.org/storm/apache-storm-2.8.0/apache-storm-2.8.0.tar.gz
# 解压
[appuser@localhost app]$ tar -zxvf apache-storm-2.8.0.tar.gz
# 进入安装目录
[appuser@localhost app]$ cd apache-storm-2.8.0
# 修改配置
# 注意zookeeper的ip
# 配置说明见官网:https://storm.apache.org/releases/2.8.0/Configuration.html
[appuser@localhost apache-storm-2.8.0]$ tee -a conf/storm.yaml << 'EOF'
storm.zookeeper.servers:
- "localhost"
nimbus.seeds: ["localhost"]
storm.local.dir: "/app/apache-storm-2.8.0/data"
supervisor.slots.ports:
- 6700
- 6701
- 6702
EOF
# 启动 nimbus、supervisor 进程
[appuser@localhost apache-storm-2.8.0]$ bin/storm nimbus &
[appuser@localhost apache-storm-2.8.0]$ bin/storm supervisor &
# 启动UI
[appuser@localhost apache-storm-2.8.0]$ bin/storm ui &
通过浏览器访问 http://localhost:8080 可以查看 Storm 集群的状态。
若是部署在虚拟机,想在主机访问,记得先关闭下firewalld(或者开放8080端口),然后使用ip访问(ip a 查询下下虚拟机ip)。
# 添加端口规则
sudo firewall-cmd --zone=public --add-port=<端口号>/tcp --permanent # 如 80 或 8080
sudo firewall-cmd --reload
# 关闭firewalld:systemctl stop firewalld
多节点 Storm 集群可以同时运行多个 Nimbus 和 Supervisor 进程,以实现更高的并发处理能力。多节点集群配置与单节点类似,但需要根据每个节点的角色进行适当调整。
storm.zookeeper.servers:
- "zookeeper1"
- "zookeeper2"
- "zookeeper3"
nimbus.seeds: ["nimbus1", "nimbus2", "nimbus3"]
storm.local.dir: "/var/storm"
bin/storm nimbus &
storm.zookeeper.servers:
- "zookeeper1"
- "zookeeper2"
- "zookeeper3"
storm.local.dir: "/var/storm"
supervisor.slots.ports:
- 6700
- 6701
- 6702
- 6703
bin/storm supervisor &
启动 UI 服务监控集群:bin/storm ui
使用 http://nimbus1:8080 或 http://nimbus2:8080 来访问 Storm UI 界面,监控集群的运行情况。
配置文件位置:storm.yaml文件通常位于Storm安装目录下的conf文件夹中。
配置更新:修改storm.yaml文件后,需要重启Storm服务以使新的配置生效。
默认值:每个配置项在Storm代码库的defaults.yaml文件中都有一个默认值,可以通过在storm.yaml中进行覆盖。
优先级:配置信息的优先级依次为:defaults.yaml < storm.yaml < 拓扑配置 < 内置型组件信息配置 < 外置型组件信息配置。
# 指定ZooKeeper服务器列表,Storm使用ZooKeeper进行集群的协调和管理。
storm.zookeeper.servers:
- "zookeeper1.example.com"
- "zookeeper2.example.com"
- "zookeeper3.example.com"
# ZooKeeper服务器的连接端口,默认为2181。
storm.zookeeper.port: 2181
# 在ZooKeeper中Storm的根目录位置,用于存储Storm的元数据。
storm.zookeeper.root: "/storm"
# ZooKeeper会话超时时间(毫秒)。
storm.zookeeper.session.timeout: 20000
# 连接到ZooKeeper的超时时间(毫秒)。
storm.zookeeper.connection.timeout: 15000
# Nimbus 主节点的主机名或 IP 地址列表(通常为单节点,但可配置高可用)。
nimbus.seeds: ["nimbus1.example.com", "nimbus2.example.com"]
# Nimbus进程的JVM启动参数。
nimbus.childopts: "-Xmx1024m"
# 定义 Supervisor 节点上可用的 Worker 端口列表(每个端口对应一个 Worker 进程)。
supervisor.slots.ports: [6700, 6701, 6702, 6703]
# Supervisor进程的JVM启动参数。
supervisor.childopts: "-Xmx768m"
# Supervisor在本地存储临时数据的目录。
supervisor.local.dir: "/var/lib/storm/supervisor"
# Supervisor 节点可分配的总内存(MB)。Worker 的内存请求不能超过此值。
supervisor.memory.capacity.mb: 8192
# Supervisor 节点可分配的 CPU 核心数(需启用资源感知调度)。
supervisor.cpu.capacity: 16.0
# 单个 Worker 进程的堆内存大小(MB)。需根据任务负载调整。
worker.heap.memory.mb: 2048
# Worker 进程的 JVM 垃圾回收参数。
worker.gc.childopts: "-XX:+UseG1GC -XX:MaxGCPauseMillis=100"
# 指定拓扑启动时的 Worker 数量(可在提交拓扑时覆盖)。
topology.workers: 4
# Storm UI服务的端口号,默认为8080。Storm UI提供了一个Web界面,用于监控集群状态和任务执行情况。
ui.port: 8080
# 消息在被认为失败之前可以被重试的最大秒数。这个设置影响消息处理的容错能力。
topology.message.timeout.secs: 30
# 每个executor(即Spout或Bolt实例)使用的线程数。通过调整这个值可以优化并发性能。
topology.executor.threads: 1
# 为每个executor分配的内存量(MB)。合理设置这个值可以避免内存溢出或资源浪费。
topology.executor.memory.mb: 1024
# 为每个task分配的CPU核心数。这个设置有助于充分利用多核CPU的计算能力。
topology.task.cpu.cores: 1
# 单个 Spout Task 允许的最大未完成消息数(避免内存溢出)。
topology.max.spout.pending: null(无限制)
# 指定拓扑中 Ackers 的数量(负责消息可靠性)。设为 0 可禁用可靠性。
topology.acker.executors: 1
# 设置Storm集群的运行模式,可以是distributed(分布式模式)或local(本地模式)。在本地模式下,所有组件都在单个节点上执行,通常用于开发和测试。
storm.cluster.mode: "distributed"
# Storm日志文件的存储目录。
storm.log.dir: "/var/log/storm"
# 日志查看器服务的端口号。
logviewer.port: 8000
# Worker 进程的日志级别(如 INFO, DEBUG)。
worker.log.level: INFO
# Log4j2 配置文件目录,用于自定义日志格式和策略。
storm.log4j2.conf.dir: "/etc/storm/log4j2"
# 指定Storm在本地存储临时数据的目录,如jars、confs等。该目录必须存在,且Storm进程需要具有读写权限。
storm.local.dir: "/usr/local/storm/data"
# DRPC 服务的监听端口(若启用分布式 RPC)。
drpc.port: 3772
# Nimbus Thrift服务的端口号,默认为6627。
nimbus.thrift.port: 6627
# Netty消息传输的缓冲区大小(字节)。
storm.messaging.netty.buffer_size: 5242880
# Netty消息传输的最大重试次数。
storm.messaging.netty.max_retries: 30
# Netty消息传输的最小等待时间(毫秒)。
storm.messaging.netty.min_wait_ms: 1000
# Netty消息传输的最大等待时间(毫秒)。
storm.messaging.netty.max_wait_ms: 60000
# 简单认证机制的用户名。
storm.security.auth.transport.simple.user: "storm"
# 简单认证机制的密码。
storm.security.auth.transport.simple.password: "password"
# ZooKeeper 认证机制(如 digest)。
storm.zookeeper.auth.scheme: "digest"
storm.zookeeper.auth.payload: "user:password"
# 定义允许模拟其他用户提交拓扑的 ACL 列表。
nimbus.impersonation.acl: ["user1", "user2"]
# 指定 BlobStore 类型为 HDFS
storm.blobstore.type: hdfs
# HDFS 的根目录路径(所有拓扑的 JAR 文件将存储在此路径下)
storm.blobstore.hdfs.path: "/storm/blobstore"
# HDFS 的 NameNode 地址(根据实际集群配置填写)
storm.blobstore.hdfs.host: "hdfs://namenode:8020"
# HDFS 客户端配置(可选,指向 Hadoop 配置文件目录)
storm.blobstore.hdfs.config.dir: "/path/to/hadoop/conf"
# HDFS 文件副本数(默认 3,按需调整)
storm.blobstore.replication.factor: 3
#指定资源调度器, 启用资源感知调度(需配置 Worker 内存和 CPU
storm.scheduler: org.apache.storm.scheduler.resource.ResourceAwareScheduler
# 定义消息传输协议(如 org.apache.storm.messaging.netty.Context 使用 Netty)。
storm.messaging.transport: org.apache.storm.messaging.netty.Context
# 集成 LDAP/AD 等用户组映射服务。
storm.group.mapping.service: "org.apache.storm.security.auth.ShellBasedGroupsMapping"
在 Apache Storm 中,Topology 是一个数据处理流程的定义,它由一组 Spout 和 Bolt 组成,负责处理从数据源读取的数据流。编写和提交 Topology 是使用 Storm 的核心部分。以下介绍如何从编写简单的 Topology 开始,到如何优化和部署 Topology 到 Storm 集群。
在 Storm 中,Topology 由多个 Spout 和 Bolt 组成,通过流的方式将它们连接起来。我们将从创建一个简单的 Topology 开始,其中包含一个 Spout(生成数据源)和一个 Bolt(处理数据)。
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Fields;
import org.apache.storm.utils.Utils;
public class SimpleTopology {
public static void main(String[] args) {
// 创建一个 TopologyBuilder 实例
TopologyBuilder builder = new TopologyBuilder();
// 设置 Spout:RandomSentenceSpout 会生成随机句子
builder.setSpout("sentence-spout", new RandomSentenceSpout(), 1);
// 设置 Bolt:SplitSentenceBolt 会将句子分割成单词
builder.setBolt("split-bolt", new SplitSentenceBolt(), 2)
.shuffleGrouping("sentence-spout");
// 配置 Topology
Config conf = new Config();
conf.setNumWorkers(3);
conf.setDebug(true);
try {
StormSubmitter.submitTopology("simple-topology", conf, builder.createTopology());
} catch (AlreadyAliveException | InvalidTopologyException | AuthorizationException e) {
LOG.error(e.getMessage());
}
// 启动本地集群(用于开发测试)
// LocalCluster cluster = new LocalCluster();
// cluster.submitTopology("simple-topology", conf, builder.createTopology());
}
}
步骤概述:
Spout:从外部系统(如消息队列或数据库)读取数据,并将其转化为 Tuple 发送到下游的 Bolt。
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
import java.util.Map;
import java.util.Random;
public class RandomSentenceSpout extends BaseRichSpout {
private SpoutOutputCollector collector;
private Random random;
@Override
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
this.collector = collector;
this.random = new Random();
}
@Override
public void nextTuple() {
String[] sentences = {"Storm is awesome", "Big data processing", "Real time analytics"};
String sentence = sentences[random.nextInt(sentences.length)];
collector.emit(new Values(sentence));
}
@Override
public void declareOutputFields(org.apache.storm.topology.OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("sentence"));
}
}
Spout通常继承自BaseRichSpout或实现IRichSpout接口。推荐使用BaseRichSpout。
主要方法:
open(Map conf, TopologyContext context, SpoutOutputCollector collector)
:在Spout初始化时调用,用于设置Spout运行所需的环境。nextTuple()
:生成下一个元组。Storm框架会不断调用这个方法,直到它返回一个非空的元组或抛出异常。declareOutputFields(OutputFieldsDeclarer declarer)
:声明Spout输出的字段。close()
:在Spout关闭时调用,用于执行清理工作。activate()和deactivate()
:用于控制Spout的激活和去激活状态。在去激活状态下,nextTuple()方法不会被调用。ack(Object msgId)和fail(Object msgId)
:用于确认或失败处理。在可靠的消息处理中,这些方法用于通知Storm元组是否被成功处理。Bolt 接收 Spout 或其他 Bolt 发送的 Tuple,进行处理(如过滤、聚合、转换等)。
Bolt通常继承自BaseRichBolt或实现IRichBolt接口。推荐使用BaseRichBolt,因为它提供了更多便利的方法。
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.BasicOutputCollector;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
public class SplitSentenceBolt extends BaseRichSpout {
@Override
public void execute(Tuple input, BasicOutputCollector collector) {
String sentence = input.getStringByField("sentence");
for (String word : sentence.split(" ")) {
collector.emit(new Values(word));
}
}
@Override
public void declareOutputFields(org.apache.storm.topology.OutputFieldsDeclarer declarer) {
declarer.declare(new org.apache.storm.tuple.Fields("word"));
}
}
主要方法:
prepare(Map stormConf, TopologyContext context, OutputCollector collector)
:在Bolt初始化时调用,用于设置Bolt运行所需的环境,如获取配置信息和输出收集器。execute(Tuple input)
:处理输入的元组(Tuple)。每个元组代表一个数据流中的一条记录。declareOutputFields(OutputFieldsDeclarer declarer)
:声明Bolt输出的字段。这是定义拓扑中数据流模式的重要步骤。cleanup()
:在Bolt关闭时调用,用于执行清理工作,但该方法不一定会被调用,特别是在使用kill -9命令强制终止进程时。在开发环境中,我们通常在本地运行拓扑进行测试,但在生产环境中,需要将拓扑提交到 Storm 集群中运行。
部署步骤:
mvn clean package
storm jar target/storm-topology.jar com.example.SimpleTopology simple-topology
http://:8080
。storm kill simple-topology
storm jar target/storm-topology.jar com.example.SimpleTopology simple-topology
(1) 命令解析
storm jar
:Storm 客户端命令,用于提交 JAR 包中的拓扑。target/storm-topology.jar
:打包好的拓扑 JAR 文件路径(通常由 Maven/Gradle 构建生成)。com.example.SimpleTopology
:JAR 包中的主类,负责定义和提交拓扑。simple-topology
:用户指定的拓扑名称(在集群中需唯一)。target/storm-topology.jar
。main
方法:com.example.SimpleTopology
类的 main
方法被调用。Submitted topology: simple-topology
,表明提交成功。storm list
命令或 Storm UI 可看到 simple-topology
处于 ACTIVE
状态。
storm.yaml
中 Nimbus 地址错误,会抛出 Could not submit topology
。ClassNotFoundException: com.example.SimpleTopology
(类名拼写错误或未打包到 JAR 中)。simple-topology
已存在,报错 Topology with name 'simple-topology' already exists
。NoClassDefFoundError
(需确保 JAR 包包含所有依赖)。任务分配计划(Assignment)是Nimbus为Topology生成的执行蓝图,主要包括以下内容:
host:port
信息(如host1:6700
)。Executor [1-3]
运行在Worker host1:6700
,负责处理Task ID 1001-1003
。[1001, 1002, 1003]
)。storm.yaml
中的参数)。为了提高 Topology 的性能,我们可以对 Topology 进行配置和调优。调优的关键点包括:
builder.setBolt("split-bolt", new SplitSentenceBolt(), 2)
.shuffleGrouping("sentence-spout");
topology.worker.cpu: 2.0
topology.worker.memory.mb: 4096
Apache Storm 的架构设计为高度并行和分布式,旨在支持大规模流数据的实时处理。Storm 的核心组件包括 Nimbus、Supervisor、Worker、Executor、Task(Spout / Bolt),它们共同协作实现实时数据流的高效处理。
Apache Storm 是一个流处理引擎,它可以持续处理不断到来的数据流(streams)。Storm 允许用户构建拓扑(Topology)来定义数据流的路径以及处理的逻辑。在这种拓扑中,数据从源(Spout)开始流入,通过一系列的处理节点(Bolt)进行转换或处理,最终得到输出结果。Storm 的架构基于并行执行的理念,支持高吞吐量和低延迟的数据处理。
Storm 集群中有两类节点:主控节点(Master Node)和工作节点(Worker Node)。
Nimbus是集群的核心协调组件,负责任务调度、监控和故障恢复。
Nimbus 职责:
/assignments/
)。/workers和/supervisors
节点监控组件活跃状态。/storms/
)。Supervisor 是 Storm 集群的工作节点执行引擎,核心职责是动态管理 Worker 生命周期,确保实时任务的高效运行。其通过 ZooKeeper 与 Nimbus 协同,实现分布式环境下的任务调度、故障恢复和资源管理。
Worker 是一个 JVM 进程,运行在 Supervisor 节点上,负责执行拓扑(Topology)中的任务(Task)。每个 Worker 属于一个特定的拓扑,一个 Supervisor 可以运行多个 Worker。
Worker 负责实际的数据处理与传输。其内部通过高效的线程模型(Executor)、队列机制(Disruptor)和网络通信(Netty)实现高吞吐、低延迟的数据流处理。
特点:
declareOutputFields
和prepare
方法)。Executor 是 Worker 进程中的一个JVM线程,负责执行一个或多个 Task(减少线程切换开销)。
Executor 之间通过线程间通信(如 Disruptor 队列)传递数据,避免跨进程的网络延迟。
Executor 工作流程:
nextTuple()
生成新Tuple并发射。execute()
方法处理。ack
(成功)或fail
(失败)信号。while True:
# 1. 检查流控是否允许发送
if not flow_control_allowed():
sleep(interval)
continue
# 2. 检查是否有待处理的Ack(可靠性模式)
if reliability_enabled and pending_tuples >= max_spout_pending:
sleep(interval)
continue
# 3. 调用Spout的nextTuple()发射新数据
spout.nextTuple()
# 4. 记录已发送的Tuple(可靠性模式)
if reliability_enabled:
track_pending(tuple_id)
# 5. 短暂休眠避免空转(根据配置调整)
sleep(next_tuple_delay)
Storm Executor 内部使用 Disruptor 队列(高性能无锁队列)处理消息,其休眠行为由队列的 Wait Strategy(等待策略) 决定。不同策略直接影响线程的 CPU 占用率和延迟:
等待策略 | 行为 | 适用场景 |
---|---|---|
BlockingWaitStrategy | 线程在无消息时进入阻塞(通过锁和条件变量),释放 CPU。 | 资源敏感场景(默认策略) |
BusySpinWaitStrategy | 线程忙等待(空循环),持续占用 CPU,但响应延迟最低。 | 极端低延迟需求(如高频交易) |
YieldingWaitStrategy | 线程在循环中主动调用 Thread.yield() ,尝试让出 CPU。 |
平衡吞吐和延迟 |
TimeoutBlockingWaitStrategy | 带超时的阻塞等待(如阻塞 10ms),超时后唤醒检查。 | 折中方案,避免长时间阻塞 |
配置参数:通过 topology.disruptor.wait.strategy 指定(默认 BlockingWaitStrategy )。 |
||
Executor 进入休眠的条件与消息队列状态和流控机制直接相关: |
ringBuffer.publish()
,触发等待的消费者线程(Executor)唤醒。TimeoutBlockingWaitStrategy
)
topology.max.spout.pending
达到上限,或下游 Bolt 的内部队列积压。ackers
),当未确认的 Tuple(pending
)数量低于 max.spout.pending
时,Spout 恢复调用 nextTuple()
。nextTuple()
没有新数据可发射(如 Kafka 分区无新消息),Executor 根据 topology.sleep.spout.wait.strategy.time.ms
进入休眠。poll()
)获取到数据,触发 nextTuple()
发射。topology.sleep.spout.wait.strategy.time.ms
(默认 1ms)周期性唤醒,调用 nextTuple()
检查数据源。Task 是 Spout 或 Bolt 的具体实例,直接处理元组(Tuple),是实际执行业务逻辑的最小单元。
Task 是逻辑上的处理单元,一个 Executor 线程可以顺序执行多个 Task(通过循环调度)。
Task 数量可以大于 Executor 数量,此时每个 Executor 会负责多个 Task,但 Task 本身是串行执行的(无并行)。
Task 的分配:当 Topology 被提交到 Storm 集群时,Storm 的 Nimbus 组件会根据配置的并行度参数(parallelism_hint 和 setNumTasks())生成 Task 列表,并为每个 Executor 分配一组 Task。
builder.setBolt("my-bolt", new MyBolt(), 4).setNumTasks(8); // 4 个 Executor、8 个 Task
每个 Executor 会被分配 ceil(numTasks / numExecutors) 个 Task。
拓扑(Topology)是 Storm 中运行的一个实时应用程序,因为各个组件间的消息流动而形成逻辑上的拓扑结构。
把实时应用程序的运行逻辑打成jar 包后提交到 Storm 的拓扑(Topology)。Storm 的拓扑类似于 MapReduce 的作业(Job)。其主要的区别是,MapReduce 的作业最终会完成,而一个拓扑永远都在运行直到它被杀死。
Topology 通常包含多个数据流处理节点,这些节点通过有向无环图(DAG)的形式连接在一起,形成一条完整的数据流管道。
Topology 的两大核心组成部分是 Spout 和 Bolt。
Spout 是 Storm 中的数据源组件,负责从外部系统(如消息队列、数据库、文件系统等)读取数据并生成数据流。Spout 会将数据流中的每个数据单元封装为一个 Tuple,并将这些 Tuple 发射到下一层的 Bolt 中进行处理。
Spout 还可以设计为可靠模式或不可靠模式:
Bolt 是实际进行数据处理的节点。数据经过 Spout 发射后,被传递到 Bolt 进行处理,Bolt 可以进行数据过滤、聚合、转换、分组等操作。Bolt 也可以发射新的 Tuple 到下游的 Bolt,构建复杂的数据处理逻辑。
Bolt 通常会以流水线的方式排列,一个 Bolt 可以接收来自多个 Spout 或其他 Bolt 的数据流,并进行多级处理。
Apache Storm 是一个分布式实时计算框架,支持大规模流数据处理。它的工作原理基于数据流的并行处理机制,通过 Spout 生成的数据流(Tuple)在多个节点(Bolt)之间传输和处理。在整个处理过程中,Storm 提供了可靠的确认机制(Acker)来确保每一个 Tuple 都能够被完整处理或在处理失败时进行补偿。
在Apache Storm中,流分组(Stream Grouping) 决定了数据流中的元组(Tuple)如何在不同的任务(Bolt)之间分发。不同的分组策略适用于不同的场景,直接影响拓扑(Topology)的性能和数据处理逻辑。
Storm 内置了8种流分组方式,通过实现 CustomStreamGrouping 接口可以实现自定义的流分组。
builder.setBolt("count-bolt", new CountBolt(), 3).fieldsGrouping("split-bolt", new Fields("word"));
public class CustomGrouping implements CustomStreamGrouping {
@Override
public void prepare(WorkerTopologyContext context, GlobalStreamId stream, List<Integer> targetTasks) {
// 初始化逻辑
}
@Override
public List<Integer> chooseTasks(int taskId, List<Object> values) {
// 返回目标Task ID列表
return Arrays.asList(targetTaskId);
}
}
如何选择流分组?
在 Apache Storm 中,数据流的生命周期从 Spout 开始,经过多级 Bolt 的处理,最后形成处理结果。其生命周期可总结为以下几个步骤:
Storm 的核心特点之一是它的并行处理能力,依赖于 Executor 和 Worker 的分布式执行机制。并行处理机制的核心概念包括以下几个方面:
在 Storm 中,Tuple 是最小的传输和处理单元。每个 Tuple 包含了一个或多个字段,表示数据流中的单个数据记录。在 Spout 和 Bolt 之间,Tuple 被持续传递和处理。
Tuple 的传输和处理可以分为以下几个步骤:
Apache Storm 的 Acker 机制 是其实现消息可靠性处理(Reliability)的核心设计,用于确保每条消息(Tuple)在拓扑(Topology)中被完全处理(即所有相关 Bolt 均成功处理)。该机制通过跟踪消息的处理链路,解决了分布式环境下可能因节点故障、网络波动等原因导致的消息丢失问题。
Storm 的可靠性基于"至少一次"(At-least-once)语义,通过以下步骤确保消息不丢失:
// Spout 发射 Tuple 时指定 Message ID
collector.emit(new Values("data"), messageId);
// Bolt 处理 Tuple 时锚定并发射新 Tuple
collector.emit(inputTuple, new Values("processed_data"));
collector.ack(inputTuple); // 显式确认处理完成
优点:
topology.enable.message.timeouts=false
禁用 Acker。框架 | 容错机制 | 语义 | 适用场景 |
---|---|---|---|
Storm | Acker + 异或校验 | At-least-once | 低延迟、简单处理逻辑 |
Flink | Checkpoint + 状态快照 | Exactly-once | 高吞吐、有状态复杂计算 |
Kafka | 偏移量提交 + 消费者重试 | At-least-once | 消息队列场景,依赖外部存储 |
Storm的BlobStore是一个用于存储和分发大型二进制文件(如JAR包、配置文件等)的分布式存储组件,确保集群中的各个节点能够高效访问这些资源。
(1)作用
storm.yaml
)storm.blobstore.dir: "/var/storm/blobs" # Blob存储目录
storm.blobstore.hdfs.uri: "hdfs://namenode:8020" # HDFS地址
storm.blobstore.hdfs.user: "storm" # HDFS操作用户
storm.blobstore.hdfs.buffer.size: 65536 # 读写缓冲区大小(字节)
storm.blobstore.s3.bucket: "my-storm-blobs" # S3桶名称
storm.blobstore.s3.access.key: "AKIAXXX" # 访问密钥
storm.blobstore.s3.secret.key: "secretKeyXXX" # 私有密钥
storm.blobstore.replication.factor: 3 # 默认值通常为3
storm.blobstore.timeout.seconds: 60 # 上传/下载超时时间(默认30秒)
storm.blobstore.cleanup.interval.secs: 86400 # 清理间隔(默认1天)
storm.blobstore.max.unused.blob.age.secs: 604800 # 保留未使用Blob的最长时间(默认7天)
blobstore.users:
- name: "user1"
type: "user"
permissions: ["READ", "WRITE"] # 允许读写
- name: "group1"
type: "group"
permissions: ["READ"] # 仅允许读取
(5)操作示例
storm blobstore upload --file myfile.jar --key myblob
storm blobstore download --key myblob --file local_copy.jar
storm blobstore delete --key myblob
(6)注意事项
Apache Storm 作为一个实时流处理系统,设计上能够处理大规模数据流并在故障发生时保持高可用性。它通过容错机制和可靠性保证策略来确保每条数据在分布式集群中的正确处理。Storm 主要通过 At-least-once 和 Exactly-once 语义来保证数据的可靠处理,并具备强大的故障恢复机制。
Storm 提供了两种主要的处理语义来确保数据的可靠性:
为了保证系统在面对节点故障或任务失败时能够继续处理数据流,Storm 提供了多种容错策略:
Storm 提供了多种配置选项来控制任务的重试行为,以及如何监控任务的执行情况。以下是相关配置与监控方法:
# 指定 Tuple 需要在多少秒内被完全处理。如果在这个时间内 Tuple 没有被确认,Spout 会认为该 Tuple 处理失败并进行重发。
topology.message.timeout.secs: 30
# 设置每个 Spout 允许同时处理的最大 Tuple 数量。这可以防止系统在任务失败时因为过多的重试而造成过载。
topology.max.spout.pending: 1000
# 设置 Acker 的执行器数量。通常,Acker 数量根据数据流的规模和处理的复杂性来决定,更多的 Acker 可以减少重试失败带来的数据丢失风险。
topology.acker.executors: 2
http://:8080
),可以查看拓扑的运行状态。通过 UI,你可以监控每个 Spout 和 Bolt 的任务成功率、失败率、处理延迟等信息。# 配置任务重试的时间间隔,确保系统不会频繁重试导致过载。可以根据拓扑的负载和系统的稳定性调整重试间隔。
topology.retry.interval.secs: 5
通过配置这些选项并监控任务执行情况,开发者可以确保 Storm 在遇到故障时能够有效恢复,并且保证数据的可靠处理。在高可用性和数据准确性要求较高的环境下,合理配置容错机制和重试策略至关重要。
Zookeeper 是 Storm 集群的核心协调组件。它在分布式环境下负责节点之间的状态同步、任务分配以及故障恢复。
/storm/assignments/{topology-id}
Assignment
对象(序列化为二进制或JSON)。
/storm/tasks/{topology-id}
/storm/tasks/{topology-id}/bolts
:存储所有Bolt组件的Task ID列表。/storm/tasks/{topology-id}/spouts
:存储所有Spout组件的Task ID列表。/storm/workerbeats/{topology-id}/{worker-id}
/storm/errors/{topology-id}/{component-id}
/storm/logconfigs/{topology-id}
Storm 利用 ZooKeeper 作为分布式协调服务,所有组件(Nimbus、Supervisor、Worker)的状态信息均通过 ZooKeeper 共享。这种设计解耦了组件之间的直接通信,提高了系统的可靠性。
/storm/supervisors/
)。supervisor.heartbeat.frequency.secs
,通常 10 秒)向 ZooKeeper 写入自身状态。
time-secs
)/storm/supervisors
目录,通过检查最新写入时间戳判断 Supervisor 是否存活。supervisor.heartbeat.timeout.secs
(默认 30 秒)未更新状态,Nimbus 认为其已离线,标记其所有 Worker 为失效。Nimbus 重新生成任务分配计划,将失效 Worker 的 Task 迁移到其他 Supervisor。/storm/workerbeats//:
)。worker.heartbeat.frequency.secs
,通常 10 秒)向 ZooKeeper 写入心跳。
/storm/workerbeats/
,检测 Worker 是否存活。worker.heartbeat.timeout.secs
(默认 30 秒)未更新心跳,Nimbus 会标记该 Worker 为失效,并触发任务重新分配。/storm/nimbus
(1)Nimbus 提交拓扑的完整流程
为了确保 Apache Storm 在高负载的流处理场景下能够高效运行,性能优化是非常关键的一环。性能优化涵盖了多个方面,包括提高吞吐量、优化内存使用、管理资源、以及监控系统运行状态。通过合理配置这些要素,可以显著提升拓扑的处理能力和系统的稳定性。
并行度 是决定 Storm 处理能力的关键因素。通过调优并行度可以有效提升吞吐量,但需要找到合适的平衡点,避免过高或过低的并行度影响性能。
builder.setSpout("spout-name", new MySpout(), 4); // 设置 Spout 并行度为 4
builder.setBolt("bolt-name", new MyBolt(), 8) // 设置 Bolt 并行度为 8
.shuffleGrouping("spout-name");
任务分配:在配置拓扑时,Spout 的并行度通常小于或等于 Bolt 的并行度,以确保 Bolt 能够及时处理从 Spout 发射的数据。topology.workers: 4 # 设置为 4 个 Worker
内存和资源管理是确保拓扑高效运行的基础。合理的资源分配可以避免内存溢出、线程阻塞等问题。
topology.worker.cpu: 2.0 # 每个 Worker 分配 2 个 CPU
topology.worker.memory.mb: 4096 # 每个 Worker 分配 4GB 内存
export STORM_WORKER_HEAPSIZE=4096 # 设置 Worker JVM 堆内存大小为 4GB
Tuple 是 Storm 中的数据传输单元,因此优化 Tuple 的传输和序列化能够有效减少网络开销,提高数据处理速度。
collector.emit(new Values(compactData)); // 传输经过压缩处理的数据
public class CustomSerializer implements Serializer {
@Override
public void serialize(Object obj, OutputStream out) throws IOException {
// 自定义序列化逻辑
}
@Override
public Object deserialize(InputStream in) throws IOException {
// 自定义反序列化逻辑
return object;
}
}
有效的监控和日志管理可以帮助开发者了解系统运行状态,并及时发现和解决性能问题。
metrics.reporters:
- class: "org.apache.storm.metrics2.reporters.PrometheusReporter"
topology.debug: false # 关闭调试日志
topology.workers: 3
在这一部分,我们将通过实际案例,展示 Apache Storm 在实时数据处理中的应用,包括实时日志分析等具体场景,并探讨在生产环境中的实践经验。
案例:实时流数据处理——用户行为分析
场景描述:某电商平台需要监控用户的实时行为(如点击、浏览、下单等),分析用户的操作路径,实时推荐产品,并在异常行为(如刷单、欺诈)发生时进行预警。这个系统需要低延迟、高吞吐量的实时处理能力。
解决方案:
TopologyBuilder builder = new TopologyBuilder();
// Spout 从 Kafka 消费用户事件数据
builder.setSpout("kafka-spout", new KafkaSpout(), 2);
// Bolt 1: 数据清洗
builder.setBolt("cleaning-bolt", new CleaningBolt(), 4)
.shuffleGrouping("kafka-spout");
// Bolt 2: 实时用户行为分析
builder.setBolt("analysis-bolt", new UserAnalysisBolt(), 4)
.shuffleGrouping("cleaning-bolt");
// Bolt 3: 实时推荐
builder.setBolt("recommendation-bolt", new RecommendationBolt(), 4)
.shuffleGrouping("analysis-bolt");
// Bolt 4: 异常行为检测
builder.setBolt("anomaly-detection-bolt", new AnomalyDetectionBolt(), 2)
.shuffleGrouping("analysis-bolt");
Config conf = new Config();
conf.setNumWorkers(4);
// 提交拓扑到集群
StormSubmitter.submitTopology("user-behavior-analysis", conf, builder.createTopology());
效果:
案例:金融交易监控
场景描述:金融行业的交易系统对实时性有极高的要求,需要在短时间内处理海量的交易数据,监控异常交易和价格波动。该系统不仅需要极低的延迟,还要保证高吞吐量,避免数据丢失。
解决方案:
案例:Web服务器的实时日志分析
场景描述:大型网站需要分析海量的实时日志,以便及时监控服务器的运行状态、用户访问情况,并在发现异常(如大量 404 错误、访问超时等)时自动告警。日志分析系统需要低延迟和高吞吐量,以处理数百台服务器产生的日志。
解决方案:
TopologyBuilder builder = new TopologyBuilder();
// Spout 从 Kafka 中读取日志
builder.setSpout("log-spout", new KafkaSpout(), 2);
// Bolt 1: 解析日志条目
builder.setBolt("log-parser-bolt", new LogParserBolt(), 4)
.shuffleGrouping("log-spout");
// Bolt 2: 日志分析
builder.setBolt("log-analysis-bolt", new LogAnalysisBolt(), 4)
.fieldsGrouping("log-parser-bolt", new Fields("log-type"));
// Bolt 3: 异常检测
builder.setBolt("anomaly-detection-bolt", new AnomalyDetectionBolt(), 2)
.shuffleGrouping("log-analysis-bolt");
// Bolt 4: 数据存储
builder.setBolt("storage-bolt", new StorageBolt(), 2)
.shuffleGrouping("log-analysis-bolt");
Config conf = new Config();
conf.setNumWorkers(3);
// 提交拓扑到集群
StormSubmitter.submitTopology("real-time-log-analysis", conf, builder.createTopology());
效果:
随着大数据处理技术的发展,实时流处理框架逐渐成为企业处理海量数据的重要工具。Apache Storm、Apache Flink 和 Apache Spark Streaming 是目前常见的流处理框架,每个框架都在不同的场景下有着独特的优势和特性。下面将通过对比 Apache Storm 与 Apache Flink 以及 Apache Spark Streaming,帮助理解它们的优缺点和适用场景。
Apache Storm 和 Apache Flink 都是分布式流处理框架,但它们在设计理念和处理方式上存在显著差异。
特性 | Apache Storm | Apache Flink |
---|---|---|
处理模型 | 基于 Tuple 的数据流处理,提供 At-least-once 和 Exactly-once 语义 | 基于事件驱动的流式和批处理,支持事件时间处理,天然提供 Exactly-once 语义 |
处理类型 | 专注于流式数据处理,批处理支持较弱 | 同时支持流处理和批处理,提供统一的 API 接口 |
时间语义 | 基于处理时间(Processing Time) | 支持事件时间(Event Time)、处理时间等多种时间语义 |
窗口机制 | Storm 支持有限的窗口功能 | Flink 提供强大的窗口机制(滚动窗口、滑动窗口、会话窗口等) |
状态管理 | 依赖外部存储进行状态管理,支持 Exactly-once 但较为复杂 | 内置状态管理,支持大型状态的低延迟 Exactly-once 语义 |
吞吐量与延迟 | 通常 Storm 的延迟较低,适合实时性要求高的应用 | Flink 在大规模批量处理时吞吐量和性能更优,但延迟略高 |
容错机制 | 通过 Acker 机制追踪 Tuple,进行重试 | 通过 Checkpoint 机制实现容错,数据不会丢失 |
生态系统 | 依赖其他组件如 Kafka、Zookeeper 实现集成 | 原生集成 Kafka、HDFS 等系统,提供完整的生态体系 |
适用场景 | 适用于简单实时处理、数据监控、低延迟的场景 | 适用于复杂流计算、大规模批处理和状态管理丰富的应用 |
Apache Spark Streaming 是 Spark 生态系统中的流处理组件,它基于微批处理(Micro-batching)模型,而 Apache Storm 则是基于事件驱动的实时流处理。
特性 | Apache Storm | Apache Spark Streaming |
---|---|---|
处理模型 | 基于事件驱动的实时流处理(Tuple-by-Tuple) | 基于微批处理模型(将数据流分割为小批次进行处理) |
延迟 | 低延迟,处理每个 Tuple 实时发射 | 相对较高,由于微批模型,延迟与批次大小有关 |
吞吐量 | 吞吐量较低,专注低延迟处理 | 吞吐量较高,尤其在批处理场景下更高效 |
容错机制 | 基于 Acker 机制,支持 At-least-once 和 Exactly-once | 通过 RDD 的 DAG 容错模型实现自动重试和容错 |
状态管理 | 依赖外部存储进行状态管理 | 基于 Spark 的原生内存管理,支持持久化和恢复 |
时间语义 | 支持处理时间语义 | 支持事件时间和处理时间 |
编程复杂度 | 较为复杂,要求开发者自行管理重试和容错逻辑 | 基于 Spark API 的批处理模型,编程简单 |
集成性 | 原生集成 Kafka 和 Zookeeper 集成 | Spark 生态系统,支持与 Spark SQL、MLlib 结合 |
适用场景 | 实时监控、低延迟数据处理 | 大规模批量数据处理、需要结合其他 Spark 模块的场景 |
选择合适的流处理框架取决于业务场景、技术要求以及现有的技术栈。以下几点建议可以帮助选择合适的流处理框架:
实时性 vs 批处理需求:
Apache Storm 作为一种强大的实时流处理框架,已经被广泛应用于多个领域,如金融交易监控、实时日志分析、用户行为分析等场景。Storm 的事件驱动模型、低延迟处理能力、分布式容错机制,使其在处理实时数据流时具有明显的优势。尽管 Storm 在低延迟和高可靠性方面表现出色,随着流处理领域的发展和用户需求的演变,Storm 也面临着进一步发展的机遇与挑战。