二、Storm入门之Hello Storm

本章我们将会创建一个Storm工程和我们的第一个Storm topology。

提示:下述假设你已经安装JRE1.6或者更高级版本。推荐使用Oracle提供的JRE:http://www.java.com/downloads/.

操作模式

在开始创建项目之前,了解Storm的操作模式(operation modes)是很重要的。Storm有两种运行方式:

本地模式

在本地模式下,Storm topologies 运行在本地机器的一个JVM中。因为本地模式下查看所有topology组件共同工作最为简单,所以这种模式被用于开发、测试和调试。例如,我们可以调整参数,这使得我们可以看到我们的topology在不同的Storm配置环境中是如何运行的。为了以本地模式运行topologies,我们需要下载Storm的开发依赖包,这是我们开发、测试topologies所需的所有东西。当我们建立自己的第一个Storm工程的时候我们很快就可以看到是怎么回事了。

提示:本地模式下运行一个topology同Storm集群中运行类似。然而,确保所有组件线程安全非常重要,因为当它们被部署到远程模式时,它们可能运行在不同的JVM或者不同的物理机器上,此时,它们之间不能直接交流或者共享内存。

在本章的所有示例中,我们都以本地模式运行。

远程模式

在远程模式下,我们将topology提交到Storm集群中,Storm集群由许多进程组成,这些进程通常运行在不同的机器上。远程模式下不显示调试信息,这也是它被认为是产品模式的原因。然而,在一台机器上创建一个Storm集群也是可能的,并且在部署至产品前这样做还是一个好方法,它可以确保将来在一个成熟的产品环境中运行topology不会出现任何问题。

译者的话:所谓产品环境/模式,指的是代码比较成熟,可以当成产品发布了,与开发环境相对。

在第六章中可以了解到更多关于远程模式的内容,我会在附录B中展示如何安装一个集群。

Hello World Storm

在这个项目中,我们会建立一个简单的topology来统计单词个数,我们可以将它看成是Storm topologies中的“Hello World”。然而,它又是一个非常强大的topology,因为它几乎可以扩展到无限大小,并且经过小小的修改,我们甚至可以使用它创建一个统计系统。例如,我们可以修改本项目来找到Twitter上的热门话题。

为了建立这个topology,我们将使用一个spout来负责从文件中读取单词,第一个bolt来标准化单词,第二个bolt去统计单词个数,如图2-1所示:

topology入门图2-1.topology入门

你可以在https://github.com/storm-book/examples-ch02-getting_started/zipball/master下载本例源码的ZIP文件。

译者的话:本站有备份:http://www.flyne.org/example/storm/storm-book-examples-ch02-getting_started-8e42636.zip

提示:如果你使用git(一个分布式的版本控制和源码管理工具),则可以运行命令:git clone [email protected]:storm-book/examplesch02-getting_started.git进入你想要下载的源码所在的目录。

检查Java安装

搭建环境的第一步就是检查正在运行的Java版本。运行java -version命令,我们可以看到类似如下信息:

java -version
java version “1.6.0_26″
Java(TM) SE Runtime Environment(build 1.6.0_26-b03)
Java HotSpot(TM) Server VM (build 20.1-b02,mixed mode)

创建项目

首先,创建一个文件夹,用于存放这个应用(就像对于任何Java应用一样),该文件夹包含了整个项目的源代码。

接着我们需要下载Storm的依赖包——添加到本应用classpath的jar包集合。可以通过下面两种方式完成:

  • 下载依赖包,解压,并将它们加入classpath路径

  • 使用Apache Maven

提示:Maven是一个软件项目管理工具,可以用于管理一个项目开发周期中的多个方面(从从依赖包到发布构建过程),在本书中我们会广泛使用Maven。可以使用mvn命令检查maven是否安装,如果未安装,可以从http://maven.apache.org/download.html下载。

下一步我们需要新建一个pom.xml文件(pom:project object model,项目的对象模型)去定义项目的结构,该文件描述了依赖包、封装、源码等等。这里我们将使用由nathanmarz构建的依赖包和Maven库,这些依赖包可以在https://github.com/nathanmarz/storm/wiki/Maven找到。

提示:Storm的Maven依赖包引用了在本地模式下运行Storm所需的所有函数库。

使用这些依赖包,我们可以写一个包含运行topology基本的必要组件的pom.xml文件:

<project xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 
    <modelVersion>4.0.0</modelVersion>
    <groupId>storm.book</groupId>
    <artifactId>Getting-Started</artifactId>
    <version>0.0.1-SNAPSHOT</version>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <compilerVersion>1.6</compilerVersion>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
    <repositories>
        <!-- Repository where we can found the storm dependencies -->
        <repository>
            <id>clojars.org</id>
            <url>http://clojars.org/repo</url>
        </repository>
    </repositories>
 
    <dependencies>
        <!-- Storm Dependency -->
        <dependency>
            <groupId>storm</groupId>
            <artifactId>storm</artifactId>
            <version>0.6.0</version>
        </dependency>
    </dependencies>
 
</project>

前几行指定了项目名称、版本;然后我们添加了一个编译器插件,该插件告诉Maven我们的代码应该用Java1.6编译;接着我们定义库(repository)(Maven支持同一个项目的多个库),clojars是Storm依赖包所在的库,Maven会自动下载本地模式运行Storm需要的所有子依赖包。

本项目的目录结构如下,它是一个典型的Maven Java项目。

application structure

java目录下的文件夹包含了我们的源代码,并且我们会将我们的单词文件放到resources文件夹中来处理。

创建第一个topology

为建立我们第一个topology,我们要创建运行本例(统计单词个数)的所有的类。本阶段例子中的有些部分不清楚很正常,我们将在接下来的几个章节中进一步解释它们。

Spout(WordReader类)

WordReader类实现了IRichSpout接口,该类负责读取文件并将每一行发送到一个bolt中去。

提示:spout发送一个定义字段(field)的列表,这种架构允许你有多种bolt读取相同的spout流,然后这些bolt可以定义字段(field)供其他bolt消费。

例2-1包含WordReader类的完整代码(后面会对代码中的每个部分进行分析)

例2-1.src/main/java/spouts/WordReader.java

package spouts;
 
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
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;}
 
    public void ack(Object msgId) {
        System.out.println("OK:"+msgId);
    }
 
    public void close(){}
 
    public void fail(Object msgId) {
        System.out.println("FAIL:"+msgId);
    }
 
    /**
    * 该方法用于读取文件并发送文件中的每一行
    */
 
    public void nextTuple() {
    /**
    * The nextuple it is called forever, so if we have beenreaded 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("Errorreading 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.context=context;
            this.fileReader=new FileReader(conf.get("wordsFile").toString());
        } catch(FileNotFoundException e) {
            throw new RuntimeException("Errorreading file["+conf.get("wordFile")+"]");
        }
        this.collector=collector;
    }
 
    /**
    * 声明输出字段“line”
    */
 
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("line"));
    }
 
}

在任何spout中调用的第一个方法都是open()方法,该方法接收3个参数:

  • TopologyContext:它包含了所有的topology数据

  • conf对象:在topology定义的时候被创建

  • SpoutOutputCollector:该类的实例可以让我们发送将被bolts处理的数据。

下面的代码块是open()方法的实现:

public void open(Map conf,TopologyContext context,SpoutOutputCollector collector) {
    try {
        this.context=context;
        this.fileReader=new FileReader(conf.get("wordsFile").toString());
    } catch(FileNotFoundException e) {
        throw new RuntimeException("Errorreading file["+conf.get("wordFile")+"]");
    }
    this.collector=collector;
}

在open()方法中,我们也创建了reader,它负责读文件。接着,我们需要实现nextTuple()方法,在该方法中发送要被bolt处理的值(values)。在我们的例子中,这个方法读文件并且每行发送一个值。

public void nextTuple() {
    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("Errorreading tuple",e);
    }finally{
        completed = true;
    }
}

 

提示:Values类是ArrayList的一个实现,将列表中的元素传递到构造方法中。

nextTuple()方法被周期性地调用(和ack()、fail()方法相同的循环),当没有工作要做时,nextTuple()方法必须释放对线程的控制,以便其他的方法有机会被调用。因此必须在nextTuple()第一行检查处理是否完成,如果已经完成,在返回前至少应该休眠1秒来降低处理器的负载,如果还有工作要做,则将文件中的每一行读取为一个值并发送出去。

提示:元组(tuple)是一个值的命名列表,它可以是任何类型的Java对象(只要这个对象是可以序列化的)。默认情况下,Storm可以序列化的常用类型有strings、byte arrays、ArrayList、HashMap和HashSet。

 

Bolt(WordNormalizer&WordCounter类)

上面我们设计了一个spout来读取文件,并且每读取一行发送一个元组(tuple)。现在,我们需要创建两个bolt处理这些元组(见图2-1)。这些bolt实现了IRichBolt接口。

在bolt中,最重要的方法是execute()方法,每当bolt收到一个元组,该方法就会被调用一次,对于每个收到的元组,该bolt处理完之后又会发送几个bolt。

提示:一个spout或bolt可以发送多个tuple,当nextTuple()或execute()方法被调用时,它们可以发送0、1或者多个元组。在第五章中你将会了解到更多。

第一个bolt,WordNormalizer,负责接收每一行,并且将行标准化——它将行分解为一个个的单词后转化成小写,并且消除单词前后的空格。

首先,我们需要声明bolt的输出参数:

public void declareOutputFields(OutputFieldsDeclarer declarer) { 
         declarer.declare(new Fields("word")); 
}

这儿,我们声明bolt发送一个命名为“word”的字段。

接着,我们实现execute方法,输入的tuple将会在这个方法中被处理:

public void execute(Tuple input) {
    String sentence = input.getString(0);
    String[]words= sentence.split(" ");
    for(String word:words){
        word =word.trim();
        if(!word.isEmpty()){
            word =word.toLowerCase();
            //Emit the word
            List a =new ArrayList();
            a.add(input);
            collector.emit(a,new Values(word));
        }
    }
    // Acknowledge the tuple
    collector.ack(input);
}

 

第一行读取元组中的值,可以按照位置或者字段命名读取。值被处理后使用collector对象发送出去。当每个元组被处理完之后,就会调用collector的ack()方法,表明该tuple成功地被处理。如果tuple不能被处理,则应该调用collector的fail()方法。

例2-2包含这个类的完整代码。

例2-2.src/main/java/bolts/WordNormalizer.java

下一页

 

你可能感兴趣的:(storm,环境)