Storm是一个免费开源、分布式、高容错的实时计算系统。Storm令持续不断的流计算变得容易,弥补了Hadoop批处理所不能满足的实时要求。Storm经常用于在实时分析、在线机器学习、持续计算、分布式远程调用和ETL [ETL,是英文 Extract-Transform-Load 的缩写,用来描述将数据从来源端经过抽取(extract)、转换(transform)、加载(load)至目的端的过程。ETL一词较常用在数据仓库,但其对象并不限于数据仓库。]等领域。Storm的部署管理非常简单,而且,在同类的流式计算工具,Storm的性能也是非常出众的。
> storm的安装配置本文不介绍。涉及到ZooKeeper、Maven的安装配置等等。
storm框架的核心由7个部分组成,他们同时也是storm的基本组成部分。
开发环境与生产环境
拓扑的并行度(parallelism)
- 工作进程(worker process)
- 执行器 (Executor),即线程(thread)
- 任务(Task)
Storm集群的一个节点可能有一个或者多个工作进程运行在一个或者多个拓扑上,一个工作进程执行拓扑的一个子集。工作进程属于一个特定的拓扑,并可能为这个拓扑的一个或多个组件(spout or bolt)运行一个或多个执行器。一个运行中的拓扑包括多个运行在Storm集群内多个节点的进程。
一个或者多个执行器可能运行在一个工作进程内,执行器是由工作进程产生的一个线程,他可能为相同的组件(spout or bolt)运行一个或多个任务。
任务执行真正的数据处理,代码中实现的每个Spout或Bolt,作为很多任务跨集群执行。一个组件的任务数量始终贯穿拓扑的整个生命周期,但一个组件的执行器(线程)数量可以随时间而改变。这意味着,#threads<=#tasks。默认情况下,任务的数量被设定为相同的执行器的数量,即Strom会使用每个线程执行一个任务。 - 工作进程数量#setNumWorkers
- 执行器/线程的数量#setSpout()、setBolt()
- 任务的数量#setNumTasks()
流分组方式
- 随机分组(Shuffle Grouping)
- 字段分组(Fields Grouping)
- 广播分组(All Grouping)
- 全局分组(Global Grouping)
- 无分组
- 直接分组(Direct Grouping)
- 本地或随机分组
- 自定义分组(通过实现CustomStreamGrouping接口)
具体可以去其他地方搜
基本接口
- IComponent接口
- 所有组件的接口
- ISpout接口
- 是实现spout的核心接口
- IBolt接口
- IRichSpout与IRichBolt接口
- IBasicBolt接口
基本抽象类
- BaseComponent抽象类
- BaseRichSpout抽象类
- 继承BaseComponent并实现IRichSpout接口
- BaseRichBolt抽象类
- 继承BaseComponent并实现IRichBolt接口
- BaseBasicBolt抽象类
- 继承BaseComponent并实现IBasicBolt接口
Spout例子
public class WordReader extends BaseRichSpout {
private SpoutOutputCollector collector;
private FileReader fileReader;
private boolean completed = false;
public void ack(Object msgId) {
System.out.println("OK:"+msgId);
}
public void close() {}
public void fail(Object msgId) {
System.out.println("FAIL:"+msgId);
}
/**
* The only thing that the methods will do It is emit each
* file line
*/
public void nextTuple() {
/**
* The nextuple it is called forever, so if we have been readed the file
* we will wait and then return
*/
if(completed){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//Do nothing
}
return;
}
String str;
//Open the reader
BufferedReader reader = new BufferedReader(fileReader);
try{
//Read all lines
while((str = reader.readLine()) != null){
/**
* By each line emmit a new value with the line as a their
*/
this.collector.emit(new Values(str),str);
}
}catch(Exception e){
throw new RuntimeException("Error reading tuple",e);
}finally{
completed = true;
}
}
/**
* We will create the file and get the collector object
*/
public void open(Map conf, TopologyContext context,
SpoutOutputCollector collector) {
try {
this.fileReader = new FileReader(conf.get("wordsFile").toString());
} catch (FileNotFoundException e) {
throw new RuntimeException("Error reading file ["+conf.get("wordFile")+"]");
}
this.collector = collector;
}
/**
* Declare the output field "word"
*/
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("line"));
}
}
继承自BaseRichSpout,实现了ack,fail,nextTuple,declareOutputFields等方法。其中最主要的是nextTuple方法,这个方法主要是源源不断的产生数据,不断的emit到下个组件bolt。
bolt例子
public class WordNormalizer extends BaseBasicBolt {
public void cleanup() {}
/**
* The bolt will receive the line from the
* words file and process it to Normalize this line
*
* The normalize will be put the words in lower case
* and split the line to get all words in this
*/
public void execute(Tuple input, BasicOutputCollector collector) {
String sentence = input.getString(0);
String[] words = sentence.split(" ");
for(String word : words){
word = word.trim();
if(!word.isEmpty()){
word = word.toLowerCase();
collector.emit(new Values(word));
}
}
}
/**
* The bolt will only emit the field "word"
*/
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}
}
上面这个bolt完成了格式化单词的功能。
public class WordCounter extends BaseBasicBolt {
Integer id;
String name;
Map<String, Integer> counters;
/**
* At the end of the spout (when the cluster is shutdown
* We will show the word counters
*/
@Override
public void cleanup() {
System.out.println("-- Word Counter ["+name+"-"+id+"] --");
for(Map.Entry<String, Integer> entry : counters.entrySet()){
System.out.println(entry.getKey()+": "+entry.getValue());
}
}
/**
* On create
*/
@Override
public void prepare(Map stormConf, TopologyContext context) {
this.counters = new HashMap<String, Integer>();
this.name = context.getThisComponentId();
this.id = context.getThisTaskId();
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {}
@Override
public void execute(Tuple input, BasicOutputCollector collector) {
String str = input.getString(0);
/**
* If the word dosn't exist in the map we will create
* this, if not We will add 1
*/
if(!counters.containsKey(str)){
counters.put(str, 1);
}else{
Integer c = counters.get(str) + 1;
counters.put(str, c);
}
}
}
上面这个bolt完成了统计单词数目的功能。并且第二个bolt的数据是从第一个bolt获取的。
topology例子
public class TopologyMain {
public static void main(String[] args) throws InterruptedException {
//Topology definition
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("word-reader",new WordReader());
builder.setBolt("word-normalizer", new WordNormalizer())
.shuffleGrouping("word-reader");
builder.setBolt("word-counter", new WordCounter(),1)
.fieldsGrouping("word-normalizer", new Fields("word"));
//Configuration
Config conf = new Config();
conf.put("wordsFile", args[0]);
conf.setDebug(false);
//Topology run
conf.put(Config.TOPOLOGY_MAX_SPOUT_PENDING, 1);
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("Getting-Started-Toplogie", conf, builder.createTopology());
Thread.sleep(1000);
cluster.shutdown();
}
}
topology先new一个TopologyBuilder,之后设置Spout,bolt以及分组方式,因为是在本地模式下运行,所以new一个LocalCluster(),最后提交拓扑。给定时间关闭cluster.
上面的三个组件完成了一个统计单词数目的功能,也算是个storm的helloworld吧。