目录
1 Storm概述
1.1 离线计算是什么?
1.2 流式计算是什么?
1.3 Storm是什么?
1.4 Storm与Hadoop的区别
1.5 Storm应用场景及行业案例
1.5.1 运用场景
1.5.2 典型案列
2 Storm基础知识
2.1 Storm编程模型
2.2 Storm核心组件
2.3 实时流计算常见框架图
3 Storm集群搭建
3.1 环境准备
3.2 Storm集群搭建
3.3 启动集群
3.4 Storm命令行操作
4 常用API
4.1 API简介
4.1.1 Component组件(Spout和Bolt)
4.1.2 Spout(水龙头)
4.1.3 Bolt(转接头)
4.1.4 spout的tail特性
4.2 网站日志处理案例
4.2.1 环境准备
4.2.2 需求1
4.2.3 需求2:动态增加日志,查看控制台打印信息(tail特性)
5 分组策略和并发度
5.1 分组策略(Stream Grouping)
5.2 并发度
5.3 实时单词统计案例
离线计算:批量获取数据、批量传输数据、周期性批量计算数据、数据展示
代表技术:Sqoop批量导入数据、HDFS批量存储数据、MapReduce批量计算数据、Hive批量计算数据
流式计算:数据实时产生、数据实时传输、数据实时计算、实时展示
代表技术:Flume实时获取数据、Kafka实时数据存储、Storm/JStorm实时数据计算、Redis实时结果缓存、持久化存储(mysql)。
离线计算与实时计算最大的区别:实时收集、实时计算、实时展示
实时流处理架构
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)Storm用于实时计算,Hadoop用于离线计算。
2)Storm处理的数据保存在内存中,源源不断;Hadoop处理的数据保存在文件系统中,一批一批处理(批处理)。
3)Storm的数据通过网络传输进来;Hadoop的数据保存在磁盘中。
Storm用来实时计算源源不断产生的数据,如同流水线生产。
Storm能用到很多场景中,包括:实时分析、在线机器学习、连续计算等。
1)推荐系统:实时推荐,根据下单或加入购物车推荐相关商品
2)金融系统:实时分析股票信息数据
3)预警系统:根据实时采集数据,判断是否到了预警阈值。
4)网站统计:实时销量、流量统计,如淘宝双11效果图
1)京东-实时分析系统:实时分析用户的属性,并反馈给搜索引擎
最初,用户属性分析是通过每天在云上定时运行的MR job来完成的。为了满足实时性的要求,希望能够实时分析用户的行为日志,将最新的用户属性反馈给搜索引擎,能够为用户展现最贴近其当前需求的结果。
2)携程-网站性能监控:实时分析系统监控携程网的网站性能
利用HTML5提供的performance标准获得可用的指标,并记录日志。Storm集群实时分析日志和入库。使用DRPC聚合成报表,通过历史数据对比等判断规则,触发预警事件。
3)淘宝双十一:实时统计销售总额
编程模型中组件介绍:
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的连接流分组。
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。
1)Flume获取数据。
2)Kafka临时保存数据。
3)Storm计算数据。
4)Redis是个内存数据库,用来保存数据
hadoop101 | hadoop102 | hadoop103 |
zookeeper | zookeeper | zookeeper |
storm | storm | 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分发到其他节点
(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
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
1)基本接口
2)基本抽象类
(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。
(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)。
Storm可以实时监测文件数据,当文件数据变化时,Storm自动读取。
pom文件中添加以下内容:
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.10version>
dependency>
<dependency>
<groupId>org.apache.stormgroupId>
<artifactId>storm-coreartifactId>
<version>1.1.0version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>utf-8encoding>
configuration>
plugin>
plugins>
build>
project>
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 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 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());
}
}
}
1)在需求1基础上,运行程序。
2)打开website.log日志文件,增加日志调试并保存。
3)观察控制台打印的信息。
结论:Storm可以动态实时监测文件的增加信息,并把信息读取到再处理
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案例不明显,后续案例比较明显 |
并发度:用户指定一个任务,可以被多个线程执行,并发度的数量等于线程executor的数量。(并发度就是线程数,即为图中executor的数量)
Task就是具体的处理逻辑对象,一个executor线程可以执行一个或多个tasks,但一般默认每个executor只执行一个task,所以我们往往认为task就是执行线程,其实不是。
(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 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());
}
}
}