ExclamationTopology源码解析
/*这个Topology包含一个Spout和两个Bolt。Spout发射单词, 每个bolt在每个单词后面加个”!!!”。这三个节点被排成一条线: spout发射单词给第一个bolt加三个“!”, 第一个bolt把处理好的单词发射给第二个bolt再加三个“!”。*/
public class FirstTopology {
/*这个例子的Spout组件是调用的storm-core包里的一个TestWordSpout类,它主要是随机发射一个单词,Spout类的定义方式我们在下个例子中讲,这个例子只用定义一个Bolt组件ExclaimtionBolt,它继承自BaseRichBolt,,实现IRichBolt接口,功能就是加感叹号,在Bolt组件中有两个方法是我们常用的,prepare方法主要做一些准备工作,当该组件的任务在群集中的工作程序中初始化时调用。它为Bolt提供执行的环境,并给它提供一个Outputcollector(输出集合)用来发射tuple;第二个是execute方法,它用来处理一个单独的输入元组,在这个方法里用户可以定义想要实现的计算功能。*/
public static class ExclamationBolt extends BaseRichBolt {
//首先创建一个输出集合用来存放和发射消息
OutputCollector _collector;
/*接下来重写函数prepare,当该组件的任务在集群中初始化时调用。它需要输入三个参数,第一个conf是这个Bolt的storm配置文件中的配置,;第二个是TopologyContext类类型context,用于获取有关拓扑中该任务位置的信息,包括任务ID和组件ID,输入和输出信息等;第三个是OutputcoCollector输出集合类型的发射器collector,该发射器用于从该Bolt发出元组Tuple。这里prepare方法只是简单地把OutputCollector作为一个类字段保存下来给后面execute方法使用,也就是初始化发射器*/
public void prepare(Map conf, TopologyContext context, OutputCollector collector)
{
_collector = collector;
}
/*下一步重写函数execute,它只有一个输入参数就是要处理的消息输入元组tuple, 此处execute方法从bolt的一个输入接收tuple(一个bolt可能有多个输入源)。ExclamationBolt获取tuple的第一个字段,加上”!!!”之后再发射出去。*/
public void execute(Tuple tuple) {
//tuple为输入的数据,在它后面加三个感叹号作为新的值被集合发射给下一个Bolt
_collector.emit(tuple, new Values(tuple.getString(0) + "!!!"));
//这个ack是storm的一种消息传输保证机制,简单说就是监督tuple有没有在各个组件之间正常传输。
_collector.ack(tuple);
}
/*前面我们讲过源源不断的消息tuple会形成数据流,各个组件之间的消息是以流的形式传递的, 所以最后我们还应该声明此拓扑流的输出模式,declarer用于声明输出流ID,输出字段以及每个输出流是否是直接流等,此处是声明了一个字段word用于下一个组件进行消息流的识别*/
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}
}
/*至此,Bolt组件就定义完了,有了发射组件类spout,执行组件类Bolt,接下来我们开始实例化并且创建拓扑提交器了。*/
public static void main(String[] args) throws Exception
{
//创建拓扑构建器
TopologyBuilder builder = new TopologyBuilder();
//接下来创建Spout对象,取名为word,并行度设置为10,意思是storm处理该拓扑时给该Spout分10个线程来运行它
builder.setSpout("word", new TestWordSpout(), 10);
//创建Bolt,该Bolt的名字是exclaim1,分配三个线程来处理它,它的上游是名为“word”的Spout(即,接收名为word的Spout的数据),spout和这个bolt之间流的分组方式是随机分组。
builder.setBolt("exclaim1", new ExclamationBolt(), 3).shuffleGrouping("word");
//创建第二个Bolt,该Bolt的名字是exclaim2,分配两个个线程来处理它,它的上游是名为“Exclamation”的Bolt,这两个bolt之间流的分组方式也是随机分组。
builder.setBolt("exclaim2", new ExclamationBolt(), 2).shuffleGrouping("exclaim1");
//创建输入的配置信息,它使用的是storm的配置文件,并设置为debug模式。这写的是一个简单的例子,如果要运行一些复杂的例子,需要在storm的配置文件中加入自己开发的拓扑需要用到的的配置信息。
Config conf = new Config();
conf.setDebug(true);
/*接下来就是提交拓扑了,storm的运行有两种模式: 本地模式和分布式模式. 在本地模式中, storm用一个进程里面的线程来模拟所有的spout和bolt. 本地模式对开发和测试来说比较有用。在分布式模式下, storm由一堆机器组成。当你提交topology给主节点的时候, 你同时也把topology的代码提交了。主节点负责分发你的代码并且负责给你的topolgoy分配工作进程。如果一个工作进程挂掉了,它会把任务重新分配到其它节点。*/
if (args != null && args.length > 0)
{
//如果。。。(这个是storm内部处理参数,是用clojure语言写的,如果是集群模式提交拓扑的clojure代码里会有参数args),说明在集群模式上提交,然后创建三个进程来执行此拓扑
conf.setNumWorkers(3);
StormSubmitter.submitTopologyWithProgressBar(args[0],conf,builder.createTopology()); //args是集群提交的一个参数
}
else
{
//本地模式通过定义一个LocalCluster对象来定义一个进程内的集群。提交topology给这个虚拟的集群和提交topology给分布式集 群是一样的。通过调用submitTopology方法来提交topology,它接受三个参数:要运行的topology的名字,一个配置对象以及要运行的topology本身。topology的名字是用来唯一区别一个topology的,这样你然后可以用这个名字来杀死这个topology的。前面已经说过了,你必须显式的杀掉一个topology, 否则它会一直运行。Conf对象可以配置很多东西。
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("test", conf, builder.createTopology());
Utils.sleep(10000); //用来睡眠一段时间
cluster.killTopology("test"); //这个句子是用来杀死拓扑
cluster.shutdown(); //关闭集群
}
}
}