目錄
4.2.3 需求2:動態增加日誌,查看控制檯打印信息(tail特性)
1 Storm概述
1.1 離線計算是什麼?
離線計算:批量獲取數據、批量傳輸數據、週期性批量計算數據、數據展示
代表技術:Sqoop批量導入數據、HDFS批量存儲數據、MapReduce批量計算數據、Hive批量計算數據
1.2 流式計算是什麼?
流式計算:數據實時產生、數據實時傳輸、數據實時計算、實時展示
代表技術:Flume實時獲取數據、Kafka實時數據存儲、Storm/JStorm實時數據計算、Redis實時結果緩存、持久化存儲(mysql)。
離線計算與實時計算最大的區別:實時收集、實時計算、實時展示
實時流處理架構
1.3 Storm是什麼?
Storm是一個分佈式計算框架,主要使用Clojure與Java語言編寫,最初是由Nathan Marz帶領Backtype公司團隊創建,在Backtype公司被Twitter公司收購後進行開源。最初的版本是在2011年9月17日發行,版本號0.5.0。
2013年9月,Apache基金會開始接管並孵化Storm項目。Apache Storm是在Eclipse Public License下進行開發的,它提供給大多數企業使用。經過1年多時間,2014年9月,Storm項目成爲Apache的頂級項目。
Storm是一個免費開源的分佈式實時計算系統。Storm能輕鬆可靠地處理無界的數據流,就像Hadoop對數據進行批處理;
1.4 Storm與Hadoop的區別
1)Storm用於實時計算,Hadoop用於離線計算。
2)Storm處理的數據保存在內存中,源源不斷;Hadoop處理的數據保存在文件系統中,一批一批處理(批處理)。
3)Storm的數據通過網絡傳輸進來;Hadoop的數據保存在磁盤中。
1.5 Storm應用場景及行業案例
Storm用來實時計算源源不斷產生的數據,如同流水線生產。
1.5.1 運用場景
Storm能用到很多場景中,包括:實時分析、在線機器學習、連續計算等。
1)推薦系統:實時推薦,根據下單或加入購物車推薦相關商品
2)金融系統:實時分析股票信息數據
3)預警系統:根據實時採集數據,判斷是否到了預警閾值。
4)網站統計:實時銷量、流量統計,如淘寶雙11效果圖
1.5.2 典型案列
1)京東-實時分析系統:實時分析用戶的屬性,並反饋給搜索引擎
最初,用戶屬性分析是通過每天在雲上定時運行的MR job來完成的。爲了滿足實時性的要求,希望能夠實時分析用戶的行爲日誌,將最新的用戶屬性反饋給搜索引擎,能夠爲用戶展現最貼近其當前需求的結果。
2)攜程-網站性能監控:實時分析系統監控攜程網的網站性能
利用HTML5提供的performance標準獲得可用的指標,並記錄日誌。Storm集羣實時分析日誌和入庫。使用DRPC聚合成報表,通過歷史數據對比等判斷規則,觸發預警事件。
3)淘寶雙十一:實時統計銷售總額
2 Storm基礎知識
2.1 Storm編程模型
編程模型中組件介紹:
1. 元組(Tuple)
元組(Tuple),是消息傳遞的基本單元,是一個命名的值列表,元組中的字段可以是任何類型的對象。Storm使用元組作爲其數據模型,元組支持所有的基本類型、字符串和字節數組作爲字段值,只要實現類型的序列化接口就可以使用該類型的對象。元組本來應該是一個key-value的Map,但是由於各個組件間傳遞的元組的字段名稱已經事先定義好,所以只要按序把元組填入各個value即可,所以元組是一個value的List。
2. 流(Stream)
流是Storm的核心抽象,是一個無界的元組系列。源源不斷傳遞的元組就組成了流,在分佈式環境中並行地進行創建和處理。
3. 水龍頭(Spout)
Spout是拓撲的流的來源,是一個拓撲中產生源數據流的組件。通常情況下,Spout會從外部數據源中讀取數據,然後轉換爲拓撲內部的源數據。
Spout可以是可靠的,也可以是不可靠的。如果Storm處理元組失敗,可靠的Spout能夠重新發射,而不可靠的Spout就儘快忘記發出的元組。
Spout可以發出超過一個流。
Spout的主要方法是nextTuple()。NextTuple()會發出一個新的Tuple到拓撲,如果沒有新的元組發出,則簡單返回。
Spout的其他方法是ack()和fail()。當Storm檢測到一個元組從Spout發出時,ack()和fail()會被調用,要麼成功完成通過拓撲,要麼未能完成。Ack()和fail()僅被可靠的Spout調用。
IRichSpout是Spout必須實現的接口。
4. 轉接頭(Bolt)
在拓撲中所有處理都在Bolt中完成,Bolt是流的處理節點,從一個拓撲接收數據,然後執行進行處理的組件。Bolt可以完成過濾、業務處理、連接運算、連接與訪問數據庫等任何操作。
Bolt是一個被動的角色,接口中有一個execute()方法,在接收到消息後會調用此方法,用戶可以在其中執行自己希望的操作。
Bolt可以完成簡單的流的轉換,而完成複雜的流的轉換通常需要多個步驟,因此需要多個Bolt。
Bolt可以發出超過一個的流。
5. 拓撲(Topology)
拓撲(Topology)是Storm中運行的一個實時應用程序,因爲各個組件間的消息流動而形成邏輯上的拓撲結構。
把實時應用程序的運行邏輯打成jar包後提交到Storm的拓撲。Storm的拓撲類似於MapReduce的作業(Job)。其主要的區別是,MapReduce的作業最終會完成,而一個拓撲永遠都在運行直到它被殺死。一個拓撲是一個圖的Spout和Bolt的連接流分組。
2.2 Storm核心組件
nimbus是整個集羣的控管核心,負責topology的提交、運行狀態監控、任務重新分配等工作。
zk就是一個管理者,監控者。
總體描述:nimbus下命令(分配任務),zk監督執行(心跳監控,worker、supurvisor的心跳都歸它管),supervisor領旨(下載代碼),招募人馬(創建worker和線程等),worker、executor就給我幹活!task就是具體要乾的活。
1 主控節點與工作節點
另外一種對Storm集羣中節點的說法而已:從它是M/S結構而言的,不要和上面的核心組件混着認識。
Storm集羣中有兩類節點:主控節點(Master Node)和工作節點(Worker Node)。其中,主控節點只有一個,而工作節點可以有多個。
2 Nimbus進程與Supervisor進程
主控節點運行一個稱爲Nimbus的守護進程。Nimbus負責在集羣中分發代碼,對節點分配任務,並監視主機故障。
每個工作節點運行一個稱爲Supervisor的守護進程。Supervisor監聽其主機上已經分配的主機的作業,啓動和停止Nimbus已經分配的工作進程。
3 流分組(Stream grouping)
流分組,是拓撲定義中的一部分,爲每個Bolt指定應該接收哪個流作爲輸入。流分組定義了流/元組如何在Bolt的任務之間進行分發。Storm內置了8種流分組方式。
4 工作進程(Worker)
Worker是Spout/Bolt中運行具體處理邏輯的進程。一個worker就是一個進程,進程裏面包含一個或多個線程。
5 執行器(Executor)
一個線程就是一個executor,一個線程會處理一個或多個任務。
6 任務(Task)
一個任務就是一個task。
2.3 實時流計算常見框架圖
1)Flume獲取數據。
2)Kafka臨時保存數據。
3)Storm計算數據。
4)Redis是個內存數據庫,用來保存數據
3 Storm集羣搭建
3.1 環境準備
hadoop101 | hadoop102 | hadoop103 |
zookeeper | zookeeper | zookeeper |
storm | storm | storm |
3.2 Storm集羣搭建
(1)下載:
官方網站:http://storm.apache.org/downloads.html
網盤鏈接:請點這裏 提取碼:4aez
(2)上傳並安裝
1)將下載的storm安裝包上傳到hadoop101的/opt/software/目錄下,配置完成後再進行分發
2)解壓安裝包到指定的目錄下
[root@hadoop101 software]$ tar -zxvf apache-storm-1.1.0.tar.gz -C /opt/module/
3)在storm安裝目錄下創建data文件夾
[root@hadoop101 apache-storm-1.1.0]# mkdir data
4)修改conf/目錄下的配置文件(Storm的配置文件非常嚴格,多或少一個空格都不可以)
修改storm.yaml配置文件
# 設置Zookeeper的主機名稱
storm.zookeeper.servers:
- "hadoop101"
- "hadoop102"
- "hadoop103"# 設置主節點的主機名稱
nimbus.seeds: ["hadoop101", "hadoop102", "hadoop103"]
# 設置Storm的數據存儲路徑
storm.local.dir: "/opt/module/apache-storm-1.1.0/data"
# 設置Worker的端口號
supervisor.slots.ports:
- 6700
- 6701
- 6702
- 6703
5)將配置好的Storm分發到其他節點
3.3 啓動集羣
(1)啓動集羣
1)後臺啓動nimbus
[root@hadoop101 storm]$ bin/storm nimbus &
[root@hadoop102 storm]$ bin/storm nimbus &
[root@hadoop103 storm]$ bin/storm nimbus &
2)後臺啓動supervisor
[root@hadoop101 storm]$ bin/storm supervisor &
[root@hadoop102 storm]$ bin/storm supervisor &
[root@hadoop103 storm]$ bin/storm supervisor &
3)啓動Storm ui
[root@hadoop101 storm]$ bin/storm ui
(2)通過瀏覽器查看集羣狀態
(3)Storm日誌信息查看
1)查看nimbus的日誌信息
在nimbus的服務器上
cd /opt/module/apache-storm-1.1.0/logs
tail -100f /opt/module/apache-storm-1.1.0/logs/nimbus.log
2)查看ui運行日誌信息
在ui的服務器上,一般和nimbus一個服務器
cd /opt/module/apache-storm-1.1.0/logs
tail -100f /opt/module/apache-storm-1.1.0/logs/ui.log
3)查看supervisor運行日誌信息
在supervisor服務上
cd /opt/module/apache-storm-1.1.0/logs
tail -100f /opt/module/apache-storm-1.1.0/logs/supervisor.log
4)查看supervisor上worker運行日誌信息
在supervisor服務上
cd /opt/module/apache-storm-1.1.0/logs
tail -100f /opt/module/apache-storm-1.1.0/logs/worker-6702.log
3.4 Storm命令行操作
1)nimbus:啓動nimbus守護進程
storm nimbus &
2)supervisor:啓動supervisor守護進程
storm supervisor &
3)ui:啓動UI守護進程。
storm ui &
4)list:列出正在運行的拓撲及其狀態
storm list
6)jar:
storm jar 【jar路徑】 【拓撲包名.拓撲類名】 【拓撲名稱】
7)kill:殺死名爲Topology-name的拓撲
storm kill topology-name [-w wait-time-secs]
-w:等待多久後殺死拓撲
8)active:激活指定的拓撲spout。
storm activate topology-name
9)deactivate:禁用指定的拓撲Spout。
storm deactivate topology-name
10)help:打印一條幫助消息或者可用命令的列表。
storm help
storm help <command>
4 常用API
4.1 API簡介
4.1.1 Component組件(Spout和Bolt)
1)基本接口
- IComponent接口
- ISpout接口
- IRichSpout接口
- IStateSpout接口
- IRichStateSpout接口
- IBolt接口
- IRichBolt接口
- BasicBolt接口
2)基本抽象類
- BaseComponent抽象類
- BaseRichSpout抽象類
- BaseRichBolt抽象類
- BaseTransactionalBolt抽象類
- BaseBasicBolt抽象類
4.1.2 Spout(水龍頭)
(1)Open()
是初始化方法
(2)close()
在該spout關閉前執行,但是並不能得到保證其一定被執行,kill -9時不執行,Storm kill {topoName} 時執行
(3)activate()
當Spout已經從失效模式中激活時被調用。該Spout的nextTuple()方法很快就會被調用。
(4)deactivate ()
當Spout已經失效時被調用。在Spout失效期間,nextTuple不會被調用。Spout將來可能會也可能不會被重新激活。
(5)nextTuple()
當調用nextTuple()方法時,Storm要求Spout發射元組到輸出收集器(OutputCollecctor)。NextTuple方法應該是非阻塞的,所以,如果Spout沒有元組可以發射,該方法應該返回。nextTuple()、ack()和fail()方法都在Spout任務的單一線程內緊密循環被調用。當沒有元組可以發射時,可以讓nextTuple去sleep很短的時間,例如1毫秒,這樣就不會浪費太多的CPU資源。
(6)ack()
成功處理tuple回調方法
(7)fail()
處理失敗tuple回調方法
原則:實現一個Spout,可以直接實現接口IRichSpout,如果不想寫多餘的代碼,可以直接繼承BaseRichSpout。
4.1.3 Bolt(轉接頭)
(1)prepare()
prepare ()方法在集羣的工作進程內被初始化時被調用,提供了Bolt執行所需要的環境。
(2)execute()
接受一個tuple進行處理(業務邏輯),也可emit(發射)數據到下一級組件。
(3)cleanup()
Cleanup方法當一個IBolt即將關閉時被調用。不能保證cleanup()方法一定會被調用,因爲Supervisor可以對集羣的工作進程使用kill -9命令強制殺死進程命令。
如果在本地模式下運行Storm,當拓撲被殺死的時候,可以保證cleanup()方法一定會被調用。
實現一個Bolt,可以實現IRichBolt接口或繼承BaseRichBolt,如果不想自己處理結果反饋,可以實現 IBasicBolt接口或繼承BaseBasicBolt,它實際上相當於自動做了prepare方法和collector.emit.ack(inputTuple)。
4.1.4 spout的tail特性
Storm可以實時監測文件數據,當文件數據變化時,Storm自動讀取。
4.2 網站日誌處理案例
4.2.1 環境準備
pom文件中添加以下內容:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.10</version>
</dependency>
<dependency>
<groupId>org.apache.storm</groupId>
<artifactId>storm-core</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
<!--指定java編譯版本,固定配置即可-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2.2 需求1
1)需求:將接收到的日誌的會話id打印在控制檯
(1)模擬訪問網站的日誌信息,包括:網站名稱、會話id、訪問網站時間等
(2)將接收到日誌的會話id打印到控制檯
2)分析
(1)創建網站訪問日誌工具類
(2)在spout中讀取日誌文件,並一行一行發射出去
(3)在bolt中將獲取到的一行一行數據的會話id獲取到,並打印到控制檯。
(4)main方法負責拼接spout和bolt的拓撲。
3)代碼
(1)生成網站訪問日誌
package com.bigdata.web;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;
public class GenerateDate {
public static void main(String[] args) throws FileNotFoundException {
File logFlie = new File("F:/webSite.log");
Random random = new Random();
// 1 網站名稱
String[] hosts = {"www.bigdata.com"};
// 2 會話id
String[] session_id = {"ABYH6Y4V4SCVXTG6DPB4VH9U123", "XXYH6YCGFJYERTT834R52FDXV9U34",
"BBYH61456FGHHJ7JL89RG5VV9UYU7", "CYYH6Y2345GHI899OFG4V9U567", "VVVYH6Y4V4SFXZ56JIPDPB4V678"};
// 3 訪問網站時間
String[] time = {"2018-08-07 08:40:50", "2018-08-07 08:40:51", "2018-08-07 08:40:52", "2018-08-07 08:40:53",
"2018-08-07 09:40:49", "2018-08-07 10:40:49", "2018-08-07 11:40:49", "2018-08-07 12:40:49"};
// 4.拼接網站訪問日誌
StringBuilder sbBuffer = new StringBuilder();
for (int i = 0; i < 40; i++) {
sbBuffer.append(hosts[0] + "\t" + session_id[random.nextInt(5)] + "\t" + time[random.nextInt(8)] + "\n");
}
// 5.判斷log日誌是否存在,不存在則創建
if (!logFlie.exists()) {
try {
logFlie.createNewFile();
} catch (IOException e) {
System.out.println("Create logFile fail !");
e.printStackTrace();
}
}
//6.將拼接的日誌信息寫到日誌文件中
FileOutputStream fos = new FileOutputStream(logFlie);
try {
fos.write(sbBuffer.toString().getBytes());
fos.close();
System.out.println("===================>>>generate data over");
} catch (IOException e) {
e.printStackTrace();
}
}
}
(2)創建spout
package com.bigdata.web;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.IRichSpout;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
import java.io.*;
import java.util.Map;
public class WebLogSpout implements IRichSpout {
private SpoutOutputCollector collector = null;
private BufferedReader bufferedReader = null;
private String str = null;
@Override
public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) {
this.collector = spoutOutputCollector;
try {
this.bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("F:/webSite.log"))));
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void close() {
}
@Override
public void activate() {
}
@Override
public void deactivate() {
}
@Override
public void nextTuple() {
try {
while ((str = bufferedReader.readLine()) != null){
collector.emit(new Values(str));
Thread.sleep(2000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void ack(Object o) {
}
@Override
public void fail(Object o) {
}
@Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("log"));
}
@Override
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
(3)創建bolt
package com.bigdata.web;
import org.apache.storm.shade.org.apache.commons.lang.StringUtils;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.IRichBolt;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.tuple.Tuple;
import java.util.Map;
public class WebLogBolt implements IRichBolt {
private OutputCollector collector = null;
private int num = 0;
@Override
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
this.collector = outputCollector;
}
@Override
public void execute(Tuple tuple) {
try {
String log = tuple.getStringByField("log");
if (StringUtils.isNotBlank(log)) {
num++;
System.err.println(Thread.currentThread().getName() + "lines :" + num + " session_id:" + log.split("\t")[1]);
}
collector.ack(tuple);
Thread.sleep(1000);
} catch (InterruptedException e) {
collector.fail(tuple);
e.printStackTrace();
}
}
@Override
public void cleanup() {
}
@Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
}
@Override
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
(4)創建main
package com.bigdata.web;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.StormSubmitter;
import org.apache.storm.topology.TopologyBuilder;
//拓撲任務驅動類
public class WebLogDriver {
public static void main(String[] args){
// 1.創建拓撲對象
TopologyBuilder builder = new TopologyBuilder();
// 2.設置spout和bolt
builder.setSpout("weblogspout",new WebLogSpout(),1);
builder.setBolt("weblogbolt",new WebLogBolt(),1).shuffleGrouping("weblogspout");
//builder.setBolt("weblogbolt",new WebLogBolt(),2).fieldsGrouping("weblogspout",new Fields("log"));
// 3.配置worker開啓個數
Config config = new Config();
config.setNumWorkers(2);
// 4.提交拓撲
if (args.length > 0) {
//提交到分佈式集羣
try {
StormSubmitter.submitTopology("weblogtopology",config,builder.createTopology());
} catch (Exception e) {
e.printStackTrace();
}
} else {
//本地集羣模式執行
LocalCluster localCluster = new LocalCluster();
localCluster.submitTopology("weblogtopology",config,builder.createTopology());
}
}
}
4.2.3 需求2:動態增加日誌,查看控制檯打印信息(tail特性)
1)在需求1基礎上,運行程序。
2)打開website.log日誌文件,增加日誌調試並保存。
3)觀察控制檯打印的信息。
結論:Storm可以動態實時監測文件的增加信息,並把信息讀取到再處理
5 分組策略和併發度
5.1 分組策略(Stream Grouping)
stream grouping用來定義一個stream應該如何分配給Bolts上面的多個Executors(多線程、多併發)。
Storm裏面有7種類型的stream grouping
1)Shuffle Grouping: 隨機分組,輪詢,平均分配。隨機派發stream裏面的tuple,保證每個bolt接收到的tuple數目大致相同。
2)Fields Grouping:按字段分組,比如按userid來分組,具有同樣userid的tuple會被分到相同的Bolts裏的一個task,而不同的userid則會被分配到不同的bolts裏的task。
3)All Grouping:廣播發送,對於每一個tuple,所有的bolts都會收到。
4)Global Grouping:全局分組,這個tuple被分配到storm中的一個bolt的其中一個task。再具體一點就是分配給id值最低的那個task。
5)Non Grouping:不分組,這stream grouping個分組的意思是說stream不關心到底誰會收到它的tuple。目前這種分組和Shuffle grouping是一樣的效果。在多線程情況下不平均分配。
6)Direct Grouping:直接分組,這是一種比較特別的分組方法,用這種分組意味着消息的發送者指定由消息接收者的哪個task處理這個消息。只有被聲明爲Direct Stream的消息流可以聲明這種分組方法。而且這種消息tuple必須使用emitDirect方法來發射。消息處理者可以通過TopologyContext來獲取處理它的消息的task的id (OutputCollector.emit方法也會返回task的id)。
7)Local or shuffle grouping:如果目標bolt有一個或者多個task在同一個工作進程中,tuple將會被隨機發送給這些tasks。否則,和普通的Shuffle Grouping行爲一致。
8)測試
(1)spout併發度修改爲2,bolt併發度修改爲1,shuffleGrouping模式
builder.setSpout("WebLogSpout", new WebLogSpout(),2); builder.setBolt("WebLogBolt", new WebLogBolt(), 1).shuffleGrouping("WebLogSpout"); Spout開兩個線程會對數據讀取兩份,打印出來就是2份。如果數據源是消息隊列,就不會出來讀取兩份的數據(統一消費者組,只能有一個消費者) Thread-33-WebLogBolt-executor[1 1]lines:60 session_id:CYYH6Y2345GHI899OFG4V9U567 |
(2)spout併發度修改爲1,bolt併發度修改爲2,noneGrouping模式
builder.setSpout("WebLogSpout", new WebLogSpout(),1); builder.setBolt("WebLogBolt", new WebLogBolt(), 2).noneGrouping("WebLogSpout"); 每個bolt接收到的單詞不同。 Thread-33-WebLogBolt-executor[1 1]lines:14 session_id:VVVYH6Y4V4SFXZ56JIPDPB4V678 Thread-34-WebLogBolt-executor[2 2]lines:16 session_id:VVVYH6Y4V4SFXZ56JIPDPB4V678 |
(3)spout併發度修改爲1,bolt併發度修改爲2,fieldsGrouping
builder.setSpout("WebLogSpout", new WebLogSpout(),1); builder.setBolt("WebLogBolt", new WebLogBolt(), 2).fieldsGrouping("WebLogSpout", new Fields("log")); 基於web案例不明顯,後續案例比較明顯 |
5.2 併發度
併發度:用戶指定一個任務,可以被多個線程執行,併發度的數量等於線程executor的數量。(併發度就是線程數,即爲圖中executor的數量)
Task就是具體的處理邏輯對象,一個executor線程可以執行一個或多個tasks,但一般默認每個executor只執行一個task,所以我們往往認爲task就是執行線程,其實不是。
5.3 實時單詞統計案例
(1)需求:實時統計發射到Storm框架中各個單詞的總數
(2)分析:設計一個拓撲,來實現對文檔裏面的單詞出現的頻率進行統計
整個topology分爲三個部分:
(1)WordCountSpout:數據源,在已知的英文句子中,隨機發送一條句子出去。
(2)WordCountSplitBolt:負責將單行文本記錄(句子)切分成單詞
(3)WordCountBolt:負責對單詞的頻率進行累加
(3)代碼
1)創建spout
package com.bigdata.word;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
import java.util.Map;
public class WorldCountSpout extends BaseRichSpout {
private SpoutOutputCollector collector = null;
@Override
public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) {
this.collector = spoutOutputCollector;
}
@Override
public void nextTuple() {
collector.emit(new Values("i am chinese love china"));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("love"));
}
}
2)創建切割單詞的bolt
package com.bigdata.word;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import java.util.Map;
public class WorldSplitBolt extends BaseRichBolt {
private OutputCollector collector = null;
@Override
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
this.collector = outputCollector;
}
@Override
public void execute(Tuple tuple) {
String line = tuple.getString(0);
String[] words = line.split(" ");
for (String word : words) {
collector.emit(new Values(word, 1));
}
}
@Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("word", "num"));
}
}
3)創建彙總單詞的bolt
package com.bigdata.word;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Tuple;
import java.util.HashMap;
import java.util.Map;
public class WorldCountBolt extends BaseRichBolt {
private OutputCollector collector = null;
private Map<String, Integer> map = new HashMap<>();
@Override
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
this.collector = outputCollector;
}
@Override
public void execute(Tuple tuple) {
String word = tuple.getString(0);
Integer num = tuple.getInteger(1);
if (map.containsKey(word)) {
Integer count = map.get(word);
map.put(word, count + num);
} else {
map.put(word, num);
}
System.err.println(Thread.currentThread().getId() + " word:" + word + " num:" + map.get(word));
}
@Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
}
}
4)創建程序的拓撲main
package com.bigdata.word;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.StormSubmitter;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Fields;
public class WorldDriver {
public static void main(String[] args) {
//1.創建拓撲對象
TopologyBuilder builder = new TopologyBuilder();
//2.設置spout和bolt
builder.setSpout("wordSpout", new WorldCountSpout(), 1);
builder.setBolt("wordSplitBolt", new WorldSplitBolt(), 4).shuffleGrouping("wordSpout");
builder.setBolt("wordCountBolt", new WorldCountBolt(), 2).fieldsGrouping("wordSplitBolt", new Fields("word"));
//3.配置Worker的開啓數
Config config = new Config();
config.setNumWorkers(4);
//4.提交拓撲
if (args.length > 0) {
//提交到分佈式集羣
try {
StormSubmitter.submitTopology("wordTopology", config, builder.createTopology());
} catch (Exception e) {
e.printStackTrace();
}
} else {
//提交到本地集羣模式
LocalCluster localCluster = new LocalCluster();
localCluster.submitTopology("wordCountTopology", config, builder.createTopology());
}
}
}