Storm的wordcount程式碼編寫與分析

weixin_34185364發表於2018-10-10

storm包裡面是給了wordcount程式例項的,所以我們是可以參考這個來自己實現。從原始碼來看,如下

468490-c1262b1ec5541786.png
image.png

首先建立TopologyBuilder物件,通過該物件來設定spout和bolt。
設定spout的時候可以指定自己編寫的Spout類以及使用的執行緒數(即executor數或者說task數,因為預設情況下就是相等的)。設定bolt的時候可以指定自己編寫的Bolt類,指定分組策略。比如圖中,split這個Bolt的資料(以tuple為單位)來源與id為spout的Spout物件,該物件發射資料給Bolt的策略是隨機發。而count對應的Bolt的資料來源於split對應的Bolt物件,該物件傳送資料按照word欄位來發。

單詞計數的各種實現虛擬碼
1、java實現

//一行一行讀取檔案中資料
String line = BufferedReader.readLine();
//按空格切割
String[] words = line.split(" ");
//進行統計
Map<String,Integer> map = new HashMap<>();
for (String word : words){
    if(map.containskey(word)){
        map.put(word,map.get(word)+1);
    }else {
        map.put(word,1);
    }
}

2、hadoop實現單詞計數

protected void map(LongWritable key, Text value,Context context)
            throws IOException, InterruptedException {
        //每讀一行會呼叫一次
        //按空格切分單詞
        String values = value.toString();
        String[] words = values.split(" ");
        for (String word : words) {
            //將單詞作為key,1作為value輸出
            context.write(new Text(word), new IntWritable(1));
        }
    }
protected void reduce(Text key, Iterable<IntWritable> values,
            Context context) throws IOException, InterruptedException {
        int count = 0;
        for (IntWritable in : values) {
            count += in.get();
        }
        context.write(new Text(key), new IntWritable(count));
    }

3、storm實現單詞計數

Spout:
    FileReader.readLine();
    輸出:line(tuple物件)
SplitBolt:
    輸入:line(tuple物件)
    String[] words = line.split();
    for(String word : words){
        //輸出word
        collectot.emit(word);
    }
CountBolt:
    輸入:word
    Map<String,Integer> map = new HashMap<>();
for (String word : words){
    if(map.containskey(word)){
        map.put(word,map.get(word)+1);
    }else {
        map.put(word,1);
    }
}

Storm的單詞計數程式碼編寫

1、主程式

public class WordCountTopologyDriver {

    public static void main(String[] args) throws AlreadyAliveException, InvalidTopologyException {

        //1、建立topologyBuilder,設定spout和bolt
        TopologyBuilder topologyBuilder = new TopologyBuilder();

        //設定spout   傳參:id,使用的Spout類,併發度
        topologyBuilder.setSpout("myspout",new MySpout(),1);

        //設定Bolt    傳參:id,使用的Bolt類,併發度
        //設定分組策略    隨機分 引數為spout的id
        //mybolt1與myspout跟進id進行連線,怎麼連線?取決於分組策略,shuffleGrouping會對myspout進行分組
        //五個task(也就是五個executor或者說五個執行緒)
        topologyBuilder.setBolt("mybolt1",new SplitBolt(),4).shuffleGrouping("myspout");
        //設定分組策略    按欄位分 引數為上一階段的bolt的id
        //注:如果欄位與mybolt裡面宣告的不一致會出現backtype.storm.generated.InvalidTopologyException: null
        topologyBuilder.setBolt("mybolt2",new CountBolt(),2).fieldsGrouping("mybolt1",new Fields("word"));

        //2、建立Config,指定分配的worker的數量
        Config config = new Config();
        config.setNumWorkers(3);

        //提交任務,可以使用storm叢集來提交也可以使用本地模式來提交(便於除錯)
//        StormSubmitter.submitTopology("wordcountsubmit",config,topologyBuilder.createTopology());
        //使用本地模式提交
        LocalCluster localCluster = new LocalCluster();
        localCluster.submitTopology("wordcountsubmit",config,topologyBuilder.createTopology());
    }
}

2、自定義Spout

/**
 * 獲取資料
 * 將資料一行行寫出去
 * Created by 12706 on 2017/11/6.
 */
public class MySpout extends BaseRichSpout {

    SpoutOutputCollector collector;

    //初始化方法
    public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector collector) {
        this.collector = collector;
    }

    //storm 框架會迴圈呼叫(while(true){..})該方法,將資料射出去
    public void nextTuple() {
        //需要傳入的是一個List,而Vlaues本身就是一個list
        collector.emit(new Values("hadoop hive hbase storm kafka spark"));
    }

    public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
        //給寫出去的資料做宣告,格式根據 collector.emit來定
        outputFieldsDeclarer.declare(new Fields("bigdata"));
    }
}

3、單詞切割Bolt

/**
 * 接收MySpout射出的資料
 * 每次接收list中的第一個資料(也只有這一個,是一行單詞),按照空格切分,射出到下一個單詞統計的CountBolt
 * Created by 12706 on 2017/11/6.
 */
public class SplitBolt extends BaseRichBolt{

    OutputCollector collector;
    //初始化方法
    public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
        this.collector = outputCollector;
    }

    //storm框架會迴圈呼叫該方法
    //對MySpout射出的資料進行處理,將資料按照空格切割寫出
    public void execute(Tuple tuple) {
        //獲取list中的第一個資料(實際也只有這一個)
//        public String getString(int i) {
//            return (String)this.values.get(i);
//        }這是原始碼中程式碼,而value本身就是個list。所以取的就是spout射出的list中的第一個資料
        String line = tuple.getString(0);

        //按空格切割
        String[] words = line.split(" ");
        for (String word : words){

            collector.emit(new Values(word,1));

        }
    }

    public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
        //寫出宣告collector.emit(new Values(word,1)),可知需要兩個引數對應word和1
//         public Fields(String... fields) {
//            this(Arrays.asList(fields));
//          }可以從原始碼看到可以傳入多個引數
        outputFieldsDeclarer.declare(new Fields("word","num"));

    }
}

4、單詞統計Bolt

/**
 * 接收上一個單詞劃分Bolt傳來的資料,進行單詞統計
 * Created by 12706 on 2017/11/6.
 */
public class CountBolt extends BaseRichBolt {

    OutputCollector collector;

    //建立一個map用來快取單詞統計結果

    Map<String,Integer> countMap = new HashMap<String, Integer>();

    public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
        this.collector = outputCollector;
    }

    public void execute(Tuple tuple) {
        //獲取單詞
        String word = tuple.getString(0);
        //獲取數量(1)
        Integer num = tuple.getInteger(1);

        if(countMap.containsKey(word)){
            //單詞已經存在,數量疊加
            countMap.put(word,countMap.get(word)+1);
        }else {
            //單詞不存在,新增單詞
            countMap.put(word,num);
        }
        //控制檯輸出檢視
        System.out.println(countMap);
    }

    public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
        //不需要輸出所以不再做宣告瞭,今後有可能輸出到redis中等
    }
}

設定的是本地執行模式,所以可以直接執行
控制檯檢視

...
{storm=79526, spark=79524, hadoop=79527, hbase=79526}
{storm=79526, spark=79525, hadoop=79527, hbase=79526}
{storm=79526, spark=79525, hadoop=79528, hbase=79526}
{storm=79526, spark=79525, hadoop=79528, hbase=79527}
{storm=79527, spark=79525, hadoop=79528, hbase=79527}
{hive=109259, kafka=109257}
{hive=109260, kafka=109257}
{hive=109260, kafka=109258}
{hive=109261, kafka=109258}
...

注:這裡分兩段是因為設定了單詞計算Bolt(CountBolt)的併發度為2,而且指定了分組策略是按欄位分組,所以分了兩段來統計,且各個段裡面的單詞是一樣的。

如果使用叢集模式,那麼講工程打包(如storm.jar)傳到叢集上,執行命令

storm jar storm.jar com.itheima.storm.WordCountTopologyDriver wordcount

相關文章