storm解析及wordcount简单实例

来源:互联网 发布:linux device is busy 编辑:程序博客网 时间:2024/06/06 16:44

storm:分布式实时流计算框架
storm分为单机模式集群模式
在集群模式下:
Storm由一个主节点和多个工作节点组成。主节点运行了一个名为“Nimbus”的守护进程,用于分配代码、布置任务及故障检测。每个工作节点都运行了一个名为“Supervisor”的守护进程,用于监听工作,开始并终止工作进程。Nimbus和Supervisor都能快速失败,而且是无状态的,这样一来它们就变得十分健壮,两者的协调工作是由Apache ZooKeeper来完成的。如图:

这里写图片描述
这也就意味着你可以用kill -9来杀死nimbus和supervisor进程, 然后再重启它们, 它们可以继续工作
更重要的是, nimbus和supervisor的fail或restart不会影响worker的工作, 不象Hadoop, Job tracker的fail会导致job失败

Storm是以Stream流为核心的,可定义为无限的tuple序列。
什么是tuple?
命名的value序列, 可以理解成Key/value序列, 每个value可以是任何类型, 动态类型不需要事先声明。
Tuple在传输中需要序列化和反序列化, storm集成了普通类型的序列化模块, 用户可以自定义特殊类型的序列化逻辑 。

Spouts, 流的源头 : 在一个topology中产生源数据流的组件。
Spout是Storm里面特有的名词, Stream的源头. 通常是从外部数据源读取tuples, 并emit到topology.
—-Spout可以同时emit多个tuple stream, 通过OutputFieldsDeclarer中的declareStream method来定义
—-Spout需要实现IRichSpout接口, 其中最重要的方法是nextTuple, 通常情况下,spout会从外部数据源中读取数据,然后转换为topology内部的源数据。Spout是一个主动的角色,其接口中有个nextTuple()函数,storm框架会不停地调用此函数,用户只要在其中生成源数据即可。
例:

    @Override   //接收外部数据转换为topology内部的源数据处理    public void nextTuple() {        String[] sentests = new String[]{"jahdkj djhdk dsjdl",                "jdkasjfl jfhk dhaku","dbhajdh frhy3ui hfkhf",                "hekdh ekfhak dkuhwk","ruyw flk ejr"};        int randIndex = (int) (Math.random() * sentests.length);        String sentence = sentests[randIndex];        collector.emit(new Values(sentence));  //写出去的数据    }

Bolts, 流的处理节点 :在一个topology中接受数据然后执行处理的组件。
对于Bolt, 用户可以定义任意的处理逻辑, 最重要的方法是execute, 输入为tuple, 输出为emit 0或多个tuples到OutputCollector.
Bolt支持多个输入流和emit多个输出流, 输出流和spout一样, 通过OutputFieldsDeclarer中的declareStream method来定义; 对于输入流, 如果想subscribe上层节点的多个输出streaming, 需要显式的通过stream_id去订阅, 如果不明确指定stream_id, 默认会订阅default stream.
也就是说Bolt可执行过滤、函数操作、合并、写数据库等操作。Bolt是一个被动的角色其接口中有一个execute(Tuple input)函数,在接受到消息后会调用此函数,用户可以在其中执行自己想要的操作。例:

public class WordCountBolt extends BaseRichBolt {    private static final long serialVersionUID = -3262356130322390839L;    private OutputCollector collector;    @SuppressWarnings("rawtypes")    @Override    public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {        this.collector = collector;    }    @Override    public void execute(Tuple input) {        String sentence = input.getStringByField("sentence");        String[] words = sentence.split("\\s+");  //以空格为单位        for (String word : words) {            collector.emit(new Values(word, 1));        }    }    @Override    public void declareOutputFields(OutputFieldsDeclarer declarer) {        declarer.declare(new Fields("word", "num"));    }

整个可以看作下图:

这里写图片描述

Topologies, 拓扑
可以理解为类似MapReduce job
区别在于, MR job执行完就结束, 而Topology会一直存在. 因为MR流动的是代码, 而Storm流动的数据.
所以Storm不可能替代MR, 因为对于海量数据, 数据的流动是不合理的
另一个区别, Topology对工作流有更好的支持, 而MR job往往只能完成一个map/reduce的过程, 而对于复杂的操作, 需要多个MR job才能完成.
而Topology的定义更加灵活, 可以简单的使用一个topology支持比较复杂的工作流场景。
这里具一个例子:

//创建一个topology拓扑结构的对象TopologyBuilder builder = new TopologyBuilder();//数据来源  设置topology中产生源数据流的组件对象builder.setSpout("spout", new WordCountSpout(), 3); //数据拆分  设置topology中接收数据然后执行处理组件对象builder.setBolt("split", new  WordCountBolt(),3).shuffleGrouping("spout"); //数据合并  设置topology中接收数据然后执行处理组件对象builder.setBolt("count", new WordCountCount(), 3).fieldsGrouping("split", new Fields("word"));

Topology有一个spout, 两个Bolt. setSpout和setBolt的参数都是一样, 分别为id(在Topology中的唯一标识); 处理逻辑(对于Spout就是数据产生function); 并发线程数(task数) 。
比较特别的是, setBolt方法会返回一个InputDeclarer对象, 并且该对象是用来定义Bolt输入的, 比如上面.shuffleGrouping(“spout”), 用spout(spout)的输出流作为输入。

这里我们可以看到这里有一个Streaming grouping:即消息的paitition方法
Stream grouping 定义了一个流在Bolt任务间该被 如何切分。这里有Storm提供的6个Stream Grouping类型。
(1)随机分组(Shuffle grouping):随机分发tuple到Bolt的任务,保证每个任务获得相等数量的tuple.
(2)字段分组(Fields grouping):根据指定字段分割数据流,并分组。例如:根据 user-id 字段,相同 user-id的元祖总是分发到同一个任务,不同user-id的元祖可能分发到不同的任务。
(3)全部分组(All grouping):tuple被复制到bolt的所有任务。这种类型需要谨慎使用。
(4)全局分组(global grouping):全部流都分配到bolt的同一个任务。明确地说,是分配给ID最小的那个task。
(5)无分组(None grouping):你不需要关心流是如何分组。目前,无分组等效于随机分组。但最终,Storm将把无分组的Bolts放到Bolts或Spouts订阅他们的同一线程去执行(如果可能)。
(6)直接分组(Direct grouping):这是一个特别的分组类型。元祖生产者决定tuple由哪个元祖处理者任务接收。

于是我们可以总结Storm工作流程
  第一步:客户端提交拓扑到Nimbus。
  第二步:Nimbus针对该拓扑建立本地的目录根据topology的配置计算task,分配task,在zookeeper上建立assignments节点存储task和supervisor机器节点中woker的对应关系。
  第三步:在zookeeper上创建taskbeats节点来监控task的心跳,启动topology。
  第四步:Supervisor去zookeeper上获取分配的tasks,启动多个woker进行,每个woker生成task,一个task一个线程;根据topology信息初始化建立task之间的连接;Task和Task之间是通过ZeroMQ管理的;后整个拓扑运行起来。
Storm的术语包括Stream、Spout、Bolt、Task、Worker、Stream Grouping和Topology。
Stream是被处理的数据。
Sprout是数据源。
Bolt处理数据。
Task是运行于Spout或Bolt中的线程。
Worker是运行这些线程的进程。
Stream Grouping规定了Bolt接收什么东西作为输入数据。 Topology是由Stream Grouping连接起来的Spout和Bolt节点网络。

WordCount实例:

package com.yc.hadoop.storm;import java.util.Arrays;import org.apache.storm.Config;import org.apache.storm.LocalCluster;import org.apache.storm.StormSubmitter;import org.apache.storm.generated.AlreadyAliveException;import org.apache.storm.generated.AuthorizationException;import org.apache.storm.generated.InvalidTopologyException;import org.apache.storm.topology.TopologyBuilder;import org.apache.storm.tuple.Fields;public class WordCountStorm {    public static void main(String[] args) throws AlreadyAliveException, InvalidTopologyException, AuthorizationException {        //创建一个topology拓扑结构的对象        TopologyBuilder builder = new TopologyBuilder();        //数据来源  设置topology中产生源数据流的组件对象        builder.setSpout("spout", new WordCountSpout(), 3);         //数据拆分  设置topology中接收数据然后执行处理组件对象        builder.setBolt("split", new WordCountBolt(), 3).shuffleGrouping("spout");         //数据合并  设置topology中接收数据然后执行处理组件对象        builder.setBolt("count", new WordCountCount(), 3).fieldsGrouping("split", new Fields("word"));        Config conf = new Config();  //storm配置        //conf.setDebug(true);        /*conf.put(Config.NIMBUS_SEEDS, Arrays.asList("master"));        conf.put(Config.STORM_ZOOKEEPER_SERVERS, Arrays.asList("slave01", "slave02", "slave03"));        */        //本地模式        LocalCluster lc = new LocalCluster();        lc.submitTopology("wordcount", conf, builder.createTopology());//提交作业        //远程集群模式        /*System.setProperty("storm.jar", "E:\\workspaces\\hadoop42-012-storm01\\target\\hadoop42-012-storm01-0.0.1-SNAPSHOT.jar");        StormSubmitter.submitTopology("wordcount4", conf, builder.createTopology());*/    }}import java.util.Map;import org.apache.storm.spout.SpoutOutputCollector;import org.apache.storm.task.TopologyContext;import org.apache.storm.topology.OutputFieldsDeclarer;import org.apache.storm.topology.base.BaseRichSpout;import org.apache.storm.tuple.Fields;import org.apache.storm.tuple.Values;public class WordCountSpout extends BaseRichSpout {    /**     *      */    private static final long serialVersionUID = -2882826287312192427L;    private SpoutOutputCollector collector;    @SuppressWarnings("rawtypes")    @Override //只会调用一次    public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {        this.collector=collector;    }    @Override   //接收外部数据转换为topology内部的源数据处理    public void nextTuple() {        String[] sentests = new String[]{"jahdkj djhdk dsjdl",                "jdkasjfl jfhk dhaku","dbhajdh frhy3ui hfkhf",                "hekdh ekfhak dkuhwk","ruyw flk ejr"};        int randIndex = (int) (Math.random() * sentests.length);        String sentence = sentests[randIndex];        collector.emit(new Values(sentence));  //写出去的数据    }    @Override    public void declareOutputFields(OutputFieldsDeclarer declarer) {        declarer.declare(new Fields("sentence"));  //写出数据的key    } }import org.apache.storm.task.OutputCollector;import org.apache.storm.task.TopologyContext;import org.apache.storm.topology.OutputFieldsDeclarer;import org.apache.storm.topology.base.BaseRichBolt;import org.apache.storm.tuple.Fields;import org.apache.storm.tuple.Tuple;import org.apache.storm.tuple.Values;public class WordCountBolt extends BaseRichBolt {    private static final long serialVersionUID = -3262356130322390839L;    private OutputCollector collector;    @SuppressWarnings("rawtypes")    @Override    public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {        this.collector = collector;    }    @Override    public void execute(Tuple input) {        String sentence = input.getStringByField("sentence");        String[] words = sentence.split("\\s+");  //以空格为单位        for (String word : words) {            collector.emit(new Values(word, 1));        }    }    @Override    public void declareOutputFields(OutputFieldsDeclarer declarer) {        declarer.declare(new Fields("word", "num"));    }}import java.util.HashMap;import java.util.Map;import org.apache.storm.task.TopologyContext;import org.apache.storm.topology.BasicOutputCollector;import org.apache.storm.topology.OutputFieldsDeclarer;import org.apache.storm.topology.base.BaseBasicBolt;import org.apache.storm.tuple.Tuple;public class WordCountCount extends BaseBasicBolt{    private static final long serialVersionUID = -4479736188433530257L;    Map<String,Integer> wordCountMap;    @SuppressWarnings("rawtypes")    @Override    public void prepare(Map stormConf, TopologyContext context) {        super.prepare(stormConf, context);        wordCountMap = new HashMap<String,Integer>();    }    @Override    public void execute(Tuple input, BasicOutputCollector collector) {        String word = input.getStringByField("word");        int num = input.getIntegerByField("num");        if(!wordCountMap.containsKey(word)){            wordCountMap.put(word, num);        }else{            num += wordCountMap.get(word);            wordCountMap.put(word, num);        }        System.out.println(word + "出现的个数是:" + num);    }    @Override    public void declareOutputFields(OutputFieldsDeclarer declarer) {    }}

运行结果:

这里写图片描述
(注:如果采用集群模式可利用maven打包,在相应的运行的机器下查看work日志)

总结:我们可以把storm过程看做一个mapreduce,但是真正的MR运行结束后job就终止了,但是storm会一直运行下去,这体现了他的实时性。根据数据的改变会有相应的反应。

原创粉丝点击