1.topology
一个topolgy是spouts和bolts组成的图,通过stream groupings将图中的spout和bolts连接起来:如图所示:
一个topology会一直运行知道你手动kill掉,Storm自动重新分配执行失败的任务,并且Storm可以保证你不会有数据丢失(如果开启了高可靠性的话)。如果一些机器意外停机它上面的所有任务会被转移到其他机器上;
运行一个toplogy很简单,首先,把你所有的代码以及所依赖的jar打进一个jar中。然后运行类似下面的命令:
storm jar all-my-code.jar backtype.storm.MyTopology arg1 arg2
这个命令会运行主类:backtype.storm.MyTopology,参数是arg1,arg2。这个类的main函数定义这个topology并且把它提交给Nimbus。storm jar负责连接到Nimbus并且上传jar包;
Topology的定义是一个Thrift结构,并且Nimbus就是一个Thrift服务,你可以提交任何语言创建的topology。上面的方法就是用JVM-based语言提交的最简单的方法。
如下代码即定义了一个topology:
TopologyBuilder builder = new TopologyBuilder(); builder.setSpout("spout",new RandomSentceSpout(),5); builder.setBolt("split",new SplitSentence(),8).shuffleGrouping("spout") builder.setBolt("count", new WordCount(),12).fieldsGrouping("spilt",new Fields("word"));
2、Streams
小溪流stream是storm里的关键抽象。一个消息流是一个没有边界的tuple序列,而这些tuple序列会以一种分布式的方式并行的创建和处理。再默认的情况写,tuplr的字段类型可以是:integer,long,short,byte,string,double,boolean和byte array。也可以自定义类型(只要实现相应的序列化器)。
public void declareOutputFields(OutputFieldsDeclarer declarer){
//默认ID的信息流定义
declarer.declare(new Fields("word","cout"));
//自定义ID的消息流
declare.declareStream("streamId",new Fields("word","count"));
}
每个消息流再定义的时候会被分配给一个id,因为单向消息流使用的相当普遍,OutputFieldsDeclarer定义了一些方法让你可以定义一个stream而不用制定这个id。在这种情况下这个stream会分配个值为‘default’默认的id。
Storm提供的最近本的处理stream的原语是spout和bolt。你可以实现spout和bolt提供的接口来处理你的业务逻辑。
3、Soupts
消息源spout是Storm里面一个topology里面的消息生产者。一般来说消息源会从一个外部源读取数据并且向topology里面发送出消息:tuple。Spout可以是可靠地也可以是不可靠的。如果这个tuple没有被storm成功处理,可靠地消息源spouts可以重新发射一个tuple,但是不可靠的消息源spouts一旦发出一个tuple就不能重发了。
消息源可以发射多条消息流stream。使用OutputFieldsDeclarer。declareStream来定义多个stream,然后使用SpoutOutputCollector来发射指定的stream。
/**
*定义了2个消息流
*/
public void declareOutputFields (OutputFieldsDeclarer declarer){
declarer.declareStream("streamId1",new Fields("words"));
declarer.declareStream("streamId2",new Fields("word2"));
}
/**
*根据消息流发射相应的消息
*/
public void nextTuple(){
collector.emit("streamId1",new Values("streamid1's word1"));
collector.emit("streamId2".new Values("streamid2's word2"));
}
Spout类里面最重要的方法是nextTuple。要么发射一个新的tuple到topology里面或者简单的返回如果已经没有新的tuple。要注意的是nextTuple方法不能阻塞,因为storm在同一个线程上面调用所有消息源spout的方法。
另外两个比较重要的spout方法是ack和fail。storm在检测到一个tuple被整个topology成功处理的时候调用ack,否则调用fail。storm只对可靠的spout调用ack和fail。
4、Bolts
所有的消息处理逻辑被封装在bolts里面。Bolts可以做很多事情:过滤,聚合,查询数据库等等;
Bolts可以简单地做消息流的传递。复杂的消息流处理往往需要很多步骤,从而就需要经过很多bolts。
Bolts和spouts一样,可以发射多条消息流,使用OutputFieldsDeclarer。declareStream定义stream,使用OutputCollector.emit来选择要发射的stream。
Bolts的主要方法是execute,他以一个tuple作为输入,使用OutputCollector来发射tuple,bolts必须要为它处理的每一个tuple调用OutputCollector的ack方法,以通知Storm这个tuple被处理完成了,从而统治这个tuple的发射这spouts.一般的流程是:bolts处理一个输入tuplr,发射0个或者多个tuple,然后调用ack通知storm自己已经处理过这个tuple了。storm提供了一个IBasicBolt会自动调用ack。
5、Stream Grouping
定义一个 toplogy的其中一步是定义每个bolt接受什么样的流作为输入。stream grouping就是用来定义一个stream应该如果分配数据个ibolts上面的多个tasks。
Storm里面有7种类型的stream grouping:
--Shuffle Grouping:随机分组,随机派发stream里面的tuple,保证每个bolt接受到的tuple数目大致相同;
--Fields Grouping:按字段分组,比如按userid来分组,具有同样的userid的tuple会被分到相同的Bolts里的一个task,而不同的userid则会被分配到不同的bolts里面的task。
--All Grouping:广播发送,对于每一个tuple,所有的bolts都会受到。
--Global Grouping:全局分组,这个tuple被分配到storm中的一个bolt的其中一个task。再具体一点就是分配给id值最低的那个task。
--Non Grouping:不分组,这个分组的意思是说stream不关心到底谁会受到它的tuple。目前这种分组和Shuffle grouping是一样的效果,有一点不同的是storm会把这个bolt放到这个bolt的订阅者同一个线程里面去执行。
--Direct Grouping:直接分组,这是一种比较特别的分组方法,用这种分组意味着消息的发送者指定由消息接受者的哪个task处理这个消息。只有被声明为Direct Stream的消息流可以声明这种分组方法。而且这种消息tuple必须使用emitDirect方法来发射。消息处理着可以通过TopologyContext来获取处理它的消息的task的id(OutputCollector。emit方法也会返回tsk的id)。
--Local or shuffle grouping:如果目标bolt有一个或者多个task在同一个工作进程中,tuple将会被随机发送给这些tasks。否则,和普通的Shuffle Grouping行为一致。
代码示例:
TopologyBuisder builder = new TopologyBuilder(); builder.setSpout("spout",new RandomSentenceSpout(),5);
builder.setBolt("split",new SplitSentence(),8).shuffleGrouping("spout");
builder.setBolt("count",new WordCount(),12).fieldsGrouping("spilt",new Fields("word")):
6.Reliability
storm保证每个tuple会被topology完整的执行。Storm会追踪有每个spout tuple所含生的tuple树(一个bolt处理一个tuple之后可能会发射别的tuple从而形成树状结构),并且跟踪这颗tuple树什么时候成功处理完。每个topology都有一个消息超时的设置,如果storm在这个超市的时间内检测不到某个tuple树到底有没有执行成功,那么topology会把这个tuple标记为执行失败,并且过一会重新发射这个tuple。
为了利用Strom的可靠性特性,在你发出一个新的tuple以及你完成处理一个tuple的时候你必须要通知storm。这一切是由OutputCollector来完成的。通过emit方法来通知一个新的tuple产生了,通过ack方法通知一个tuple处理完成了。
7、Tasks
每一个spout和bolt会被当做很多task在整个集群里执行。每一个executor对应到一个线程,在这个线程上运行多个task,而stream grouping则是定义怎么从一堆task发射tuple到另外一堆task。你可以调用TopologyBuilder类里的setSpout和setBolt来设置并调度(也就是有多少个task)。
代码示例如下:
ToplogyBuilder builder = new TopologyBuilder(); builder.setSpout("spout",new RandomSentenceSpout(),5).setNumTasks(10);
builder.setBolt("spilt",new SplitSentence(),8).shuffleGrouping("spout").setNumTasks(8);
builder.setBolt("count",new WoedCount(),12).fieldsGrouping("split",new Fields("woed")).setNumTasks(24);
表示“spout”的线程数为5,任务数为10,即一个线程运行两个任务;
“split”则为一个线程运行一个任务,和默认的一致。
8.Workers
一个topology可能会在一个或者多个worker(工作进程)里面执行,每个worker是一个屋里JVM并执行整个topology的一部分。比如,对于并行度是300的topology来说,如果我们使用50个工作进程来执行,那么每个工作进程会开启6个线程,默认默认每个线程处理一个tasks。Storm会尽量均匀的工作分配给所有的worker。每个supervisor上运行着若干个worker进程(根据配置文件supervisor.slots.ports进行配置)。
9.Configuration
Storm里面有一堆参数可以配置来调整Nimbus,Supervisor以及正在运行的topology的行为,一些配置是系统级别的,一些配置是topology级别的。default。yaml里面有所有的默认配置。你可以通过定义一个storm.yaml在你的classpath里面来覆盖这些默认的配置并且你也可以在代码里面设置一些topology相关的配置信息(使用StromSubmitter)。