Storm 笔记 nc集成log输出 hashmap.clear() storm集成kafka maven复制jar包到共享目录下

Topology:有向图,顶点是计算,边是数据流。




storm消息的可靠处理 IRichBolt和IBasicBolt/BaseBasicBolt对比 使用IBasicBolt/BaseBasicBolt不需要总是调用collect.ack,storm会帮我们处理。

对于spout,有ISpout,IRichSpout,BaseRichSpout

对于bolt,有IBolt,IRichBolt,BaseRichBolt,IBasicBolt,BaseBasicBolt

IBasicBolt,BaseBasicBolt不用每次execute完成都写ack/fail,因为已经帮你实现好了。


 作 为storm的使用者,有两件事情要做以更好的利用storm的可靠性特征。 首先,在你生成一个新的tuple的时候要通知storm; 其次,完成处理一个tuple之后要通知storm。 这样storm就可以检测整个tuple树有没有完成处理,并且通知源spout处理结果。storm提供了一些简洁的api来做这些事情。

由一个tuple产生一个新的tuple称为: anchoring。你发射一个新tuple的同时也就完成了一次anchoring。看下面这个例子: 这个bolt把一个包含一个句子的tuple分割成每个单词一个tuple。

   

Java代码  收藏代码

  1. public class SplitSentence implements IRichBolt   

  2.             Output Collector _collector;   

  3.         

  4.             public void prepare(Map conf,   

  5.                                 TopologyContext context,   

  6.                                 OutputCollector collector)   

  7.                 _collector collector;   

  8.               

  9.         

  10.             public void execute(Tuple tuple)   

  11.                 String sentence tuple.getString(0);   

  12.                 for(String word: sentence.split("))   

  13.                     _collector.emit(tuple,newValues(word));   

  14.                   

  15.                 _collector.ack(tuple);   

  16.               

  17.         

  18.             public void cleanup()   

  19.               

  20.         

  21.             public void declareOutputFields(OutputFieldsDeclarer declarer)   

  22.                 declarer.declare(newFields("word"));   

  23.               

  24.            

 

 我 们通过anchoring来构造这个tuple树,最后一件要做的事情是在你处理完当个tuple的时候告诉storm,  通过OutputCollector类的ack和fail方法来做,如果你回过头来看看SplitSentence的例子, 你可以看到“句子tuple”在所有“单词tuple”被发出之后调用了ack。

你可以调用OutputCollector 的fail方法去立即将从消息源头发出的那个tuple标记为fail, 比如你查询了数据库,发现一个错误,你可以马上fail那个输入tuple, 这样可以让这个tuple被快速的重新处理, 因为你不需要等那个timeout时间来让它自动fail。

每个你处理的tuple, 必须被ack或者fail。因为storm追踪每个tuple要占用内存。所以如果你不ack/fail每一个tuple, 那么最终你会看到OutOfMemory错误。

大 多数Bolt遵循这样的规律:读取一个tuple;发射一些新的tuple;在execute的结束的时候ack这个tuple。这些Bolt往往是一些 过滤器或者简单函数。Storm为这类规律封装了一个BasicBolt类。如果用BasicBolt来做, 上面那个SplitSentence可以改写成这样:

   

Java代码  收藏代码

  1. public class SplitSentence implements IBasicBolt   

  2.             public void prepare(Map conf,   

  3.                                 TopologyContext context)   

  4.               

  5.         

  6.             public void execute(Tuple tuple,   

  7.                                 BasicOutputCollector collector)   

  8.                 String sentence tuple.getString(0);   

  9.                 for(String word: sentence.split("))   

  10.                     collector.emit(newValues(word));   

  11.                   

  12.               

  13.         

  14.             public void cleanup()   

  15.               

  16.         

  17.             public void declareOutputFields(   

  18.                             OutputFieldsDeclarer declarer)   

  19.                 declarer.declare(newFields("word"));   

  20.               

  21.            

 
这个实现比之前的实现简单多了, 但是功能上是一样的。

发送到BasicOutputCollector的tuple会自动和输入tuple相关联,而在execute方法结束的时候那个输入tuple会被自动ack的。
我们编写的时候使用IBasicBolt最方便了。或者 extends BaseBasicBolt类


Install dependencies on Nimbus and worker machines

Next you need to install Storm's dependencies on Nimbus and the worker machines. These are:

  1. Java 7
  2. Python 2.6.6

These are the versions of the dependencies that have been tested with Storm. Storm may or may not work with different versions of Java and/or Python.

Download and extract a Storm release to Nimbus and worker machines

Next, download a Storm release and extract the zip file somewhere on Nimbus and each of the worker machines. The Storm releases can be downloaded from here.

Fill in mandatory configurations into storm.yaml

The Storm release contains a file at conf/storm.yaml that configures the Storm daemons. You can see the default configuration values here. storm.yaml overrides anything in defaults.yaml. There's a few configurations that are mandatory to get a working cluster:

1) storm.zookeeper.servers: This is a list of the hosts in the Zookeeper cluster for your Storm cluster. It should look something like:

storm.zookeeper.servers:
  - "111.222.333.444"
  - "555.666.777.888"

If the port that your Zookeeper cluster uses is different than the default, you should set storm.zookeeper.port as well.

2) storm.local.dir: The Nimbus and Supervisor daemons require a directory on the local disk to store small amounts of state (like jars, confs, and things like that). You should create that directory on each machine, give it proper permissions, and then fill in the directory location using this config. For example:

storm.local.dir: "/mnt/storm"

If you run storm on windows,it could be: yaml storm.local.dir: "C:\\storm-local" If you use a relative path,it will be relative to where you installed storm(STORM_HOME). You can leave it empty with default value $STORM_HOME/storm-local

3) nimbus.seeds: The worker nodes need to know which machines are the candidate of master in order to download topology jars and confs. For example:

nimbus.seeds: ["111.222.333.44"]

You're encouraged to fill out the value to list of machine's FQDN. If you want to set up Nimbus H/A, you have to address all machines' FQDN which run nimbus. You may want to leave it to default value when you just want to set up 'pseudo-distributed' cluster, but you're still encouraged to fill out FQDN.

4) supervisor.slots.ports: For each worker machine, you configure how many workers run on that machine with this config. Each worker uses a single port for receiving messages, and this setting defines which ports are open for use. If you define five ports here, then Storm will allocate up to five workers to run on this machine. If you define three ports, Storm will only run up to three. By default, this setting is configured to run 4 workers on the ports 6700, 6701, 6702, and 6703. For example:

supervisor.slots.ports:
    - 6700
    - 6701
    - 6702
    - 6703

Monitoring Health of Supervisors

Storm provides a mechanism by which administrators can configure the supervisor to run administrator supplied scripts periodically to determine if a node is healthy or not. Administrators can have the supervisor determine if the node is in a healthy state by performing any checks of their choice in scripts located in storm.health.check.dir. If a script detects the node to be in an unhealthy state, it must print a line to standard output beginning with the string ERROR. The supervisor will periodically run the scripts in the health check dir and check the output. If the script’s output contains the string ERROR, as described above, the supervisor will shut down any workers and exit.

If the supervisor is running with supervision "/bin/storm node-health-check" can be called to determine if the supervisor should be launched or if the node is unhealthy.

The health check directory location can be configured with:

storm.health.check.dir: "healthchecks"

The scripts must have execute permissions. The time to allow any given healthcheck script to run before it is marked failed due to timeout can be configured with:

storm.health.check.timeout.ms: 5000


他继承了ArrayList类


Storm Grouping:

  1. Shuffle Grouping :随机分组,尽量均匀分布到下游Bolt中

    将流分组定义为混排。这种混排分组意味着来自Spout的输入将混排,或随机分发给此Bolt中的任务。shuffle grouping对各个task的tuple分配的比较均匀。

  2. Fields Grouping :按字段分组,按数据中field值进行分组;相同field值的Tuple被发送到相同的Task

    这种grouping机制保证相同field值的tuple会去同一个task,这对于WordCount来说非常关键,如果同一个单词不去同一个task,那么统计出来的单词次数就不对了。“if the stream is grouped by the “user-id” field, tuples with the same “user-id” will always go to the same task”. —— 小示例

  3. All grouping :广播

    广播发送, 对于每一个tuple将会复制到每一个bolt中处理。

  4. Global grouping :全局分组,Tuple被分配到一个Bolt中的一个Task,实现事务性的Topology。

    Stream中的所有的tuple都会发送给同一个bolt任务处理,所有的tuple将会发送给拥有最小task_id的bolt任务处理。

  5. None grouping :不分组

    不关注并行处理负载均衡策略时使用该方式,目前等同于shuffle grouping,另外storm将会把bolt任务和他的上游提供数据的任务安排在同一个线程下

  6. Direct grouping :直接分组 指定分组

    由tuple的发射单元直接决定tuple将发射给那个bolt,一般情况下是由接收tuple的bolt决定接收哪个bolt发射的Tuple。这是一种比较特别的分组方法,用这种分组意味着消息的发送者指定由消息接收者的哪个task处理这个消息。 只有被声明为Direct Stream的消息流可以声明这种分组方法。而且这种消息tuple必须使用emitDirect方法来发射。消息处理者可以通过TopologyContext来获取处理它的消息的taskid (OutputCollector.emit方法也会返回taskid)。

 从task角度看Topology:

  A stream grouping tells a topology how to send tuples between two components. Remember, spouts and bolts execute in parallel as many tasks across the cluster. If you look at how a topology is executing at the task level, it looks something like this:

  问:When a task for Bolt A emits a tuple to Bolt B, which task should it send the tuple to?

  答:A "stream grouping" answers this question by telling Storm how to send tuples between sets of tasks.

Field Grouping:

  A fields grouping lets you group a stream by a subset of its fields. This causes equal values for that subset of fields to go to the same task

  Fields groupings are the basis of implementing streaming joins and streaming aggregations as well as a plethora of other use cases. Underneath the hood, fields groupings are implemented using mod hashing.(stream的合并、聚合、等的基础;哈希)



      -L长时间开启,一个客户端断开连接,服务器并不会退出。-s 0.0.0.0通配ip。





这个要放在里面,因为可能是不同的线程,不同的虚拟机调用此方法。放到集群如果是上面的放在外面,它就只能打印一台主机的情况了。





task数决定了你的实例个数,builder.setSpout(...,2)其实就等价于builder.setSpout(...,2).setNumTasks(2),后面也是2.如果设置了Takes数,Task平均分配到每个执行线程Executor.




前面错了。

集群状态监控:




storm集成kafka:










报错

修改pom依赖,把它拿掉就好了。他是storm-core所依赖的,直接进入下面点击就能拿到他的组ID和工件ID了。


确保消息被处理一次,only-one一次。







自定义再分区:



CheckEventSumFilter()是上面用户自定义的过滤器。


修改成这样,就会发现奇数被过滤掉了。



批次分发:


FixedBatchSpout初始化的时候制定了字段名称,一个批次的最大元祖数,要发送的数据。setCycle()是否循环发送。只有设置了再分区才有并发度的概念。

分区:




shuffle:随机。global:分配到一个taskID最小的实例中,partionBy:按字段分区,相同字段进入同一个实例。broadcast:每个实例都有一份数据。




批次聚合:


---------------------------------------------------------------------------------------------------------------------------------------


reduce()迭代输入的tuple,生成一个字段的新tuple。


curr作为反复使用的值变量。


批次聚合:一个结果是12,一个是6


分区聚合:一个结果是8,一个是4,一个是0,一个是6.




看它里面有个batchID,就应该与批次聚合共同使用。


平均值:


创建AvgBatchAggregator extends BaseAggregator




可以减少网络传输



上面SumCombinerAggregator还需要加一个串行化的字段.





分支概念。最后合成一个两个字段的tuple





编程使用DRPCClient类访问top结果:





storm整合 Hbase:


构造函数创什么表哦,写的太垃圾了。





修改上面代码:






封装插入方法:



1 storm消息的容错机制

说明:数据在处理中出现异常,需要保证数据被完整处理;
需求:Spout---A---B---C---D,当其中一个环节出现异常时,Spout能够重新发送一份数据
问题:1、Spout如何知道一条消息的处理状态?
        成功:ack(Object msgId)
        失败:fail(Object msgId)
    2、Bolt如何告知Spout的处理状态?
        collector.emit(new Value());
        collector.ack(tuple);//当消息处理成功时
        collector.fail(tuple);//当消息处理失败时

1.1 Storm中的ack机制

1.1.2 Spout发送一条数据出去,需要知道数据处理成功和失败的状态,如果失败进行消息重新发送

步骤: 
a.自定义Spout实现BaseRichSpout,覆写ackfail的方法;
b.在自定义Spout发送数据的时候,需要指定messageId,messageId其实是一个Object;
c.当消息处理成功或失败的时候,Storm框架会将messageId传输回来。如果消息要重发,直接通过messageId找到或直接转化成数据内容进行重发;
d.自定义Bolt实现BaseRichBolt,在Boltexecute的方法中进行两个操作:
    ①:发送数据时需要指定血缘关系(锚点),即collector.emit(父tuple,newtuple)
    ②:当executor处理完业务逻辑的时候,需要告诉Storm框架当前阶段处理的状态,即collector.ack(tuple)或collector.fail(tuple)

1.1.3编写Storm程序时,在bolt环节忘记手动ack或fail,Storm框架会等待反馈,达到超时的阈值之后,直接给fail

1.1.4编写Storm程序时,在bolt环节忘记标识锚点,Storm框架会认为你不关心后面阶段处理状况

说明:Storm框架针对BaseRichBoltapi过于繁琐,Storm框架引入另外的apiBaseBasicBolt。如果实现了BaseBasicBolt,就不需要手动指定锚点、手动ackfail

1.2 Storm框架ack机制的原理分析

1.3 Acker机制总结

1、Spout发送一条消息,会由系统生成一个RootId;
2、由于用户在发送数据的时候,指定了MessageId,所以在发送数据的时候会创建PendingMap,PendingMap以RootId为key,以用户的MessageId为Value;
3、Spout将数据发送给Bolt的时候,不仅发送一份DataTuple消息给下游的Bolt,还发送一份AckTuple消息给AckerBolt。DataTuple和AckTuple的区别是AckTuple没有数据只有RootId和锚点Id;
4、Bolt处理完业务逻辑之后,发送一份ack信号,将ack信号转成AckTuple。

对于Storm,它有一个很重要的特性:“Guarantee no data loss” ——可靠性

很显然,要做到这个特性,必须要track每个data的去向和结果。Storm是如何做到的呢——acker机制。

先概括下acker所参与的工作流程:

1. Spout创建一个新的Tuple时,会发一个消息通知acker去跟踪;

2. Bolt在处理Tuple成功或失败后,也会发一个消息通知acker;

3. acker会找到发射该Tuple的Spout,回调其ack或fail方法。


我们说RichBolt和BasicBolt的区别是后者会自动ack。那么是不是我们只要实现了Spout的ack或fail方法就能看到反馈了呢?

试试在RandomSpout中加入如下代码:

[java]  view plain  copy
  1. @Override  
  2.     public void ack(Object msgId) {  
  3.         System.err.println("ack " + msgId);  
  4.     }  
  5.   
  6.     @Override  
  7.     public void fail(Object msgId) {  
  8.         System.err.println("fail " + msgId);  
  9.     }  

重新运行ExclaimBasicTopo,看下结果。并没有任何的ack 和 fail 出现?

原因是,Storm要求如果要track一个Tuple,必须要指定其messageId,也就是回调回ack和fail方法的参数。如果我们不指定,Storm是不会去track该tuple的,即不保证消息丢失!

我们改下Spout代码,为每个消息加入一个唯一Id。同时,为了方便看结果,加入更多的打印,并且靠sleep减慢发送速度。(只是为了演示!)

[java]  view plain  copy
  1. public class RandomSpout extends BaseRichSpout {  
  2.   
  3.     private SpoutOutputCollector collector;  
  4.   
  5.     private Random rand;  
  6.       
  7.     private AtomicInteger counter;  
  8.       
  9.     private static String[] sentences = new String[] {"edi:I'm happy""marry:I'm angry""john:I'm sad""ted:I'm excited""laden:I'm dangerous"};  
  10.       
  11.     @Override  
  12.     public void open(Map conf, TopologyContext context,  
  13.             SpoutOutputCollector collector) {  
  14.         this.collector = collector;  
  15.         this.rand = new Random();  
  16.         counter = new AtomicInteger();  
  17.     }  
  18.   
  19.     @Override  
  20.     public void nextTuple() {  
  21.         Utils.sleep(5000);  
  22.         String toSay = sentences[rand.nextInt(sentences.length)];  
  23.         int msgId = this.counter.getAndIncrement();  
  24.         toSay = "["+ msgId + "]"+ toSay;  
  25.         PrintHelper.print("Send " + toSay );  
  26.           
  27.         this.collector.emit(new Values(toSay), msgId);  
  28.     }  
  29.   
  30.     @Override  
  31.     public void declareOutputFields(OutputFieldsDeclarer declarer) {  
  32.         declarer.declare(new Fields("sentence"));  
  33.     }  
  34.       
  35.     @Override  
  36.     public void ack(Object msgId) {  
  37.         PrintHelper.print("ack " + msgId);  
  38.     }  
  39.   
  40.     @Override  
  41.     public void fail(Object msgId) {  
  42.         PrintHelper.print("fail " + msgId);  
  43.     }  
  44.   
  45. }  

PrintHelper类:

[java]  view plain  copy
  1. public class PrintHelper {  
  2.   
  3.     private static SimpleDateFormat sf = new SimpleDateFormat("mm:ss:SSS");  
  4.       
  5.     public static void print(String out){  
  6.         System.err.println(sf.format(new Date()) + " [" + Thread.currentThread().getName() + "] " + out);  
  7.     }  
  8.       
  9. }  

同时把PrintBolt里面打印也换成PrintHelper.print打印


看下打印结果:

[plain]  view plain  copy
  1. 53:33:891 [Thread-26-spout] Send [0]ted:I'm excited  
  2. 53:33:896 [Thread-20-print] Bolt[0] String recieved: [0]ted:I'm excited!  
  3. 53:38:895 [Thread-26-spout] Send [1]edi:I'm happy  
  4. 53:38:895 [Thread-22-print] Bolt[1] String recieved: [1]edi:I'm happy!  
  5. 53:38:895 [Thread-26-spout] ack 0  
  6. 53:43:896 [Thread-26-spout] Send [2]edi:I'm happy  
  7. 53:43:896 [Thread-22-print] Bolt[1] String recieved: [2]edi:I'm happy!  
  8. 53:43:896 [Thread-26-spout] ack 1  
  9. 53:48:896 [Thread-26-spout] Send [3]edi:I'm happy  
  10. 53:48:896 [Thread-26-spout] ack 2  
  11. 53:48:896 [Thread-24-print] Bolt[2] String recieved: [3]edi:I'm happy!  
  12. 53:53:896 [Thread-26-spout] Send [4]ted:I'm excited  
  13. 53:53:896 [Thread-26-spout] ack 3  
  14. 53:53:896 [Thread-20-print] Bolt[0] String recieved: [4]ted:I'm excited!  
  15. 53:58:897 [Thread-26-spout] Send [5]laden:I'm dangerous  
  16. 53:58:897 [Thread-26-spout] ack 4  
  17. 53:58:898 [Thread-24-print] Bolt[2] String recieved: [5]laden:I'm dangerous!  

很明显看到:

a. 并发度为1的Spout确实是一个线程,并发度为3的Bolt确实是三个线程;

b. 消息完全处理完成后,确实回调了ack(Object msgId)方法,而且msgId的值,即为我们emit的msgId;

c. 虽然我们在topology中定义了两个bolt,但实际上ack对于每个tuple只调用了一次;

d. spout发出tuple后,Bolt很快就完成了,但是ack直到5秒后spout醒来才打印。


Tuple树

对于Spout创建的Tuple,在topology定义的流水线中经过Bolt处理时,可能会产生一个或多个新的Tuple。源Tuple+新产生的Tuple构成了一个Tuple树。当整棵树被处理完成,才算一个Tuple被完全处理,其中任何一个节点的Tuple处理失败或超时,则整棵树失败。

超时的值,可以通过定义topology时,conf.setMessageTimeoutSecs方法指定。


Anchor

在我们例子中ExclaimRichBolt

collector.emit(inputTule, new Values(newTupleValue));

发射一个新的tuple。

第一个参数是传入Bolttuple,第二个参数是新产生的tuplevalue,这种emit的方式,在Storm中称为: "anchor"。


Tuple的ack

前面我们一直提到acker,看到这里,你应该能猜出acker其实就是Storm里面track一个Tuple保证其一定被处理的功能。acker也是一个component

我们来看看acker的工作流程:

1. Spout在初始化时会产生一个tasksId;

2. Spout中创建新的Tuple,其id是一个64位的随机数;

3. Spout将新建的Tuple发送出去(给出了messageId来开启Tuple的追踪), 同时会发送一个消息到某个acker,要求acker进行追踪。该消息包含两部分:

  • Spout的taskId:用户acker在整个tuple树被完全处理后找到原始的Spout进行回调ack或fail
  • 一个64位的ack val值: 标志该tuple是否被完全处理。初始值为0。

3. 一个Bolt在处理完Tuple后,如果发射了一个新的anchor tuple,Storm会维护anchor tuple的列表;

4. 该Bolt调用OutputCollector.ack()时,Storm会做如下操作:

  • anchor tuple列表中每个已经ack过的和新创建的Tuple的id做异或(XOR)。假定Spout发出的TupleID是tuple-id-0,该Bolt新生成的TupleID为tuple-id-1,那么,tuple-id-0XORtuple-id-0XOR tuple-id-1
  • Storm根据该原始TupleID进行一致性hash算法,找到最开始Spout发送的那个acker,然后把上面异或后得出的ack val值发送给acker

5. acker收到新的ack val值后,与保存的原始的Tuple的id进行异或,如果为0,表示该Tuple已被完全处理,则根据其taskId找到原始的Spout,回调其ack()方法。


fail的机制类似,在发现fail后直接回调Spout的fail方法。

Storm就是通过这个acker的机制来保证数据不丢失。


回头再看看上面的打印结果,b、c两条得到很好的解释了。那d是为什么呢?

在最开始时,我曾经提到过,Storm的设计模型中,Spout是源源不断的产生数据的,所以其nextTuple()方法在任何时候不应该被打断。ack,fail 和 nextTuple是在同一个线程中完成的。

所以,虽然acker发现一个Tuple已经完全处理完成,但是由于Spout线程在Sleep,无法回调。


在设计中,我们应尽量避免在Spout、Bolt中去Sleep。如果确实需要控制,最好用异步线程来做,例如用异步线程读取数据到队列,再由Spout去取队列中数据。异步线程可以随意控制速度等。


另外,

Storm是否会自动重发失败的Tuple?

这里答案已经很明显了。fail方法如何实现取决于你自己。只有在fail中做了重发机制,才有重发。

注:Trident除外。这是Storm提供的特殊的事务性API,它确实会帮你自动重发的。

Unanchor

如果我们在Bolt中用OutputCollector.emit()发射一个新的Tuple时,并没有指定输入的Tuple(IBasicBolt的实现类用的是BasicOutPutCollector,其emit方法实际上还是调用OutputCollector.emit(),只不过内部会帮你填上输入的Tuple),那么行为称之为“Unanchor”。

是否用Unanchor方式取决于你的实现。


调整可靠性

在某些特定的情况下,你或许想调整Storm的可靠性。例如,你并不关心数据是否丢失,或者你想看看后面是否有某个Bolt拖慢了Spout的速度?
那么,有三种方法可以实现:
1. 在build topology时,设置acker数目为0,即conf.setNumAckers(0);
2. 在Spout中,不指定messageId,使得Storm无法追踪;
3. 在Bolt中,使用Unanchor方式发射新的Tuple。


Storm Trident的核心数据模型是一批一批被处理的“流”,“流”在集群的分区在集群的节点上,对“流”的操作也是并行的在每个分区上进行。

Trident有五种对“流”的操作:

1.      不需要网络传输的本地批次运算

2.      需要网络传输的“重分布”操作,不改变数据的内容

3.      聚合操作,网络传输是该操作的一部分

4.      “流”分组(grouby)操作

5.      合并和关联操作

批次本地操作:

批次本地操作不需要网络传输,本格分区(partion)的运算是互相独立的

Functions(函数)

         函数接收一些字段并发射(emit)零个或更多“元组”。输出的字段附加在原始的元组上面。如果一个函数不发射数据,原始的数据被过滤。如果发射(emit)多个元组,原始的元组被重复的冗余到输出元组上。例如:

public class MyFunction extends BaseFunction {

    public void execute(TridentTuple tuple, TridentCollector collector) {

        for(int i=0; i < tuple.getInteger(0); i++) {

            collector.emit(new Values(i));

        }

    }

}

假设有一个叫“mystream”输入流有[“a”,“b”,“c“]三个字段

[1, 2, 3]
[4, 1, 6]
[3, 0, 8]

如果运行下面的代码:

mystream.each(new Fields("b"), new MyFunction(), new Fields("d")))

运行的结果将会有4个字段[“a”,“b”,“c”,“d”],如下:

[1, 2, 3, 0]
[1, 2, 3, 1]
[4, 1, 6, 0]

 

Filters(过滤)

Filters接收一个元组(tuple),决定是否需要继续保留这个元组。,比如:

public class MyFilter extends BaseFunction {
    public booleanisKeep(TridentTuple tuple) {
        return tuple.getInteger(0) == 1 && tuple.getInteger(1) == 2;
    }
}

假设有如下输入:

[1, 2, 3]
[2, 1, 1]
[2, 3, 4]

运行下面的代码:

mystream.each(new Fields("b","a"), new MyFilter())

结果将会如下:

[2, 1, 1]

 

分区汇聚

         分区汇总是在每个批次的分区上运行的函数,和函数不同的是,分区汇总发射(emit)的数据覆盖了原始的tuple。考虑下面的例子:

mystream.partitionAggregate(new Fields("b"), new Sum(), new Fields("sum"))

假设输入的“流”包含【“a”,“b”】两个字段,并且按照如下分区

Partition 0:
["a", 1]
["b", 2]
 
Partition 1:
["a", 3]
["c", 8]
 
Partition 2:
["e", 1]
["d", 9]
["d", 10

 

输出的“流”将只包含一个叫“sum”的字段:

Partition 0:
[3]
 
Partition 1:
[11]
 
Partition 2:
[20]

这里有定义了三种不同的聚合接口:CombinerAggreator,ReduceAggregator和Aggregate。

CombinerAggregator:

 public interface CombinerAggregator<T> extends Serializable {
    T init(TridentTuple tuple);
    T combine(T val1, T val2);
    T zero();
}

一个CombinerAggregator返回一个单独的元组,这个元组值有一个字段。CombinerAggregator在每个tuple上运行init,使用combine去联合结果直到只有一个tuple剩余。如果批次没有数据,运行zero函数。比如,下面实现了一个Count

public class Count implements CombinerAggregator<Long> {
    public Long init(TridentTuple tuple) {
        return 1L;
    }
 
    public Long combine(Long val1, Long val2) {
        return val1 + val2;
    }
 
    public Long zero() {
        return 0L;
    }
}

可以看到,CombinerAggregator的优势使用聚合函数代替分区聚合。在这种情况下,trident自动优化成,在网络传输前合一做局部的汇总。(类似于mapreduce的combine)。

         RducerAggregator接口如下:

public interface ReducerAggregator<T> extends Serializable {
    T init();
    T reduce(T curr, TridentTuple tuple);
}

         RducerAggregator在初始化的时候产生一个值,每个输入的元组在这个值的基础上进行迭代并输出一个单独的值,例如下面定义了一个Count的reduceAggegator:

public class Count implements ReducerAggregator<Long> {
    public Long init() {
        return 0L;
    }
    
    public Long reduce(Long curr, TridentTuple tuple) {
        return curr + 1;
    }
}

ReducerAggregator可以和persistentAggregate一起使用,后面会讲到。

 

         更加通用聚合接口是Aggregator,如下:

public interface Aggregator<T> extends Operation {
    T init(Object batchId, TridentCollector collector);
    voidaggregate(T state, TridentTuple tuple, TridentCollector collector);
    voidcomplete(T state, TridentCollector collector);
}

         Aggregator可以发射任何数量的输出tuple,这些tuple可以包含多个字段(fields)。可以在执行过程的任何点发射输出。聚合按照下面的方式执行:

1.      Init函数在执行批次操作之前被调用,并返回一个state对象,这个额对象将会会传入到aggregate和complete函数中。

2.      Aggregate会对批次中每个tuple调用,这个方法可以跟新state也可以发射(emit)tuple。

3.      当这个批次分区的数据执行结束后调用complete函数。

下面是一个使用Aggregate事项的Count

public class CountAgg extends BaseAggregator<CountState> {
    static class CountState {
        long count = 0;
    }
 
    public CountState init(Object batchId, TridentCollector collector) {
        return new CountState();
    }
 
    public voidaggregate(CountState state, TridentTuple tuple, TridentCollector collector) {
        state.count+=1;
    }
 
    public voidcomplete(CountState state, TridentCollector collector) {
        collector.emit(new Values(state.count));
    }
}

如果想同事执行多个聚合,可以使用如下的调用链

mystream.chainedAgg()
        .partitionAggregate(new Count(), new Fields("count"))
        .partitionAggregate(new Fields("b"), new Sum(), new Fields("sum"))
        .chainEnd()

这个代码将会在每个分区上执行count和sum聚合。输出将包含【“count”,“sum”】字段。

 

StateQuery和partitionPersist

         stateQuery和partitionPersistent查询和跟新状态。可以参考trident state doc。https://github.com/nathanmarz/storm/wiki/Trident-state
投影(projection)

投影操作是对数据上进行列裁剪,如果你有一个流有【“a”,“b”,“c”,“d”】四个字段,执行下面的代码:

mystream.project(new Fields("b","d"))

输出流将只有【“b”,“d”】两个字段。

 

重分区(repartition)操作

         重分区操作是通过一个函数改变元组(tuple)在task之间的分布。也可以调整分区数量(比如,如果并发的hint在repartition之后变大)重分区(repatition)需要网络传输。,线面是重分区函数:

1.      Shuffle:使用随机算法在目标分区中选一个分区发送数据

2.      Broadcast:每个元组重复的发送到所有的目标分区。这个在DRPC中很有用。如果你想做在每个分区上做一个statequery。

3.      paritionBy:根据一系列分发字段(fields)做一个语义的分区。通过对这些字段取hash值并对目标分区数取模获取目标分区。paritionBy保证相同的分发字段(fields)分发到相同的目标分区。

4.      global:所有的tuple分发到相同的分区。这个分区所有的批次相同。

5.      batchGobal:本批次的所有tuple发送到相同的分区,不通批次可以在不通的分区。

6.      patition:这个函数接受用户自定义的分区函数。用户自定义函数事项 backtype.storm.grouping.CustomStreamGrouping接口。

 

聚合操作

         Trident有aggregate和persistentAggregate函数对流做聚合。Aggregate在每个批次上独立运行,persistentAggregate聚合流的所有的批次并将结果存储下来。

         在一个流上做全局的聚合,可以使用reducecerAggregator或者aggretator,这个流先被分成一个分区,然后聚合函数在这个分区上运行。如果使用CombinerAggreator,Trident贤惠在每个分区上做一个局部的汇总,然后重分区冲为一个分区,在网络传输结束后完成聚合。CombinerAggreator非常有效,在尽可能的情况下多使用。

 

下面是一个做批次内聚合的例子:

mystream.aggregate(new Count(), new Fields("count"))

和partitionAggregate一样,聚合的aggregate也可以串联。如果将CombinerAggreator和非CombinerAggreator串联,trident就不能做局部汇总的优化。

 

流分组操作

         GroupBy操作根据特殊的字段对流进行重分区,分组字段相同的元组(tuple)被分到同一个分区,下面是个GroupBy的例子:

 

如果对分组的流进行聚合,聚会会对每个组聚合而不是这个批次聚合。(和关系型数据库的groupby相同)。PersistentAggregate也可以在分组的流哈桑运行,这种情况下结果将会存储在MapState里面,key是分组字段。可以查看https://github.com/nathanmarz/storm/wiki/Trident-state

和普通聚合一样,分组流的聚合也可以串联。

 

合并和关联

         最后一部分API是将不通的流合并,最简单的方式就是合并(meger)多个流成为一个流。可以使用tridentTopology#meger,如下:

topology.merge(stream1, stream2, stream3);

Trident合并的流字段会一第一个流的字段命名。

         另一个中合并流的方法是join。类似SQL的join都是对固定输入的。而流的输入是不固定的,所以不能按照sql的方法做join。Trident中的join只会在spout发出的每个批次见进行。

 

下面是个join的例子,一个流包含字段【“key”,“val1”,“val2”】,另一个流包含字段【“x”,“val1”】:

topology.join(stream1, new Fields("key"), stream2, new Fields("x"), new Fields("key","a","b","c"));

Stream1的“key”和stream2的“x”关联。Trident要求所有的字段要被名字,因为原来的名字将会会覆盖。Join的输入会包含:

1.      首先是join字段。例子中stream1中的“key”对应stream2中的“x”。

2.      接下来,会吧非join字段依次列出来,排列顺序按照传给join的顺序。例子中“a”,“b”对应stream1中的“val1”和“wal2”,“c”对应stream2中的“val1”。

当join的流分别来自不通的spout,这些spout会同步发射的批次,也就是说,批次处理会包含每个spout发射的tuple。

         有人可能会问怎么做“windowedjoin”,join的一边和另一边最近一个小时的数据做join运算。

         为了实现这个,你可以使用patitionPersist和stateQuery。最近一个小时的数据可以按照join字段做key存储下改变,在join的过程中可以查询存储的额数据完成join操作。


    FixedBatchSpout spout=new FixedBatchSpout(new Fields("sentence"), 3,

              new Values("The cow jumped over the moon"),

                new Values("The man went to the store and bought some candy"),

                new Values("Four score and seven years ago"),

                new Values("How many apples can you eat"),

                new Values("To be or not to be the person"));

       spout.setCycle(true);

或者:

    BrokerHosts brokerHosts =  new ZkHosts(ConfigFactory.getConfigProps().getString(ConfigProps.KEY_ZOOKEEPER_KAFKA));

    TridentKafkaConfig kafkaConfig = new TridentKafkaConfig(brokerHosts, ConfigProps.TOPIC_USER"pv"); 

    TransactionalTridentKafkaSpout kafkaSpout = new TransactionalTridentKafkaSpout(kafkaConfig);



       TridentTopology topology=new TridentTopology();

       TridentState tridentState = topology.newStream("spout1", spout)

           .parallelismHint(16)

           .each(new Fields("sentence"), new Split(), new Fields("item"))

           .each(new Fields("item"), new LowerCase(), new Fields("word"))

           //输入item  lowcase 操作   输出word

           .groupBy(new Fields("word"))//根据上一步输出word聚合

           .persistentAggregate(new MemoryMapState.Factory(), new Count(), new Fields("count"))

           //聚合并且持久化

           .parallelismHint(6);

       topology.newDRPCStream("words", localDRPC)

           .each(new Fields("args"), new Split(), new Fields("word"))

           .groupBy(new Fields("word"))

           .stateQuery(tridentState, new Fields("word"), new MapGet(), new Fields("count"))

           //tridentState的输出结果作为输入源

           .each(new Fields("count"), new FilterNull())

           .aggregate(new Fields("count"), new Sum(), new Fields("sum"));

       return topology.build();



新建storm工程

这里,推荐用新建Maven工程,多好!

当然,为了照顾初学者,手工添加导入依赖包。

 

同时,各位来观看我本博客的博友们,其实,在生产是一定要是Maven的啊!何止能出书的人。

 

 

 

weekend110-storm    ->     Build Path  ->   Configure Build Path

 

D:\SoftWare\apache-storm-0.9.2-incubating\lib 

 

D:\SoftWare\apache-storm-0.9.2-incubating\external\storm-kafka

这个很重要,一般storm和kafka,做整合,是必须要借助用到这个jar包的。

 

 

新建包cn.itcast.stormdemo 

 

新建类RandomWordSpout.java

 

新建类UpperBolt.java

 

新建类 SuffixBolt.java

 

新建类 TopoMain.java

 

编写代码

RandomWordSpout.java

package cn.itcast.stormdemo;

 

import java.util.Map;

import java.util.Random;

 

import backtype.storm.spout.SpoutOutputCollector;

import backtype.storm.task.TopologyContext;

import backtype.storm.topology.OutputFieldsDeclarer;

import backtype.storm.topology.base.BaseRichSpout;

import backtype.storm.tuple.Fields;

import backtype.storm.tuple.Values;

import backtype.storm.utils.Utils;

 

public class RandomWordSpout extends BaseRichSpout{

 

       private SpoutOutputCollector collector;

      

       //模拟一些数据

       String[] words = {"iphone","xiaomi","mate","sony","sumsung","moto","meizu"};

      

       //不断地往下一个组件发送tuple消息

       //这里面是该spout组件的核心逻辑

       @Override

       public void nextTuple() {

 

              //可以从kafka消息队列中拿到数据,简便起见,我们从words数组中随机挑选一个商品名发送出去

              Random random = new Random();

              int index = random.nextInt(words.length);

             

              //通过随机数拿到一个商品名

              String godName = words[index];

             

              //将商品名封装成tuple,发送消息给下一个组件

              collector.emit(new Values(godName));

             

              //每发送一个消息,休眠500ms

              Utils.sleep(500);

            }

 

       //初始化方法,在spout组件实例化时调用一次

       @Override

       public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {

 

              this.collector = collector;             

       }

       //声明本spout组件发送出去的tuple中的数据的字段名

       @Override

       public void declareOutputFields(OutputFieldsDeclarer declarer) {

              declarer.declare(new Fields("orignname"));         

       }

 

}

 

UpperBolt.java

package cn.itcast.stormdemo;

 

import backtype.storm.topology.BasicOutputCollector;

import backtype.storm.topology.OutputFieldsDeclarer;

import backtype.storm.topology.base.BaseBasicBolt;

import backtype.storm.tuple.Fields;

import backtype.storm.tuple.Tuple;

import backtype.storm.tuple.Values;

 

public class UpperBolt extends BaseBasicBolt{

   

       //业务处理逻辑

       @Override

       public void execute(Tuple tuple, BasicOutputCollector collector) {

             

              //先获取到上一个组件传递过来的数据,数据在tuple里面

              String godName = tuple.getString(0);

             

              //将商品名转换成大写

              String godName_upper = godName.toUpperCase();

             

              //将转换完成的商品名发送出去

              collector.emit(new Values(godName_upper));

             

       }

  

       //声明该bolt组件要发出去的tuple的字段

       @Override

       public void declareOutputFields(OutputFieldsDeclarer declarer) {

             

              declarer.declare(new Fields("uppername"));

       }

 

}


SuffixBolt.java

package cn.itcast.stormdemo;

 

import java.io.FileWriter;

import java.io.IOException;

import java.util.Map;

import java.util.UUID;

 

import backtype.storm.task.TopologyContext;

import backtype.storm.topology.BasicOutputCollector;

import backtype.storm.topology.OutputFieldsDeclarer;

import backtype.storm.topology.base.BaseBasicBolt;

import backtype.storm.tuple.Tuple;

 

public class SuffixBolt extends BaseBasicBolt{

      

       FileWriter fileWriter = null;

           

       //在bolt组件运行过程中只会被调用一次

       @Override

       public void prepare(Map stormConf, TopologyContext context) {

 

              try {

                     fileWriter = new FileWriter("/home/hadoop/stormoutput/"+UUID.randomUUID());

              } catch (IOException e) {

                     throw new RuntimeException(e);

              }        

       }

      

            

       //该bolt组件的核心处理逻辑

       //每收到一个tuple消息,就会被调用一次

       @Override

       public void execute(Tuple tuple, BasicOutputCollector collector) {

 

              //先拿到上一个组件发送过来的商品名称

              String upper_name = tuple.getString(0);

              String suffix_name = upper_name + "_itisok";

                          

              //为上一个组件发送过来的商品名称添加后缀

             

              try {

                     fileWriter.write(suffix_name);

                     fileWriter.write("\n");

                     fileWriter.flush();

                    

              } catch (IOException e) {

                     throw new RuntimeException(e);

              }

                                   

       }

  

       //本bolt已经不需要发送tuple消息到下一个组件,所以不需要再声明tuple的字段

       @Override

       public void declareOutputFields(OutputFieldsDeclarer arg0) {         

       }

 

}

 

TopoMain.java

package cn.itcast.stormdemo;

 

import backtype.storm.Config;

import backtype.storm.StormSubmitter;

import backtype.storm.generated.AlreadyAliveException;

import backtype.storm.generated.InvalidTopologyException;

import backtype.storm.generated.StormTopology;

import backtype.storm.topology.TopologyBuilder;

 

/**

 * 组织各个处理组件形成一个完整的处理流程,就是所谓的topology(类似于mapreduce程序中的job)

 * 并且将该topology提交给storm集群去运行,topology提交到集群后就将永无休止地运行,除非人为或者异常退出

 *

 *

 */

public class TopoMain {

 

      

       public static void main(String[] args) throws Exception {

             

              TopologyBuilder builder = new TopologyBuilder();

             

              //将我们的spout组件设置到topology中去

              //parallelism_hint :4  表示用4个excutor来执行这个组件

              //setNumTasks(8) 设置的是该组件执行时的并发task数量,也就意味着1个excutor会运行2个task

              builder.setSpout("randomspout", new RandomWordSpout(), 4).setNumTasks(8);

             

              //将大写转换bolt组件设置到topology,并且指定它接收randomspout组件的消息

              //.shuffleGrouping("randomspout")包含两层含义:

              //1、upperbolt组件接收的tuple消息一定来自于randomspout组件

              //2、randomspout组件和upperbolt组件的大量并发task实例之间收发消息时采用的分组策略是随机分组shuffleGrouping

              builder.setBolt("upperbolt", new UpperBolt(), 4).shuffleGrouping("randomspout");

             

              //将添加后缀的bolt组件设置到topology,并且指定它接收upperbolt组件的消息

              builder.setBolt("suffixbolt", new SuffixBolt(), 4).shuffleGrouping("upperbolt");

             

              //用builder来创建一个topology

              StormTopology demotop = builder.createTopology();

             

              //配置一些topology在集群中运行时的参数

              Config conf = new Config();

              //这里设置的是整个demotop所占用的槽位数,也就是worker的数量

              conf.setNumWorkers(4);

              conf.setDebug(true);

              conf.setNumAckers(0);

             

              //将这个topology提交给storm集群运行

              StormSubmitter.submitTopology("demotopo", conf, demotop);

             

       }

}

 

补充:

 

http://www.cnblogs.com/vincent-vg/p/5850852.html 

 





自定义分组:


分到最大的taskID,

hashmap不是线程安全的,把他装饰成线程安全的



通过config设置超时时间:



问题描述:
  在java语言中,map的clear方法会把整个map清空吗?
问题解答:
  答案是肯定的
测试代码:
    HashMap hashMap = new HashMap();
    hashMap.put("1", "mqboss");
    hashMap.put("2", "Jboss");
    System.out.println("hashMap: " + hashMap);
    hashMap.clear();
    System.out.println("hashMap: " + hashMap);
测试输出:
  hashMap: {2=Jboss, 1=mqboss}

  hashMap: {}




示例一:数据累加(storm本地模式)

/**

 * 数据累加

 * @author Administrator

 *

 */

public class LocalStormTopology1 {

    private static Logger logger =Logger.getLogger(LocalStormTopology1.class);

   

    /**

     * spout

     * @author Administrator

     *

     */

    public static classDataSourceSpout extends BaseRichSpout{

 

        private Map conf;

        private TopologyContextcontext;

        private SpoutOutputCollectorcollector;

       

        /**

         * 初始化方法,本实例运行时被调用一次,只能执行一次

         * @param conf 配置参数,可获得Topology的配置参数

         * @param context 上下文(一般用不到)

         * @param collector 发射器(向下一步发射数据,spout生成的数据让bolt接收到)

         */

        public void open(Map conf,TopologyContext context, SpoutOutputCollectorcollector) {

            this.conf = conf;

            this.context = context;

            this.collector =collector;

        }

       

        int i=0;

        /**

         * 死循环的调用,心跳,经过这个方法产生数据,并发射出去

         */

        public void nextTuple() {

            logger.info("spout="+i);

            this.collector.emit(newValues(i++));

//          this.collector.emit(newValues(i++,i+2));

            try {

                Thread.sleep(1000);

            } catch(InterruptedException e) {

                e.printStackTrace();

            }

        }

 

        /**

         * 声明输出的内容(让后面的bolt知道spout输出的是什么内容)

         */

        public voiddeclareOutputFields(OutputFieldsDeclarer declarer) {

            declarer.declare(newFields("num")); //输出Fields内的个数需与发射出去的字段个数一致(通过字段名来获取字段的内容)

//          declarer.declare(newFields("num1","num2"));

        }

       

    }

   

    /**

     * bolt

     * @author Administrator

     */

    public static class SumBoltextends BaseRichBolt{

 

        private Map stormConf;

        private TopologyContext context;

        private OutputCollectorcollector;

       

        /**

         * 被调用一次,进行初始化

         * @param stormConf 配置参数,可获得Topology的配置参数

         * @param context 上下文(一般用不到)

         * @param collector 发射器

         */

        public void prepare(MapstormConf, TopologyContext context, OutputCollectorcollector) {

            this.stormConf =stormConf;

            this.context = context;

            this.collector =collector;

        }

 

        int sum=0;

        /**

         * 死循环,读取数据

         */

        public void execute(Tupleinput) {

//          input.getInteger(0);//由于Tuple内封装着List,getInteger(0);取list中的第一个元素,如果spout发送了多个字段,则getInteger(i);内的i是取第几个字段

            Integer value =input.getIntegerByField("num"); //通过字段名来获取数据

            sum+=value;

            logger.info("sum="+sum);

           

//          this.collector.emit(newValues("test"));

        }

 

        /**

         * 声明输出的内容(让后面的bolt知道这个bolt输出的是什么内容),

         * 如果execute方法发射数据,declareOutputFields方法得声明字段名称

         * 如果这是最后一个bolt就不用再向后发射数据了,因此就不用再declareOutputFields方法内声明字段名称

         */

        public voiddeclareOutputFields(OutputFieldsDeclarer declarer) {

//          declarer.declare(newFields("testName"));

        }

       

    }

   

    /**

     * 通过topology将spout和bolt组装起来

     * @param args

     */

    public static void main(String[]args) {

        TopologyBuildertopologyBuilder = new TopologyBuilder();

        topologyBuilder.setSpout("spout_id",new DataSourceSpout());

        topologyBuilder.setBolt("bolt_id",newSumBolt()).shuffleGrouping("spout_id");//shuffleGrouping("spout_id") 指定上一个步骤

       

        //设置storm在本地运行

        LocalCluster localCluster =new LocalCluster();

        localCluster.submitTopology("topology",new Config(), topologyBuilder.createTopology());

    }

   

}

 

示例二:单词记数(storm本地模式)

pom.xml内增加

    <dependency>

        <groupId>commons-io</groupId>

        <artifactId>commons-io</artifactId>

        <version>2.4</version>

    </dependency>

 

import java.io.File;

import java.util.Collection;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

 

import org.apache.commons.io.FileUtils;

import org.apache.log4j.Logger;

 

import backtype.storm.Config;

import backtype.storm.LocalCluster;

import backtype.storm.spout.SpoutOutputCollector;

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.topology.base.BaseRichSpout;

import backtype.storm.tuple.Fields;

import backtype.storm.tuple.Tuple;

import backtype.storm.tuple.Values;

 

/**

 * 单词计数

 * @author Administrator

 *

 */

public class LocalStormTopology2 {

    private static Logger logger = Logger.getLogger(LocalStormTopology2.class);

   

    /**

     * spout

     * @author Administrator

     *

     */

    public static class DataSourceSpout extends BaseRichSpout{

 

        private Map conf;

        private TopologyContext context;

        private SpoutOutputCollector collector;

       

        /**

         * 初始化方法,本实例运行时被调用一次,只能执行一次

         * @param conf 配置参数,可获得Topology的配置参数

         * @param context 上下文(一般用不到)

         * @param collector 发射器(向下一步发射数据,spout生成的数据让bolt接收到)

         */

        public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {

            this.conf = conf;

            this.context = context;

            this.collector = collector;

        }

       

        /**

         * 死循环的调用,心跳,经过这个方法产生数据,并发射出去

         */

        public void nextTuple() {

            try {

                //读取指定目录下所有文件

                Collection<File> files = FileUtils.listFiles(new File("/opt/workspace/testdata"),new String[] { "txt"}, true); //参数1:目录位置  参数2:过滤文件的后缀 参数3:是否递归读目录

                for (File file : files) {

                    //获取每个文件的所有数据

                    List<String> lines = FileUtils.readLines(file);

                    //把每一行数据发射出去

                    for (String line : lines){

                        this.collector.emit(new Values(line));

                    }

                    //修改读完的文件名,防止文件被重复读取

                    FileUtils.moveFile(file, new File(file.getAbsolutePath()+System.currentTimeMillis()));

                }

            } catch (Exception e) {

                e.printStackTrace();

                logger.error("程序执行错误:"+e.getMessage());

            }

           

        }

 

        /**

         * 声明输出的内容(让后面的bolt知道spout输出的是什么内容)

         */

        public void declareOutputFields(OutputFieldsDeclarer declarer) {

            declarer.declare(new Fields("line")); //输出Fields内的个数需与发射出去的字段个数一致(通过字段名来获取字段的内容)

        }

       

    }

   

    /**

     * bolt1

     * @author Administrator

     */

    public static class SplitBolt extends BaseRichBolt{

 

        private Map stormConf;

        private TopologyContext context;

        private OutputCollector collector;

       

        /**

         * 被调用一次,进行初始化

         * @param stormConf 配置参数,可获得Topology的配置参数

         * @param context 上下文(一般用不到)

         * @param collector 发射器

         */

        public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {

            this.stormConf = stormConf;

            this.context = context;

            this.collector = collector;

        }

 

        /**

         * 死循环,读取数据

         */

        public void execute(Tuple input) {

            try {

                //获取每一行数据

                String line = input.getStringByField("line");

                //据数据切分成一个个单词

                String[] words = line.split(" ");

                for (String word : words){

                    //所每个单词都发射出去

                    this.collector.emit(new Values(word));

                }

            } catch (Exception e) {

                e.printStackTrace();

                logger.error("程序执行错误:"+e.getMessage());

            }

        }

 

        /**

         * 声明输出的内容(让后面的bolt知道这个bolt输出的是什么内容),

         * 如果execute方法发射数据,declareOutputFields方法得声明字段名称

         * 如果这是最后一个bolt就不用再向后发射数据了,因此就不用再declareOutputFields方法内声明字段名称

         */

        public void declareOutputFields(OutputFieldsDeclarer declarer) {

            declarer.declare(new Fields("words"));

        }

       

    }

   

    /**

     * bolt2

     * @author Administrator

     */

    public static class CountBolt extends BaseRichBolt{

 

        private Map stormConf;

        private TopologyContext context;

        private OutputCollector collector;

       

        /**

         * 被调用一次,进行初始化

         * @param stormConf 配置参数,可获得Topology的配置参数

         * @param context 上下文(一般用不到)

         * @param collector 发射器

         */

        public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {

            this.stormConf = stormConf;

            this.context = context;

            this.collector = collector;

        }

 

        HashMap<String,Integer> hashMap = new HashMap<String,Integer>();

        /**

         * 死循环,读取数据

         */

        public void execute(Tuple input) {

            //获取每一个单词

            String word = input.getStringByField("words");

            //对所有单词汇总

            Integer num = hashMap.get(word);

            if (num == null) {

                num=0;

            }

            num++;

            hashMap.put(word, num);

 

            //把结果打印出来

            System.out.println("===========================================================");

            for (Map.Entry<String, Integer> entry : hashMap.entrySet()){

                logger.info("word="+entry.getKey()+",  number="+entry.getValue());

            }

        }

 

        /**

         * 声明输出的内容(让后面的bolt知道这个bolt输出的是什么内容),

         * 如果execute方法发射数据,declareOutputFields方法得声明字段名称

         * 如果这是最后一个bolt就不用再向后发射数据了,因此就不用再declareOutputFields方法内声明字段名称

         */

        public void declareOutputFields(OutputFieldsDeclarer declarer) {

        }

       

    }

   

    /**

     * 通过topology将spout和bolt组装起来

     * @param args

     */

    public static void main(String[] args) {

        TopologyBuilder topologyBuilder = new TopologyBuilder();

        topologyBuilder.setSpout("spout_id", new DataSourceSpout());

        topologyBuilder.setBolt("bolt_id1",new SplitBolt()).shuffleGrouping("spout_id"); //shuffleGrouping("spout_id") 指定上一个步骤

        topologyBuilder.setBolt("bolt_id2",new CountBolt()).shuffleGrouping("bolt_id1");

       

        //设置storm在本地运行

        LocalCluster localCluster = new LocalCluster();

        localCluster.submitTopology("topology", new Config(), topologyBuilder.createTopology());

    }

   

}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值