Storm

Storm

Storm是免费开源的分布式实时计算系统,该系统在2.0.0之前改架构核心实现使用Clojure编程实现,在本次版本以后Storm底层实现做了重大的调整使用Java8重构了Storm。Storm是一个实时的流处理引擎,能实现对记录的亚秒级的延迟处理。Storm在 realtime analytics、online machine learning、continuous computation、distributed RPC、 ETL等领域都有应用。每秒中一个计算节点可以处理100万个Tuple记录。除此之外Storm还可以和现有的数据(RDBMS/NoSQL)以及 消息队列集成(Kafka)。

架构

在这里插入图片描述
nimbus:计算任务的主节点,负责分发代码/分配任务/故障检测 Supervisor任务执行.

supervisor:接受来自Nimbus的任务分配,启动Worker进程执行计算任务.

zookeeper:负责Nimbus和Supervisor协调,Storm会使用zookeeper存储nimbus和supervisor进程状态信息,这就导致了Nimbus和Supervisor是无状态的可以实现任务快速故障恢复,即而让流计算达到难以置信的稳定。

Worker:是Supervisor专门为某一个Topology任务启动的一个Java 进程,Worker进程通过执行Executors(线程)完成任务的执行,每个任务会被封装成一个个Task。

集群构建(在后续大数据所有集群搭建那篇上)

注意:同步时钟

  • 启动进程
[root@CentOSX ~]# nohup storm nimbus >/dev/null 2>&1 &  -- 启动 主节点
[root@CentOSX ~]# nohup storm supervisor >/dev/null 2>&1 &  --启动 计算节点
[root@CentOSA ~]# nohup storm ui >/dev/null 2>&1 &  --启动web ui界面

Topology概念

Topology:Storm topology编织数据流计算的流程。Storm拓扑类似于MapReduce作业。一个关键的区别是MapReduce作业最终完成,而拓扑结构永远运行(当然,直到你杀死它)。

Streams:流是无限的Tuple(等价与Kafka Streaming的Record)序列,以分布式方式并行处理和创建。Streams是使用Schema定义的,该Schema命名流的Tuple中的字段。

Tuple:是Storm中一则记录,该记录存储是一个数组元素,Tuple元素都是只读的,不允许修改.

Tuple t=new Tuple(new Object[]{1,"zs",true})// readOnly

Spouts:负责产生Tuple,是Streams源头.通常是通过Spout读取外围系统的数据,并且将数据封装成Tuple,并且将封装Tuple发射emit到Topology中.IRichSpout|BaseRichSpout

Bolts:所有的Topology中的Tuple是通过Bolt处理,Bolt作用是用于过滤/聚合/函数处理/join/存储数据到DB中等.

IRichBolt|BaseRichBolt At Most Once机制,
IBasicBolt|BaseBasicBolt At Least Once ,
IStatefulBolt | BaseStatefulBolt 有状态计算。

快速入门

  1. 导入依赖pom.xml
<dependency>
    <groupId>org.apache.storm</groupId>
    <artifactId>storm-core</artifactId>
    <version>2.0.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.storm</groupId>
    <artifactId>storm-client</artifactId>
    <version>2.0.0</version>
    <scope>provided</scope>
</dependency>

2 . 编写 Spout

public class WordCountSpout extends BaseRichSpout {
    private String[] lines={"this is a demo","hello Storm","ni hao"};
     //该类负责将数据发送给下游
    private SpoutOutputCollector collector;
    
    public void open(Map<String, Object> conf, TopologyContext context, SpoutOutputCollector collector) {
        this.collector=collector;
    }
    //向下游发送Tuple ,改Tuple的Schemal在declareOutputFields声明
    public void nextTuple() {
        Utils.sleep(1000);//休息1s钟
        String line=lines[new Random().nextInt(lines.length)];
        collector.emit(new Values(line));
    }
    //对emit中的tuple做字段的描述
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("line"));
    }
}

编写 Bolt

LineSplitBolt

public class LineSplitBolt extends BaseRichBolt {
     //该类负责将数据发送给下游
    private OutputCollector collector;
    public void prepare(Map<String, Object> topoConf, TopologyContext context, OutputCollector collector) {
        this.collector=collector;
    }
	
    public void execute(Tuple input) {
        String line = input.getStringByField("line");
        String[] tokens = line.split("\\W+");
        for (String token : tokens) {
            collector.emit(new Values(token,1));
        }
    }

    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("word","count"));
    }
}

WordCountBolt

public class WordCountBolt extends BaseRichBolt {
    //存储状态
    private Map<String,Integer> keyValueState;
    //该类负责将数据发送给下游
    private OutputCollector collector;
    
    public void prepare(Map<String, Object> topoConf, TopologyContext context, OutputCollector collector) {
        this.collector=collector;
        keyValueState=new HashMap<String, Integer>();
    }

    public void execute(Tuple input) {
        String key = input.getStringByField("word");
        int count=0;
        if(keyValueState.containsKey(key)){
            count=keyValueState.get(key);
        }
        //更新状态
        int currentCount=count+1;
        keyValueState.put(key,currentCount);
        //将最后结果输出给下游
        collector.emit(new Values(key,currentCount));
    }

    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("key","result"));
    }
}

WordPrintBolt

public class WordPrintBolt extends BaseRichBolt {
    //这个时候就不用写collector了 因为数据不用再往下游发送了
    public void prepare(Map<String, Object> topoConf, TopologyContext context, OutputCollector collector) {
    }

    public void execute(Tuple input) {
        String word=input.getStringByField("key");
        Integer result=input.getIntegerByField("result");
        System.out.println(input+"\t"+word+" , "+result);
    }

    public void declareOutputFields(OutputFieldsDeclarer declarer) {

    }
}

编写Topology

import org.apache.storm.Config;
import org.apache.storm.StormSubmitter;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Fields;

public class WordCountTopology {
    public static void main(String[] args) throws Exception {
        //1.创建TopologyBuilder
        TopologyBuilder builder = new TopologyBuilder();

        //2.编织流处理逻辑- 重点(Spout、Bolt、连接方式)

        builder.setSpout("WordCountSpout",new WordCountSpout(),1);

        builder.setBolt("LineSplitBolt",new LineSplitBolt(),3)
                .shuffleGrouping("WordCountSpout");//设置 LineSplitBolt 接收上游数据通过 随机

        builder.setBolt("WordCountBolt",new WordCountBolt(),3)
                .fieldsGrouping("LineSplitBolt",new Fields("word"));

        builder.setBolt("WordPrintBolt",new WordPrintBolt(),4)
                .fieldsGrouping("WordCountBolt",new Fields("key"));

        //3.提交流计算
        Config conf= new Config();
        conf.setNumWorkers(3); //设置Topology运行所需的Worker资源,JVM个数
        conf.setNumAckers(0);  //关闭Storm应答,可靠性有关
        StormSubmitter.submitTopology("worldcount",conf,builder.createTopology());
    }
}

shuffleGrouping:表示下游的LineSplitBolt会随机的接收上游的Spout发出的数据流。
fieldsGrouping: 表示相同的Fields数据总会发送给同一个Task Bolt节点。

任务提交
使用mvn package打包应用,然后将打包的jar包上传到 集群中的任意一台机器

[root@CentOSA ~]# storm jar /root/storm-lowlevel-1.0-SNAPSHOT.jar com.zax.demo01.WordCountTopology

提交成功后,用户可以查看Storm UI界面查看程序的执行效果http://centosa:8080/

在这里插入图片描述
查看任务列表

[root@CentOSA ~]# storm list
...
Topology_name        Status     Num_tasks  Num_workers  Uptime_secs  Topology_Id          Owner
----------------------------------------------------------------------------------------
worldcount           ACTIVE     11         3            66           worldcount-2-1560760048 root

kill Topology

[root@CentOSX ~]# storm kill worldcount

任务并行度的理解

并行度和线程是一 一对应的
在这里插入图片描述
conf.setNumWorkers(3);
决定了当前的Topology计算所需的Work进程,每一个Worker只能属于某一个Topology。每一个Worker就代表一个计算资源称为Slot
在这里插入图片描述

一个Supervisor最多启动/管理4个Woker/Slot 进程。Woker不能跨Topology共享,每一个流计算任务在启动前就已经分配好了JVM计算进程。Storm是通过Worker/Slot实现计算资源的隔离。

Task和Executor关系

Task实际是Spout和Blot的实例。默认情况下一个线程只运行一个Task(一个线程中只有一个Spout或者Bolt)。例如一下代码

 builder.setBolt("LineSplitBolt",new LineSplitBolt(),3)
                .setNumTasks(5)
                .shuffleGrouping("WordCountSpout");//设置 LineSplitBolt 接收上游数据通过 随机

LineSplitBolt组件会占用3线程,系统会实例化5个LineSplitBolt`的实例。其中2、2、1分配方式分配三个线程。
在这里插入图片描述

Topology并行度:Worker(进程)、Executors(线程)和Task(实例)相关
在这里插入图片描述

是不是worker越多越好?

  1. 不是 如果一个worker能解决最好放在一个worker中解决
  2. 因为worker多了要走进程之间的通信,网络通信是比较慢的
  3. 但大数据中的数据量太大了 内存相对紧张
  4. 所以要根据实际的运行效率和延迟,经过测量后在线实时修改

storm rebalance(指定worker数目 某个组件的并行度等)

[root@CentOSA ~]# storm rebalance
usage: storm rebalance [-h] [-w WAIT_TIME_SECS] [-n NUM_WORKERS]
                       [-e EXECUTORS] [-r RESOURCES] [-t TOPOLOGY_CONF]
                       [--config CONFIG]
                       [-storm_config_opts STORM_CONFIG_OPTS]
                       topology-name
  • 修改Worker数目
[root@CentOSX ~]# storm rebalance -w 10 -n 6  wordcount02
  • 修改某个组件的并行度,一般不能超过Task个数(一个线程可以运行多个task实例 但是多个线程运行一个不是浪费了么)
[root@CentOSX ~]# storm rebalance -w 10 -n 3 -e LineSplitBolt=5  wordcount02

Tuple可靠性处理

Storm 消息Tuple可以通过一个叫做__ackerBolt去监测整个Tuple Tree是否能够被完整消费,如果消费超时或者失败该__ackerBolt会调用Spout组件(发送改Tuple的Spout组件)的fail方法,要求Spout重新发送Tuple.默认__ackerBolt并行度是和Worker数目一致,用户可以通过config.setNumAckers(0);关闭Storm的Acker机制。

如何使用可靠性发送

  • Spout端
  • Spout在发射 tuple 的时候必须提供msgID
  • 同时覆盖ack和fail方法
public class WordCountSpout extends BaseRichSpout {
    private String[] lines={"this is a demo","hello Storm","ni hao"};
    private SpoutOutputCollector collector;

    public void open(Map<String, Object> conf, TopologyContext context, SpoutOutputCollector collector) {
        this.collector=collector;
    }

    public void nextTuple() {
        Utils.sleep(5000);//休息1s钟
        int msgId = new Random().nextInt(lines.length);
        String line=lines[msgId];
        //发送 Tuple 指定 msgId
        collector.emit(new Values(line),msgId);
    }
    //对emit中的tuple做字段的描述
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("line"));
    }

    //发送成功回调 AckerBolt
    @Override
    public void ack(Object msgId) {
        System.out.println("发送成功:"+msgId);
    }
    //发送失败回调 AckerBolt
    @Override
    public void fail(Object msgId) {
        String line = lines[(Integer) msgId];
        System.out.println("发送失败:"+msgId+"\t"+line);
    }
}
  • Bolt端
  • 将当前的子Tuple 锚定到父Tuple上
  • 向上游应答当前父Tuple的状态,应答有两种方式 collector.ack(input);|collector.fail(input);
 public void execute(Tuple 父Tuple) {
        try {
            //do sth
            //锚定当前父Tuple
            collector.emit(父Tuple,子Tuple);
            //向上游应答当前父Tuple的状态
            collector.ack(父Tuple);
        } catch (Exception e) {
            collector.fail(父Tuple);
        }
    }

可靠性机制检测原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值