转发请注明出处:http://blog.youkuaiyun.com/qq_28945021/article/details/52912142
主体
刚开始学习Storm发现这个流式处理框架还是比较容易理解与使用的。也许是我刚开始学习吧。这篇博客可能没有太多干货,只是记录下来供自己以后翻看。
整个Storm程序可分为几个部分:
- spout:作为Storm的开始模块。
- bolt:作为每一个任务的处理模块。
spout
作为Storm的开始,spout负责读取数据并不断地给bolt以供处理。因此spout的两个核心方法便是:open()——用于读取数据并放入集合(SpoutOutputCollector)中以供传输。nextTuple()——用于数据预处理及传输到bolt。
其中open方法的签名为:
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector)
conf是Storm的配置文件,里面配置了数据源等信息。想WordCount的例子中就配置了word文件的路径。可在open方法中读取出并读取数据。
可从TopologyContext 获取taskNum,taskId,任务Id等等一系列参数,以此可以做的事就很多了——包括分配不同bolt,包括监督任务数等等。
collector是一个数据传输管道,它能让我们发布交给bolts处理的数据。
这段代码是WordCount例子中的一个简单spout:
/**
* 我们将创建一个文件并维持一个collector对象
* 第一个被调用的spouts方法
* @param conf:在定义topology对象时创建
* @param TopologyContext:包含所有拓扑数据
* @param SpoutOutputCollector:它能让我们发布交给bolts处理的数据。
*/
@Override
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
try {
this.context=context;
this.fileReader = new FileReader(conf.get("wordsFile").toString()); //从conf中读取出wordsFile(文件位置)
} catch (FileNotFoundException e) {
throw new RuntimeException("Error reading file ["+conf.get("wordFile")+"]");
}
this.collector=collector;
}
nextTuple的方法签名为
public void nextTuple()
spouts会不断调用这个方法直到传输完或者我们通过代码控制停止时停止。值得注意的是,spout停止并不代表着Storm停止运行。仅仅代表不再有新的数据传入程序而已。我的小例子中使用了一个静态的flag来控制nextTuple的停止。
/**
* 这个方法用来分发文件中的文本行
*/
@Override
public void nextTuple() {
/**
* 这个方法会不断被调用直到文件被读取完,我们等待并返回
*/
if(completed){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//什么也不做
}
return;
}
String str;
//创建Reader
BufferedReader reader = new BufferedReader(fileReader);
//读所有文本行
try {
while((str=reader.readLine())!=null){
//按行发布一条消息
this.collector.emit(new Values(str),str);
}
} catch (IOException e) {
throw new RuntimeException("Error reading tuple",e);
}finally{
completed=true; //这里控制要么发完要么出错停止调用
}
}
spout还有三个十分重要的方法:ack,fail,declareOutputFields
ack方法代表之后的bolt运行成功,fail代表失败。这是通过锚记得到的,因此bolt中必须调用这两个方法,否则无法监督程序运行状态。在这两个方法中也能进行操作。例如:在fail方法中将失败方法重新计算。
declareOutputFields配置由该spout传出的数据将传到哪个域,默认default域,如果配置了域则只会传到该域bolt中否则为随机配置以达到每个bolt数据均衡。
bolt
作为Storm的计算模块,bolt模块含有三个主要方法:prepare——初始化方法,签名部分与spout的open一致,由此就知道了,Storm支持在运行中添加新的输入数据。execute(Tuple input)——核心计算方法。其中Tuple是Storm对集合的一个重写容器类。也是从spout或其他bolt传来的数据。由于perpare中含有collector,bolt不仅可以用来进行计算后输出,也可传给下一个bolt。cleanup()——bolt运行结束后运行的方法,通常最后一个bolt才使用。可将数据放入接下来的消息队列也好,持久化数据也好。
prepare:与spout的open方法大致相同,不赘述。
execute(Tuple input):正如上文所说,一定要调用ack方法,而fail方法会在失败后自动调用。
/**
* 最主要!
* 这里处理从spouts或其他地方传入的Tuple
*/
@Override
public void execute(Tuple input) {
String sentence = input.getString(0); //因为我们传的Tuple只有一个元素:line
String words[] = sentence.split(" ");
for(String word:words){
word=word.trim();
if(!word.isEmpty()){
word=word.toLowerCase();
//发布这个单词
collector.emit(new Values("word"));
}
}
//对元组做出应答
collector.ack(input);
}
cleanup:这里只是简单的缓存到内存
/**
* 这个spout结束时(集群关闭的时候),我们会显示单词数量(执行cleanup方法)
*/
@Override
public void cleanup() {
System.out.println("-- 单词数 【"+name+"-"+id+"】 --");
for(Map.Entry<String,Integer> entry : counters.entrySet()){
System.out.println(entry.getKey()+": "+entry.getValue());
}
}
同样的,bolt也含有declareOutputFields方法用于指定该bolt之后要传到的域。
知道这些,简单的Storm操作就能够满足了。接下来的进阶——包括使用消息队列中间件,自定义DRPC,看看之后有时间再来几篇博客吧。
以下是所有代码:(Main类的注释详细,可以看看)
package test;
import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.topology.TopologyBuilder;
import bolts.WordCounter;
import bolts.WordNormalizer;
import grouping.ModuleGrouping;
import spouts.WordReader;
public class TopologyMain {
public static void main(String[] args) throws InterruptedException {
//1.TopologyBuilder将用来创建拓扑,它决定Storm如何安排各节点,以及它们交换数据的方式。
//在spout和bolts之间通过shuffleGrouping方法连接。
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("word-reader", new WordReader());
builder.setBolt("word-normalizer", new WordNormalizer())
.customGrouping("word-reader", new ModuleGrouping())
.shuffleGrouping("word-reader");
builder.setBolt("word-counter", new WordCounter()).shuffleGrouping("word-normalizer");
//2.下一步,创建一个包含拓扑配置的Config对象,它会在运行时与集群配置合并,并通过prepare方法发送给所有节点。
Config conf=new Config();
conf.put("wordsFile", "D:\\software\\大数据\\Strom\\testStrom1.txt");
conf.setDebug(true); //由于是在开发阶段,设置debug属性为true,Strom会打印节点间交换的所有消息,以及其它有助于理解拓扑运行方式的调试数据。
//3.要用一个LocalCluster对象运行这个拓扑。在生产环境中,拓扑会持续运行,不过对于这个例子而言,你只要运行它几秒钟就能看到结果。
conf.setMaxTaskParallelism(1);
LocalCluster cluster =new LocalCluster();
cluster.submitTopology("Getting-Started-Topologie",conf,builder.createTopology());
Thread.sleep(2000);
cluster.shutdown();
}
}
package spouts;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Map;
import backtype.storm.spout.SpoutOutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.IRichSpout;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Values;
public class WordReader implements IRichSpout{
private SpoutOutputCollector collector;
private FileReader fileReader;
private boolean completed = false;
private TopologyContext context;
public boolean isDistributed() {return false;}
@Override
public void ack(Object msgId) {
System.out.println("OK"+msgId);
}
@Override
public void close() {}
@Override
public void fail(Object msgId) {
System.out.println("Fail"+msgId);
}
/**
* 这个方法用来分发文件中的文本行
*/
@Override
public void nextTuple() {
/**
* 这个方法会不断被调用直到文件被读取完,我们等待并返回
*/
if(completed){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//什么也不做
}
return;
}
String str;
//创建Reader
BufferedReader reader = new BufferedReader(fileReader);
//读所有文本行
try {
while((str=reader.readLine())!=null){
//按行发布一条消息
this.collector.emit(new Values(str),str);
}
} catch (IOException e) {
throw new RuntimeException("Error reading tuple",e);
}finally{
completed=true; //这里控制要么发完要么出错停止调用
}
}
/**
* 我们将创建一个文件并维持一个collector对象
* 第一个被调用的spouts方法
* @param conf:在定义topology对象时创建
* @param TopologyContext:包含所有拓扑数据
* @param SpoutOutputCollector:它能让我们发布交给bolts处理的数据。
*/
@Override
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
try {
this.context=context;
this.fileReader = new FileReader(conf.get("wordsFile").toString()); //从conf中读取出wordsFile(文件位置)
} catch (FileNotFoundException e) {
throw new RuntimeException("Error reading file ["+conf.get("wordFile")+"]");
}
this.collector=collector;
}
/**
* 声明输入域"word"
* @param declarer
*/
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("line"));
}
@Override
public void activate() {
}
@Override
public void deactivate() {
}
@Override
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
package bolts;
import java.util.Map;
import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.IRichBolt;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;
/**
* 第一个bolt,WordNormalizer,负责得到并标准化每行文本。它把文本行切分成单词,大写转化成小写,去掉头尾空白符。
* @author wrm
*
*/
public class WordNormalizer implements IRichBolt{
private OutputCollector collector;
/**
* 声明bolt出参
* 这里我们声明bolt将发布一个名为“word”的域
*/
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}
/**
* 最主要!
* 这里处理从spouts或其他地方传入的Tuple
*/
@Override
public void execute(Tuple input) {
String sentence = input.getString(0); //因为我们传的Tuple只有一个元素:line
String words[] = sentence.split(" ");
for(String word:words){
word=word.trim();
if(!word.isEmpty()){
word=word.toLowerCase();
//发布这个单词
collector.emit(new Values("word"));
}
}
//对元组做出应答
collector.ack(input);
}
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
this.collector=collector;
}
@Override
public void cleanup() {
}
@Override
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
package bolts;
import java.util.HashMap;
import java.util.Map;
import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.IRichBolt;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.tuple.Tuple;
/**
* 这个例子的bolt什么也没发布,它把数据保存在map里,但是在真实的场景中可以把数据保存到数据库。
* @author wrm
*
*/
public class WordCounter implements IRichBolt{
Integer id;
String name;
Map<String,Integer> counters;
private OutputCollector collector;
//初始化方法
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
this.counters = new HashMap<String, Integer>();
this.collector = collector;
this.name = context.getThisComponentId();
this.id = context.getThisTaskId();
}
/**
* 为每个单词计数
*/
@Override
public void execute(Tuple input) {
String str = input.getString(0);
//如果单词不存在于Map,我们就从新创建一个
if(!counters.containsKey(str)){
counters.put(str, 1);
}else{
Integer c = counters.remove(str)+1;
counters.put(str, c);
}
//对元组进行应答
collector.ack(input);
}
/**
* 这个spout结束时(集群关闭的时候),我们会显示单词数量(执行cleanup方法)
*/
@Override
public void cleanup() {
System.out.println("-- 单词数 【"+name+"-"+id+"】 --");
for(Map.Entry<String,Integer> entry : counters.entrySet()){
System.out.println(entry.getKey()+": "+entry.getValue());
}
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
}
@Override
public Map<String, Object> getComponentConfiguration() {
return null;
}
}