以Storm开发指南中的一个简单例子开始
import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.StormSubmitter;
import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.TopologyBuilder;
import backtype.storm.topology.base.BaseRichBolt;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;
import backtype.storm.utils.Utils;
import backtype.storm.spout.SpoutOutputCollector;
import backtype.storm.topology.base.BaseRichSpout;
import org.apache.log4j.Logger;
import java.util.Map;
import java.util.HashMap;
import java.util.Random;
public class ExclamationTopology {
public static class TestWordSpout extends BaseRichSpout {
public static Logger LOG = Logger.getLogger(TestWordSpout.class);
boolean _isDistributed;
SpoutOutputCollector _collector;
public TestWordSpout() {
this(true);
}
public TestWordSpout(boolean isDistributed) {
_isDistributed = isDistributed;
}
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
_collector = collector;
}
public void close() {
}
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));
}
public void ack(Object msgId) {
}
public void fail(Object msgId) {
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}
@Override
public Map getComponentConfiguration() {
if(!_isDistributed) {
Map ret = new HashMap();
ret.put(Config.TOPOLOGY_MAX_TASK_PARALLELISM, 1);
return ret;
} else {
return null;
}
}
}
public static class ExclamationBolt extends BaseRichBolt {
OutputCollector _collector;
@Override
public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
_collector = collector;
}
@Override
public void execute(Tuple tuple) {
_collector.emit(tuple, new Values(tuple.getString(0) + "!!!"));
_collector.ack(tuple);
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}
}
public static void main(String[] args) throws Exception {
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("word", new TestWordSpout(), 10);
builder.setBolt("exclaim1", new ExclamationBolt(), 3)
.shuffleGrouping("word");
builder.setBolt("exclaim2", new ExclamationBolt(), 2)
.shuffleGrouping("exclaim1");
Config conf = new Config();
conf.setDebug(true);
if(args!=null && args.length > 0) {
conf.setNumWorkers(3);
StormSubmitter.submitTopology(args[0], conf, builder.createTopology());
} else {
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("test", conf, builder.createTopology());
Utils.sleep(10000);
cluster.killTopology("test");
cluster.shutdown();
}
}
}
几点说明
1.Spout实现
Strom使用元组作为数据模型,元组就是一组命名的值,元组中的每个字段都可以是任何类型的对象。Storm支持所有基本类型,string和byte数组作为元组字段值。如果要使用自己定义的类型,也只需要为你自己定义的类型实现并且注册一个serializer即可。每个节点还必须要为输出的元组定义字段名称。
Spout要么继承BaseRichSpout要么实现IRichSpout和IComponent接口,对与实现来说主要是实现以下这些函数(参考TestWordSpout的实现):
void open(java.util.Map conf,
TopologyContext context,
SpoutOutputCollector collector)
当一个Supervisor初始化该Spout组件时调用,提供Spout运行所必需的环境
参数
conf - Storm关于这个Spout的配置
context - 这个配置被用来获取该Spout任务的信息,包括任务id,组件id,输入输出信息等等
collector - 用来从这个Spout里发送元组,元组可以在任何时间里发送,包括open和close函数里。collector是线程安全的,应该被作为一
个实例对象保存到Spout对象里
void declareOutputFields(OutputFieldsDeclarer declarer)
定义topology里的Stream的schema
declarer - 定义输出stream的ids,输出的字段,输出stream是不是直接stream(direct stream)
java.util.Map getComponentConfiguration()
定义该组件的配置
void ack(java.lang.Object msgId)
以msgId消息告诉Storm这个Spout已经成功输出了该元组
void activate()
激活Spout,Spout从deactivate模式转化为activate模式,Spout开始调用nextTuple输出数据。
void close()
关闭Spout
void deactivate()
解除激活Spout,Spout从activate模式转化为deactivate模式,Spout停止调用nextTuple输出数据
void fail(java.lang.Object msgId)
以msgId消息告诉Storm这个Spout输出该元组失败,主要用于将该元组重新放回消息队列,以在一段时间后重发该元组
void nextTuple()
调用该函数请求Storm发送元组到Output Collector,这个函数不应该是阻塞的,当没有元组发送时,一般调用sleep,以充分利用CPU
2.Bolt的实现
最主要的三个函数是,其余的关于组件接口的函数和Spout的实现是一样的,这里就不说了
void prepare(java.util.Map stormConf,
TopologyContext context,
OutputCollector collector)
和Spout的open函数的作用类似,在Bolt组件初始化的时候调用,提供Bolt所必需的环境
void execute(Tuple input)
处理单个输入的元组,元组对象包含了从组件/流/任务得来的元数据。元组的值通过Tuple#getValue访问,Bolt并不需要马上处理元组,可以先将数据保存在合适的时间处理。Bolt使用在prepare函数中得到的OutputCollector对象输出元组,必须在这个函数里面确保使用OutputCollector#ack或者OutputCollector#fail告知Storm已经处理成功或者处理失败,否则Storm将无法确定Spout里元组是否已经被处理完成。
void cleanup()
当Bolt要关闭的时候调用,但是不能保证该函数一定可以被调用,当使用kill -9命令杀死工作进程时该函数就无法调用,一般用于local mode下清理使用
3.Topology构建
构建相当直接,使用TopologyBuilder构建,如例子中的main函数的代码所示。TopologyBuilder#setSpout设置Topology的Spout,使用TopologyBuilder#setBolt设置Topology的Bolt。
其中
public BoltDeclarer setBolt(java.lang.String id,
IBasicBolt bolt,
java.lang.Number parallelism_hint)
id-需要消费该组件输出的流的组件用来识别该组件的唯一标识
bolt-该节点处理数据的Bolt
parallelism_hint-用来执行该Bolt的任务的数量,每个任务会在集群的某个进程的某个线程里面执行
其中BoltDeclarer中包含了很多元组从一个节点怎么映射到另一个节点的规则,例子中的builder.setBolt("exclaim1", new ExclamationBolt(), 3)
.shuffleGrouping("words")表示设置exclaim1节点的Bolt为ExclamationBolt,并行度为3,从words节点到exclaim1节点使用随机散发规则。
public SpoutDeclarer setSpout(java.lang.String id,
IRichSpout spout,
java.lang.Number parallelism_hint)
id-需要消费该组件输出的流的组件用来识别该组件的唯一标识
spout-Spout类
parallelism_hint-用来执行该Bolt的任务的数量,每个任务会在集群的某个进程的某个线程里面执行