JStorm Storm 上手demo

衣舞晨風發表於2017-04-18
--------------------------------------------------------------------------------------------------------------------------------------------
在全面介紹Storm之前,我們先通過一個簡單的Demo讓大家整體感受一下什麼是Storm。
Storm執行模式:
  1. 本地模式(Local Mode): 即Topology(相當於一個任務,後續會詳細講解)  執行在本地機器的單一JVM上,這個模式主要用來開發、除錯。
  2. 遠端模式(Remote Mode):在這個模式,我們把我們的Topology提交到叢集,在這個模式中,Storm的所有元件都是執行緒安全的,因為它們都會執行在不同的Jvm或物理機器上,這個模式就是正式的生產模式。
寫一個HelloWord Storm

     我們現在建立這麼一個應用,統計文字檔案中的單詞個數,詳細學習過Hadoop的朋友都應該寫過。那麼我們需要具體建立這樣一個Topology,用一個spout負責讀取文字檔案,用第一個bolt來解析成單詞,用第二個bolt來對解析出的單詞計數,整體結構如圖所示:

可以從這裡下載原始碼:http://download.csdn.net/detail/xunzaosiyecao/9818483


寫一個可執行的Demo很簡單,我們只需要三步:
  1. 建立一個Spout讀取資料
  2. 建立bolt處理資料
  3. 建立一個Topology提交到叢集
下面我們就寫一下,以下程式碼拷貝到eclipse(依賴的jar包到官網下載即可)即可執行。
1.建立一個Spout作為資料來源
     Spout作為資料來源,它實現了IRichSpout介面,功能是讀取一個文字檔案並把它的每一行內容傳送給bolt。
package storm.demo.spout;  
  
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 static final long serialVersionUID = 1L;  
    private SpoutOutputCollector collector;  
    private FileReader fileReader;  
    private boolean completed = false;  
  
    public boolean isDistributed() {  
        return false;  
    }  
    /** 
     * 這是第一個方法,裡面接收了三個引數,第一個是建立Topology時的配置, 
     * 第二個是所有的Topology資料,第三個是用來把Spout的資料發射給bolt 
     * **/  
    @Override  
    public void open(Map conf, TopologyContext context,  
            SpoutOutputCollector collector) {  
        try {  
            //獲取建立Topology時指定的要讀取的檔案路徑  
            this.fileReader = new FileReader(conf.get("wordsFile").toString());  
        } catch (FileNotFoundException e) {  
            throw new RuntimeException("Error reading file ["  
                    + conf.get("wordFile") + "]");  
        }  
        //初始化發射器  
        this.collector = collector;  
  
    }  
    /** 
     * 這是Spout最主要的方法,在這裡我們讀取文字檔案,並把它的每一行發射出去(給bolt) 
     * 這個方法會不斷被呼叫,為了降低它對CPU的消耗,當任務完成時讓它sleep一下 
     * **/  
    @Override  
    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) {  
                /** 
                 * 發射每一行,Values是一個ArrayList的實現 
                 */  
                this.collector.emit(new Values(str), str);  
            }  
        } catch (Exception e) {  
            throw new RuntimeException("Error reading tuple", e);  
        } finally {  
            completed = true;  
        }  
  
    }  
    @Override  
    public void declareOutputFields(OutputFieldsDeclarer declarer) {  
        declarer.declare(new Fields("line"));  
  
    }  
    @Override  
    public void close() {  
        // TODO Auto-generated method stub  
    }  
      
    @Override  
    public void activate() {  
        // TODO Auto-generated method stub  
  
    }  
    @Override  
    public void deactivate() {  
        // TODO Auto-generated method stub  
  
    }  
    @Override  
    public void ack(Object msgId) {  
        System.out.println("OK:" + msgId);  
    }  
    @Override  
    public void fail(Object msgId) {  
        System.out.println("FAIL:" + msgId);  
  
    }  
    @Override  
    public Map<String, Object> getComponentConfiguration() {  
        // TODO Auto-generated method stub  
        return null;  
    }  
}  

2.建立兩個bolt來處理Spout發射出的資料
     Spout已經成功讀取檔案並把每一行作為一個tuple(在Storm資料以tuple的形式傳遞)發射過來,我們這裡需要建立兩個bolt分別來負責解析每一行和對單詞計數。
     Bolt中最重要的是execute方法,每當一個tuple傳過來時它便會被呼叫。
     第一個bolt:WordNormalizer
package storm.demo.bolt;  
import java.util.ArrayList;  
import java.util.List;  
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;  
public class WordNormalizer implements IRichBolt {  
    private OutputCollector collector;  
    @Override  
    public void prepare(Map stormConf, TopologyContext context,  
            OutputCollector collector) {  
        this.collector = collector;  
    }  
    /**這是bolt中最重要的方法,每當接收到一個tuple時,此方法便被呼叫 
     * 這個方法的作用就是把文字檔案中的每一行切分成一個個單詞,並把這些單詞發射出去(給下一個bolt處理) 
     * **/  
    @Override  
    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));  
            }  
        }  
        //確認成功處理一個tuple  
        collector.ack(input);  
    }  
    @Override  
    public void declareOutputFields(OutputFieldsDeclarer declarer) {  
        declarer.declare(new Fields("word"));  
  
    }  
    @Override  
    public void cleanup() {  
        // TODO Auto-generated method stub  
  
    }  
    @Override  
    public Map<String, Object> getComponentConfiguration() {  
        // TODO Auto-generated method stub  
        return null;  
    }  
}  
第二個bolt:WordCounter

package storm.demo.bolt;  
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;  
  
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);  
        if (!counters.containsKey(str)) {  
            counters.put(str, 1);  
        } else {  
            Integer c = counters.get(str) + 1;  
            counters.put(str, c);  
        }  
        // 確認成功處理一個tuple  
        collector.ack(input);  
    }  
    /** 
     * Topology執行完畢的清理工作,比如關閉連線、釋放資源等操作都會寫在這裡 
     * 因為這只是個Demo,我們用它來列印我們的計數器 
     * */  
    @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());  
        }  
        counters.clear();  
    }  
    @Override  
    public void declareOutputFields(OutputFieldsDeclarer declarer) {  
        // TODO Auto-generated method stub  
  
    }  
    @Override  
    public Map<String, Object> getComponentConfiguration() {  
        // TODO Auto-generated method stub  
        return null;  
    }  
}  
3.在main函式中建立一個Topology
     在這裡我們要建立一個Topology和一個LocalCluster物件,還有一個Config物件做一些配置。   
package storm.demo;  
  
import storm.demo.bolt.WordCounter;  
import storm.demo.bolt.WordNormalizer;  
import storm.demo.spout.WordReader;  
import backtype.storm.Config;  
import backtype.storm.LocalCluster;  
import backtype.storm.topology.TopologyBuilder;  
import backtype.storm.tuple.Fields;  
public class WordCountTopologyMain {  
    public static void main(String[] args) throws InterruptedException {  
        //定義一個Topology  
        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(),2)  
        .fieldsGrouping("word-normalizer", new Fields("word"));  
        //配置  
        Config conf = new Config();  
        conf.put("wordsFile", "d:/text.txt");  
        conf.setDebug(false);  
        //提交Topology  
        conf.put(Config.TOPOLOGY_MAX_SPOUT_PENDING, 1);  
        //建立一個本地模式cluster  
        LocalCluster cluster = new LocalCluster();  
        cluster.submitTopology("Getting-Started-Toplogie", conf,  
        builder.createTopology());  
        Thread.sleep(1000);  
        cluster.shutdown();  
    }  
}  
執行這個函式我們即可看到後臺列印出來的單詞個數。(ps:因為是Local模式,執行開始可能會列印很多錯誤log,這個先不用管)

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

折線之間的內容整理自:http://blog.csdn.net/suifeng3051/article/details/38369689

以上是Storm的上手例子,那麼JStorm 應該如何寫呢?

我們用的是JStorm,但上面的可以不修改一行就可以在JStorm上跑起來。

 <!-- Storm Dependency -->
       <!-- <dependency>
          <groupId>storm</groupId>
          <artifactId>storm</artifactId>
          <version>0.7.1</version>
       </dependency>-->
      <!-- JStorm Dependency -->
      <dependency>
          <groupId>com.alibaba.jstorm</groupId>
          <artifactId>jstorm-core</artifactId>
          <version>2.1.1</version>
      </dependency>
修改程式碼中pom檔案的依賴項即可,其餘的不需要修改。

小注:

如果不清楚如何使讀取config下word.txt,可以修改TopologyMain類,將其中的

//conf.put("wordsFile", args[0]);
//在conf新增路徑wordsFile的時候,可以將路徑寫死,弄成一個固定值
//比如:我這裡將word.txt放到了/usr/local/jstorm-2.2.1/wait_deploy/路徑下
conf.put("wordsFile", "/usr/local/jstorm-2.2.1/wait_deploy/word.txt");
 如果是要執行在JStrom上,使用mvn打包命令:

# 打包時跳過測試
mvn clean package  -Dmaven.test.skip=true
將打包後的檔案提交到JStorm即可

例如我這裡打包檔名為:Getting-Started-0.0.1-SNAPSHOT.jar,提交命令:

//提交jar 
//jar包名稱:Getting-Started-0.0.1-SNAPSHOT.jar
//入口類:TopologyMain
//入口類需要引數的話,需要在入口類後面新增需要的引數
jstorm jar Getting-Started-0.0.1-SNAPSHOT.jar TopologyMain
#提交jar 

jstorm jar xxxxxx.jar com.alibaba.xxxx.xx parameter
  • xxxx.jar 為打包後的jar
  • com.alibaba.xxxx.xx 為入口類,即提交任務的類
  • parameter即為提交引數

demo中部分函式及引數註釋:
setBolt方法中的引數parallelism_hint代表這樣一個Spout或Bolt有多少個例項,即對應多少個執行緒,一個例項對應一個執行緒。


注意理解spout及bolt:

spout:自定義獲取待處理流的地方

bolt:自定義處理流的地方



JStorm的安裝可以參考官網:https://github.com/alibaba/jstorm/wiki/JStorm-Chinese-Documentation

下午寫JStorm的demo花了一下午的時間,主要原因是:知道storm程式碼不需要修改就能跑在jstorm上,但上網搜資料的還是搜尋jstorm的案例,但網上大部分jstrom的demo都是跑不起來的,或者需要自己升級版本的。jstorm官網的Example,拉到本地後,也是各種報錯。

要寫jstorm的程式碼,搜尋storm,參考storm部分即可。


作者:jiankunking 出處:http://blog.csdn.net/jiankunking




相關文章