storm 手册

    在此教程中,会学到如何创建storm topologies,且将他们部署到storm cluster上。

准备工作

    本教程使用storm-start工程的demo。推荐复制此工程,按照demo一步一步来做。阅读setting up development environment和creating a new storm project来在你的机器上设置环境。

storm cluster的组件

    storm cluster与hadoop cluster表面上有点相似。hadoop上运行"MapReduce jobs",而storm则运行"topologies"。jobs和topologies它们有很大的区别,最为关键的区别是:MapReduce job最终会结束,而topologies会永远处理消息,直到进程被kill。
    在storm cluster中存在两种类型节点:master node和workers nodes。master node运行的守护进程成为"Nimbus",类似与JobTracker。Nimbus负责在cluster中分发代码,分配task给机器并且监控失败。

    每个worker node运行的守护进程称为"supervisor"。supervisor监听Nimbus分配过来的work,启动,停止worker线程。每个workder进程执行topology的子集。一个运行的topology由多个横跨多个机器的worker组成。


    Nimbus和所有的supervisor之间的所有协作都是通过Zookeeper集群完成的。另外,Nimbus和supervisor都是快速失败,且无状态的。所有的状态都保存在Zookeeper或本地磁盘上。意味着可以kill -9 Nimbus或supervisor进程,它们会恢复启动后就像什么没有发生一样。此设计保证了storm的高稳定性。

Topologies

    为了在storm做实时计算,必须创建topology。topology是计算图。topology中的每个节点包含一个处理逻辑,节点之间的链接表明了数据如何在节点之间被传输。
    运行topology非常直接了当:首先将你的代码和依赖打包为一个jar,接着运行以下命令即可:
  storm jar all-my-code.jar backtype.storm.MyTopology arg1 arg2
    此命令运行类backtype.storm.MyTopology,参数为arg1和arg2。此类的主要功能定义了topology,并且提交到Nimbus。storm jar负责链接Nimbus上传jar。
    因为topology定义是thrift结构,Nimbus是thrift服务,你可以使用任何语言创建,提交topology。
stream。
    storm的核心是"stream"。stream是无边界的tuple序列。storm以分布、可靠的方式为转换一个stream到新的stream提供了基本组件。
    storm为stream的转换提供的基本组件是spouts和bolts。spouts和bolt是你必须实现的接口,运行程序特定逻辑。
    spout是stream的源,例如spout会读取kestrel队列的tuples,且作为一个stream发出。或spout会链接到twitter api,作为tweet stream发送。
    bolt消费多个input stream,做一些处理,且可能发送新的stream。复杂的stream转换,如从tweet stream计算trending topics,需要多个步骤和多个bolts。bolts通过运行函数,过滤tuple,聚集,链接,与database交互可以实现任何操作。
    spouts和bolts的网络被包装成一个topology,这是提交给storm集群来执行的最顶层的抽象。一个topology是stream转换(每个节点是spout或bolt)的图。图中的边表示bolt订阅了哪些stream。当spout或bolt输出tuple到一个stream,它发送tuple到每个订阅此stream的bolt。

    topology中nodes之间的链接表明tuple如何传递。例如spout A->Bolt B,spout A->Bolt C,Bolt B->Bolt C,spout A发送的tuple会发送到Bolt B和C,所有的Bolt B的输出也会流向Bolt C。
    storm topolgy中的每个节点的计算都是并行的。可以指定每个node的并行度,此时storm会在集群中产生指定并行度大小个线程来执行。

    topology永不休止的运行,直到kill它。storm会自动重新分配失败的task。另外storm会保证没有数据丢失,即使机器停止,消息落下(遗失)。

(Data model)数据模型

    storm使用tuple作为它的数据模型。tuple是一命名的值的列表,tuple中的field可以是任何类型的对象。storm支持所有基本类型,string,字节数组。为了使用其他类型的对象,仅需要实现serializer接口。
    在topology中的每个节点必须声明tuples的输出域。下例,bolt声明它输出两个tuples(double和triple两个域)。
public class DoubleAndTripleBolt extends BaseRichBolt {
    private OutputCollectorBase _collector;
    @Override
    public void prepare(Map conf,TopologyContext context,OutputCollectorBase collector){
        _collector = collector;
    }
    @Override
    public void execute(Tuple input) {
        int val = input.getInteger(0);        
        _collector.emit(input, new Values(val*2, val*3));
        _collector.ack(input);
    }
    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("double", "triple"));
    }    
}

    declareoutputField函数声明了组件的["double","triple"]输出域。bolt的剩下部分会在后面解释。

simple topology

    先看看简单的topology来探究更多的概念,看代码如何改进。
TopologyBuilder builder = new TopologyBuilder();        
builder.setSpout("words", new TestWordSpout(), 10);        
builder.setBolt("exclaim1", new ExclamationBolt(), 3).shuffleGrouping("words");
builder.setBolt("exclaim2", new ExclamationBolt(), 2).shuffleGrouping("exclaim1");
    此topology包含一个spout和两个bolts。spout输处word,每个bolt追加"!!!"到输出。node排列成一行:spout输出到第一个bolt,此bolt输出到第二个bolt。

    定义node的代码使用了setSpout和setBolt方法。这些方法接收用户自定义ID输入,一个包含处理逻辑的对象和node的并行度。包含处理逻辑的对象实现了IRichSpout和IRichBolt接口。最后一个参数:node的并行度,是可选的,它指定了在集群中多少个线程被创建来执行此组件,如果忽略,storm会为每个Node分配一个线程。

    setBolt返回InputDeclarer对象,用来定义Bolt的输入。组件"exclaim1"声明它需要读取被组件"words"的所有tuple输出,使用shuffle分组。"shuffle group"意味着tuple被随机从输入task分配到bolts task。这有很多方式在组件中对数据分组。
如果想要组件"exclaim2"读取组件"words"和"exclaim"的所有tuple输出,可以在"exclaim2"中这样定义:

builder.setBolt("exclaim2", new ExclamationBolt(), 5)
            .shuffleGrouping("words")
            .shuffleGrouping("exclaim1");
输出声明可以链接起来,指定多个blot源。

spouts和bolts实现

    spouts负责输出新消息到topology。TestWordSpout输出从列表["nathan", "mike", "jackson", "golda", "bertels"]中随机输出word。
public void nextTuple() {
    Utils.sleep(100);
    final String[] words = new String[] {"nathan","mike","jackson","golda","bertels"};
    final Random rand = new Random();
    final String word = words[rand.nextInt(words.length)];
    _collector.emit(new Values(word));
}

ExclamationBolt追加"!!!"到它的输出。

public static class ExclamationBolt implements IRichBolt {
    OutputCollector _collector;
    public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
        _collector = collector;
    }
    public void execute(Tuple tuple) {
        _collector.emit(tuple, new Values(tuple.getString(0) + "!!!"));
        _collector.ack(tuple);
    }
    public void cleanup() {
    }
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("word"));
    }
    public Map getComponentConfiguration() {
        return null;
    }
}

   prepare方法给bolt个提供了OutputCollector,用来输出tuple。tuple可以在prepare,execute,cleanup等方法中任何时候输出,或在其他同步线程中。
   execute方法从bolt的输入接收tuple。ExcamationBolt从tuple中抓取第一个filed,并输出新的tuple。如果自己实现的bolt订阅了多个输入源,你可以通过Tuple.getSourceComponent方法来判断来自哪个组件。
   表面上,input tuple作为第一个参数被传递输出,在最后一行调用ask()方法,这是storm的可靠API,来保证没有数据丢失。
   cleanup方法在Bolt被关闭,且清理任何打开的资源使用。不能保证此方法会在cluster上被调用,例如,如果机器的任务爆发性增长,不会调用此方法。cleanup方法更倾向于topology的本地模式。
   declareOutputFields方法声明ExclamationBolt输出。
   getComponentConfiguration方法允许你配置此组件怎样运行的多个aspect。
   cleanup和getComponentConfiguration方法在bolt实现中并不是经常需要。可以使用Base Class来提供默认实现。
public static class ExclamationBolt extends BaseRichBolt {
    OutputCollector _collector;
    public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
        _collector = collector;
    }
    public void execute(Tuple tuple) {
        _collector.emit(tuple, new Values(tuple.getString(0) + "!!!"));
        _collector.ack(tuple);
    }
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("word"));
    }    
}

本地模式运行Topology

    storm有两种模式:本地模式和分布式模式。在本地模式中,storm使用线程来模拟worker nodes执行。本地模式用来测试和开发topologies。在分布式模式中,storm作为集群机器操作。当提交topology给master,你也提交所有必要的代码来运行topology。master利用你的分布式代码,分配workers运行你的topology。如果workers宕机了,master会在其他地方重新分配。

//ExclamationTopology
Config conf = new Config();
conf.setDebug(true);
conf.setNumWorkers(2);
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("test", conf, builder.createTopology());
Utils.sleep(10000);
cluster.killTopology("test");
cluster.shutdown();    

    首先,代码通过创建的LocalCluster对象定义了in-process cluster。提交topology给虚拟的cluster,等同与提交topology给分布式cluster。它调用subumitTopology提交topology给LocalCluster,参数为要运行的topology的名称,topology的配置和topology自己。

    topology的名字用来识别,可以在稍后kill它。configuration用来调节正在运行的topology的各方面。
    1)TOPOLOGY_WORKERS,指定在集群中分配多少个进程来 执行topology。topology中的每个组件将会以多个线程来执行。分配给组件的多少个线程通过setBolt和setSpout方法来配置。这些线程在worker进程内退出。每个worker进程内一些组件有多个线程。例如你可以指定所有组件中300个线程,50个worker进程。每个worker进程执行6个线程,每个线程可以属于不同的组件。可以通过稍微调整每个组件的并行度,每个worker进程的线程数来调整storm topology的性能。
    2)TOPOLOGY_DEBUG,设置为true,记录被组件输出的每个消息。

stream grouping

    stream grouping告诉topology在两个组件中怎样发送tuple。记住,spout和bolt在集群中并行以多个task执行。

当任务(Bolt A输出一个tuple给Bolt B),此任务将tuple发送给哪个呢?stream grouping告诉storm在多个task之间如何发送tuple。

TopologyBuilder builder = new TopologyBuilder();        
builder.setSpout("sentences", new RandomSentenceSpout(), 5);        
builder.setBolt("split", new SplitSentence(), 8)
        .shuffleGrouping("sentences");
builder.setBolt("count", new WordCount(), 12)
        .fieldsGrouping("split", new Fields("word"));

    splitSentence为每个单词输出一个tuple,wordCount在内存中保存一份从word到count的映射,每次wordcount接收一个word时,它更新状态,输出新word,count。
    有多种stream grouping。最简单的group,称为"shuffle grouping",它发送tuple给随机的task。更为有趣的"field grouping",它保证统一个word分配到统一个task。fields grouping使得通过field的子集将stream分组。Fields grouping是streaming join和streaming aggregation的实现基础。

以其他语言定义Bolt

    以其他语言编写的bolt作为子进程执行,storm与这些子进程通过(基于标准输入输出)Json消息通信。storm为Ruby,python,Fancy提供了适配器库。
public static class SplitSentence extends ShellBolt implements IRichBolt {
    public SplitSentence() {
        super("python", "splitsentence.py");
    }
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("word"));
    }
}
splitsentence覆写了shellBolt,声明使用python 运行,脚本为splitsentence.py。

import storm

class SplitSentenceBolt(storm.BasicBolt):
    def process(self, tup):
        words = tup.values[0].split(" ")
        for word in words:
          storm.emit([word])
SplitSentenceBolt().run()


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值