Seal^_^头像
Apache Storm 动态扩缩容:Rebalance、在线调整并行度与拓扑滚动升级实战封面图

Apache Storm 动态扩缩容:Rebalance、在线调整并行度与拓扑滚动升级实战

Apache Storm 动态扩缩容:Rebalance、在线调整并行度与拓扑滚动升级实战


1. Storm 动态扩缩容概述


Apache Storm 是一个开源的分布式实时计算系统,广泛应用于流数据处理场景。在生产环境中,随着业务负载的变化,需要对 Storm 集群进行动态扩缩容以优化资源利用率并保持系统性能。Storm 提供了几种机制来实现动态扩缩容,主要包括 Rebalance 命令、在线调整并行度以及拓扑滚动升级。


Storm 集群架构与扩缩容组件展示 Storm 集群主要组件及其在动态扩缩容中的作用Nimbus 主节点Supervisor 工作节点Worker 进程Executor 线程ZooKeeper 集群Rebalance 命令并行度调整 API滚动升级工具


上图展示了 Storm 集群的典型架构以及实现动态扩缩容的核心组件。Nimbus 主节点负责资源分配和任务调度,Supervisor 工作节点托管实际执行计算的 Worker 进程,而每个 Worker 包含多个 Executor 线程来处理具体的计算任务。ZooKeeper 作为协调服务存储集群状态和拓扑信息,而底部的三种扩缩容机制则提供了不同的动态调整能力。


Storm 动态扩缩容的主要优势在于:


  1. 无需停止拓扑即可进行资源调整
  2. 支持平滑过渡,避免服务中断
  3. 可根据实际负载实时优化资源配置
  4. 提供细粒度的并行度控制能力


2. Rebalance 机制详解


Rebalance 是 Storm 提供的一种核心动态扩缩容机制,允许在不停止拓扑的情况下重新分配计算资源。通过 Rebalance,我们可以动态调整拓扑中各组件的并行度,以应对负载变化。


Rebalance 工作原理


Rebalance 命令通过以下步骤实现资源重新分配:


  1. 向 Nimbus 提交 Rebalance 请求
  2. Nimbus 通知所有相关 Supervisor 准备迁移
  3. Supervisor 重新分配 Executor 进程
  4. 重新分配数据流和分组策略


Rebalance 机制流程决策树展示 Rebalance 执行过程中的关键决策点和操作流程提交 Rebalance 请求首次执行二次执行重置 Executor 数量?是否有未处理消息?是否是否重置消费位点保持消费位点等待处理完成强制重新分配Nimbus 重新分配资源 → Supervisor 重新启动 Executor → 拓扑恢复运行


上图展示了 Rebalance 机制的执行决策树。当提交 Rebalance 请求后,系统会判断是首次执行还是二次执行。如果是首次执行,系统会询问是否需要重置 Executor 数量;如果是二次执行,则会检查是否有未处理消息。根据不同的决策路径,系统会采取相应操作,最终实现资源的重新分配。


Rebalance 命令使用示例


# 基本语法
storm rebalance <topology-name> [option1] [option2] ...

# 示例:将并行度调整为 10,等待 30 秒
storm rebalance my-topology -n 10 -w 30

# 示例:只重新分配 bolt 的并行度
storm rebalance my-topology -e bolt:5

# 示例:强制重新分配(丢弃未处理消息)
storm rebalance my-topology --force


Rebalance 关键参数解释


参数描述默认值推荐场景
-n, --num-executors设置 Executor 总数保持当前值统一增加/减少资源
-e, --executors按组件设置 Executor 数量保持当前值精细化调整特定组件
-w, --wait等待时间(秒)0需要优雅停止的情况
--force强制重新分配false需要立即生效的场景


3. 在线调整并行度实践


除了 Rebalance 机制外,Storm 还提供了更细粒度的并行度调整能力,允许单独调整拓扑中各个组件(Spout/Bolt)的并行度。


并行度调整的工作原理


并行度调整通过修改拓扑配置中的 parallelism.hint 参数实现。当调整并行度后,Nimbus 会:


  1. 计算需要的 Executor 数量
  2. 分配新的资源
  3. 启动新 Executor 并迁移状态


并行度调整流程展示在线调整并行度的详细步骤和状态转换原拓扑配置: spout=2, bolt1=3, bolt2=2提交并行度调整请求Nimbus 解析新配置计算资源需求新拓扑配置: spout=4, bolt1=5, bolt2=3 (总 Executor 从 7 增至 12)分配新资源启动新 Executor迁移数据流拓扑继续运行,新并行度生效


上图展示了并行度调整的完整流程。首先,系统会解析新的并行度配置,然后计算所需的资源需求,分配新资源并启动新的 Executor 进程,最后迁移数据流并使新并行度生效。


并行度调整实现方法


方法一:通过 Storm UI 调整


  1. 登录 Storm Web UI (http://<nimbus-host>:8080)
  2. 选择目标拓扑
  3. 点击 "Rebalance" 按钮
  4. 在弹出的对话框中调整并行度参数
  5. 提交调整请求


方法二:通过代码动态调整


// 创建拓扑配置
TopologyBuilder builder = new TopologyBuilder();
// 设置初始并行度
builder.setSpout("my-spout", new MySpout(), 2);
builder.setBolt("my-bolt1", new MyBolt1(), 3)
       .shuffleGrouping("my-spout");
builder.setBolt("my-bolt2", new MyBolt2(), 2)
       .shuffleGrouping("my-bolt1");

// 创建配置
Config conf = new Config();
conf.setNumWorkers(4);
conf.setMaxTaskParallelism(12);

// 提交拓扑
StormSubmitter.submitTopology("my-topology", conf, builder.createTopology());

// 后续动态调整并行度的代码
// 获取拓扑配置
TopologyConfig topologyConfig = new TopologyConfig();
topologyConfig.setComponentParallelism("my-spout", 4); // 调整 spout 并行度为 4
topologyConfig.setComponentParallelism("my-bolt1", 5); // 调整 bolt1 并行度为 5
topologyConfig.setComponentParallelism("my-bolt2", 3); // 调整 bolt2 并行度为 3

// 提交调整请求
StormSubmitter.rebalance("my-topology", topologyConfig, 30); // 等待 30 秒完成调整


方法三:通过 Storm CLI 命令调整


# 设置拓扑配置文件
cat > topology-config.yaml <<EOF
config:
  spout:
    parallelism: 4
  bolt1:
    parallelism: 5
  bolt2:
    parallelism: 3
  workers: 6
EOF

# 应用配置调整
storm rebalance my-topology -f topology-config.yaml


并行度调整最佳实践


  1. 逐步调整:避免一次性大幅度调整并行度
  2. 监控资源:调整前后密切关注 CPU、内存使用情况
  3. 数据倾斜:特别关注数据倾斜问题,避免部分负载过高
  4. 状态管理:有状态的组件调整并行度需要谨慎处理状态迁移
  5. 优雅过渡:设置适当的等待时间,确保数据处理完整


4. 拓扑滚动升级策略


滚动升级是在不中断服务的情况下更新拓扑代码或配置的重要策略。Storm 提供了多种机制来实现拓扑的滚动升级。


滚动升级的工作流程


  1. 准备新版本的 JAR 包
  2. 部署新代码到集群
  3. 逐步升级 Supervisor 节点
  4. 验证升级结果


拓扑滚动升级时间线展示拓扑滚动升级的步骤、时间点和关键操作时间05min10min15min20min准备新版本JAR 包编译部署到共享存储更新代码路径修改 storm.yaml重启 Supervisor触发升级kill -9 旧进程启动新进程验证升级节点检查日志确认指标正常升级下一个节点重复上一阶段逐节点进行全部升级完成监控整体性能回滚准备回滚准备保留旧版本备份记录配置变更问题回滚恢复旧版本恢复原有配置稳定性验证持续监控压力测试


上图展示了拓扑滚动升级的时间线,从准备新版本到最终完成升级的全过程。整个滚动升级过程可以分为准备阶段、执行阶段和验证阶段,每个阶段都有明确的操作步骤和时间点。


滚动升级实现方法


方法一:使用 Storm 原生滚动升级


# 1. 准备新版本 JAR
cp my-topology-new.jar /shared/storage/

# 2. 更新 storm.yaml 中的 JAR 路径
vim /opt/storm/conf/storm.yaml
# 添加或修改以下内容:
# topology.jar: "/shared/storage/my-topology-new.jar"

# 3. 重启 Supervisor
storm supervisor

# 4. 逐步升级拓扑
storm rebalance my-topology --wait 60  # 等待 60 秒让每个节点完成升级

# 5. 验证升级结果
storm list my-topology


方法二:使用自定义滚动升级脚本


#!/bin/bash

TOPOLOGY_NAME="my-topology"
NEW_JAR_PATH="/shared/storage/my-topology-new.jar"
NODES=("node1" "node2" "node3" "node4")
WAIT_TIME=60

# 1. 部署新 JAR
echo "Deploying new JAR to shared storage..."
cp $NEW_JAR_PATH /shared/storage/

# 2. 备份当前配置
echo "Backing up current configuration..."
cp /opt/storm/conf/storm.yaml /opt/storm/conf/storm.yaml.backup

# 3. 修改配置
echo "Updating storm.yaml..."
sed -i "s|topology.jar:.*|topology.jar: \"$NEW_JAR_PATH\"|" /opt/storm/conf/storm.yaml

# 4. 逐节点重启 Supervisor
for node in "${NODES[@]}"; do
    echo "Upgrading node: $node"
    ssh $node "sudo systemctl restart supervisor"
    sleep $WAIT_TIME
    echo "Verifying node: $node"
    ssh $node "jps | grep -q StormSubmitter"
done

# 5. 验证升级
echo "Verifying upgrade..."
storm list $TOPOLOGY_NAME


方法三:通过 Storm UI 进行滚动升级


  1. 登录 Storm Web UI
  2. 选择目标拓扑
  3. 点击 "Reconfig" 按钮
  4. 上传新的 JAR 包
  5. 调整配置参数
  6. 提交升级请求


滚动升级注意事项


  1. 版本兼容性:确保新旧版本间的数据格式兼容
  2. 状态迁移:有状态组件升级需要特别处理状态迁移
  3. 回滚计划:提前准备好回滚方案
  4. 监控告警:升级过程中加强监控,及时发现异常
  5. 业务影响:评估升级对业务的影响,必要时选择低峰期执行


实战示例与注意事项


下面是一个完整的动态扩缩容实践示例,包括 Rebalance、并行度调整和滚动升级的组合使用。


完整示例代码


import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.StormSubmitter;
import org.apache.storm.generated.StormTopology;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import org.apache.storm.utils.Utils;

public class DynamicScalingTopology {

    public static class SampleSpout extends BaseRichSpout {
        private SpoutOutputCollector collector;
        private int count = 0;

        @Override
        public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
            this.collector = collector;
        }

        @Override
        public void nextTuple() {
            Utils.sleep(100);
            collector.emit(new Values("message-" + count++));
        }

        @Override
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("message"));
        }
    }

    public static class SampleBolt extends BaseRichBolt {
        private OutputCollector collector;

        @Override
        public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
        }

        @Override
        public void execute(Tuple input) {
            String message = input.getString(0);
            System.out.println("Processing: " + message);
            // 模拟处理延迟
            Utils.sleep(50);
            collector.ack(input);
        }

        @Override
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            // 无输出
        }
    }

    public static StormTopology buildTopology(int spoutParallelism, int boltParallelism) {
        TopologyBuilder builder = new TopologyBuilder();
        
        // 设置初始并行度
        builder.setSpout("sample-spout", new SampleSpout(), spoutParallelism);
        builder.setBolt("sample-bolt", new SampleBolt(), boltParallelism)
               .shuffleGrouping("sample-spout");
                
        return builder.createTopology();
    }

    public static void main(String[] args) throws Exception {
        Config conf = new Config();
        conf.setNumWorkers(2);
        
        if (args != null && args.length > 0) {
            // 生产环境运行
            int spoutParallelism = Integer.parseInt(args[0]);
            int boltParallelism = Integer.parseInt(args[1]);
            
            StormTopology topology = buildTopology(spoutParallelism, boltParallelism);
            StormSubmitter.submitTopology("dynamic-scaling-topology", conf, topology);
        } else {
            // 本地测试
            int spoutParallelism = 2;
            int boltParallelism = 3;
            
            StormTopology topology = buildTopology(spoutParallelism, boltParallelism);
            LocalCluster cluster = new LocalCluster();
            cluster.submitTopology("dynamic-scaling-topology", conf, topology);
            
            // 模拟扩缩容
            Thread.sleep(10000);
            System.out.println("Increasing parallelism...");
            cluster.rebalance("dynamic-scaling-topology", 30000, 
                             new TopologyConfig().setComponentParallelism("sample-spout", 4)
                                               .setComponentParallelism("sample-bolt", 5));
            
            Thread.sleep(30000);
            System.out.println("Decreasing parallelism...");
            cluster.rebalance("dynamic-scaling-topology", 30000,
                             new TopologyConfig().setComponentParallelism("sample-spout", 2)
                                               .setComponentParallelism("sample-bolt", 3));
            
            Thread.sleep(30000);
            cluster.shutdown();
        }
    }
}


最小运行示例


  1. 编译打包:


mvn clean package -DskipTests


  1. 启动拓扑:


storm jar target/dynamic-scaling.jar DynamicScalingTopology 2 3


  1. 动态扩容:


storm rebalance dynamic-scaling-topology -e sample-spout:4,sample-bolt:5 -w 30


  1. 动态缩容:


storm rebalance dynamic-scaling-topology -e sample-spout:2,sample-bolt:3 -w 30


注意事项


  1. 数据一致性:扩缩容过程中可能出现数据重复或丢失,需要设计适当的去重或容错机制
  2. 资源争用:调整并行度时注意系统资源争用问题,避免过度占用导致集群不稳定
  3. 监控告警:建立完善的监控体系,及时发现扩缩容过程中的异常情况
  4. 渐进式调整:避免一次性大幅调整并行度,采用渐进式调整策略
  5. 业务影响评估:评估扩缩容对业务的影响,选择合适的调整时机


通过合理运用 Rebalance、在线调整并行度和拓扑滚动升级等技术,可以有效地实现 Storm 集群的动态扩缩容,提高资源利用率和系统稳定性,满足不同业务场景下的需求变化。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/qq_41840843/article/details/166372155

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--