Storm框架应用
1 .Storm简介
1.1 引例
-
在介绍Storm之前,我们先看一个日志统计的例子:假如我们想要根据用户的访问日志统计使用斗鱼客户端的用户的地域分布情况,一般情况下我们会分这几步:
-
取出访问日志中客户端的IP
-
把IP转换成对应地域
-
按照地域进行统计
-
如果有时效性要求呢?
-
小时级:还行,每小时跑一个MapReduce Job
-
10分钟:还凑合能跑
-
5分钟 :够呛了,等槽位可能要几分钟呢
-
1分钟 :算了吧,启动Job就要几十秒呢
-
秒级 :… 要满足秒级别的数据统计需求,需要
-
进程常驻运行;
-
数据在内存中
-
Storm正好适合这种需求。
1.2 特性
- Storm是一个分布式实时流式计算平台。主要特性如下:
- 简单的编程模型:类似于MapReduce降低了并行批处理复杂性,Storm降低了实时处理的复杂性,只需实现几个接口即可(Spout实现ISpout接口,Bolt实现IBolt接口)。
- 支持多种语言:你可以在Storm之上使用各种编程语言。默认支持Clojure、Java、Ruby和Python。要增加对其他语言的支持,只需实现一个简单的Storm通信协议即可。
- 容错性:nimbus、supervisor都是无状态的, 可以用kill -9来杀死Nimbus和Supervisor进程, 然后再重启它们,任务照常进行; 当worker失败后, supervisor会尝试在本机重启它。
- 分布式:计算是在多个线程、进程和服务器之间并行进行的。
- 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失。
- 可靠的消息处理:Storm保证每个消息至少能得到一次完整处理。任务失败时,它会负责从消息源重试消息(ack机制)。
- 快速、实时:Storm保证每个消息能能得到快速的处理。
1.3 与常用其他大数据计算平台对比
-
Storm vs. MapReduce Storm的一个拓扑常驻内存运行,MR作业运行完了进行就被kill了;storm是流式处理,MR是批处理;Storm数据在内存中不写磁盘,而MR会与磁盘进行交互;Storm的DAG(有向无环图)模型可以组合多个阶段,而MR只可以有MAP和REDUCE两个阶段。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3YrUr69g-1655396236253)(assets/1653527021011.png)]
2. Storm的核心组件
2.1 拓扑结构
-
topology 是storm中运行的一个实时应用程序的名称(拓扑),因为各个组件间的消息流动而形成逻辑上的拓扑结构。
-
Strom在运行中可分为spout与bolt两个组件,其中,数据源从spout开始,数据以tuple的方式发送到bolt,多个bolt可以串连起来,一个bolt也可以接入多个spot/bolt。运行时Topology如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OHeOlcmf-1655396236254)(assets/1653527052067.png)]
2.2 编程模型的一些基本概念
2.2.1 Tuple
- stream 表示数据的流向,流式Storm的核心抽象。一个流是一个无界Tuple序列,Tuple可以包含整性、长整型、短整型、字节、字符、双精度数、浮点数、布尔值和字节数组。用户可以通过定义序列化器,在本机Tuple使用自定义类型;
2.2.2 Spout组件
-
spout 在一个topology中获取源数据流的组件,通常情况下spout会从外部数据源读取数据,然后转换为topology内部的源数据;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RHyk2DLX-1655396236255)(assets/1653527890072.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qlJLnrF8-1655396236256)(assets/1653527901165.png)]
2.2.3 Bolt组件
-
bolt 接收数据,然后执行处理的组件,用户可以其中执行自己想要的操作(Bolt可以完成过滤、业务处理、连接运算、连接访问数据库等业务);
- 核心方法
prepare:初始化 execute:处理一个tuple暑假,tuple对象中包含了元数据信息 cleanup:shutdown之前的资源清理操作
2.2.4 Stream grouping组件
-
stream grouping(流分组) 流分组是拓扑定义的一部分,为每个Bolt指定应该接收哪个流作为输入。在bolt的任务中定义流应该如何分区,Storm有7个内置的流分组接口(随机分组(Shuffle grouping)、字段分组(Fields grouping)、全部分组(All grouping)、全局分组(Global grouping)、无分组(None grouping)、直接分组(Direct grouping)、本地或随机分组(Local or shuffle grouping))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WQEzE1en-1655396236258)(assets/1653528023787.png)]
- 随机分组(Shuffle grouping):随机分发tuple到Bolt的任务,保证每个任务获得相等数量的tuple。 - 字段分组(Fields grouping):根据指定字段分割数据流,并分组。例如,根据“user-id”字段,相同“user-id”的元组总是分发到同一个任务,不同“user-id”的元组可能分发到不同的任务。 - 全部分组(All grouping):tuple被复制到bolt的所有任务。这种类型需要谨慎使用。- 全局分组(Global grouping):全部流都分配到bolt的同一个任务。明确地说,是分配给ID最小的那个task。- 无分组(None grouping):你不需要关心流是如何分组。目前,无分组等效于随机分组。- 直接分组(Direct grouping):这是一个特别的分组类型。元组生产者决定tuple由哪个元组处理者任务接收。
2.2.5 Worker组件
- Worker(工作进程)是Spout/Bolt中运行具体处理逻辑的进程。Topology跨一个或多个Worker节点的进程执行,每个Worker节点的进程是一个物理的JVM和Topology执行所有任务的子集。
2.2.6 Task组件
-
Task(任务) Worker中每一个Spout/Bolt的线程称为一个任务。每个spout或bolt在集群执行许多任务,每个任务对应一个线程的执行,流分组定义如何从一个任务集到另一个任务集发送Tuple。可通过TopologyBuilder类的setSoupt()和setBolt()方法来设置每个spout或bolt的并行度(parallelism)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ixvANw4T-1655396236258)(assets/1653527593704.png)]
3. 编写Storm的helloworld程序
3.1 配置pom文件
<?xml version="1.0" encoding="UTF-8"?>
<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>com.gec</groupId>
<artifactId>storm-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--导入storm的jar文件-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.storm</groupId>
<artifactId>storm-core</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
</project>
3.2 编写HelloWorldStormDemo类
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import org.apache.storm.utils.Utils;
import java.util.Map;
public class HelloWorldStormDemo {
/*
* 定义一个Spouts
* 此组件作用:产生数据(数据源)
* */
public static class HelloSpouts extends BaseRichSpout
{
private long number=0;
private SpoutOutputCollector collector;
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
this.collector=collector;
}
//此方法会循环调用:不断产生数据
public void nextTuple() {
//不断产生一个累加的数据
number++;
//发送数据
this.collector.emit(new Values(number));
//耗时1秒
Utils.sleep(1000);
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
//数据身份标识
declarer.declare(new Fields("number"));
}
}
/*
* 定义一个bolt组件
* 作用:处理数据(0+1+2+3+4+......+xxxx)
* */
public static class HelloBolt extends BaseRichBolt
{
private long sum=0;
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
}
//此方法作用:处理数据
//作用:处理数据(0+1+2+3+4+......+xxxx)
public void execute(Tuple input) {
Long number=input.getLongByField("number");
sum+=number;
System.out.println(""+sum);
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
}
}
public static void main(String[] args) {
//要构建一个拓扑对象
TopologyBuilder topologyBuilder=new TopologyBuilder();
//设置数据源组件
topologyBuilder.setSpout("HelloSpouts",new HelloSpouts());
//设置bolt组件
topologyBuilder.setBolt("HelloBolt",new HelloBolt()).shuffleGrouping("HelloSpouts");
//使用本地方式提交
LocalCluster localCluster=new LocalCluster();
Config config=new Config();
localCluster.submitTopology("HelloWorldStormDemo",config,topologyBuilder.createTopology());
}
}
4. 编写Wordcount编程实例
4.1 配置pom文件
<?xml version="1.0" encoding="UTF-8"?>
<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>com.gec</groupId>
<artifactId>storm-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--导入storm的jar文件-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.storm</groupId>
<artifactId>storm-core</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
</project>
4.2 编写程序
import org.apache.commons.io.FileUtils;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WordCountStormTopolopy {
/*
* 定义产生数据的数据源
* Spout
* */
public static class DataSourceSpout extends BaseRichSpout
{
private SpoutOutputCollector collector;
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
this.collector=collector;
}
public void nextTuple() {
//读取文件的行内容
/*
File directory:扫描那个文件目录
String[] extensions:针对那些扩展名的文件进行扫描
boolean recursive:是否扫描子目录
*/
File file=new File("D:\\data\\storm\\wc");
//只针对storm/wc目录下的txt文件进行扫描及处理
Collection<File> fileCollection=FileUtils.listFiles(file,new String[]{"txt"},true);
for (File file1 : fileCollection) {
try {
//读取文件的行数据
List<String> lines=FileUtils.readLines(file1);
for (String line : lines) {
this.collector.emit(new Values(line));
}
//针对已经处理过的文件重命名
FileUtils.moveFile(file1,new File(file1.getAbsolutePath()+System.currentTimeMillis()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("line"));
}
}
//作用:拆分行数据,获取单词
public static class SplitLineBolt extends BaseRichBolt
{
private OutputCollector collector;
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
this.collector=collector;
}
public void execute(Tuple input) {
//hadoop spark hadoop storm
//获取行数据
String line=input.getStringByField("line");
//获取单词
String words[]=line.split(" ");
for (String word : words) {
this.collector.emit(new Values(word));
}
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}
}
//作用:单词汇总
public static class CountWordBolt extends BaseRichBolt
{
//存储统计单词出现次数的数据
//key:word
//value:出现的次数
private Map<String,Integer> map=new HashMap<String, Integer>();
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
}
public void execute(Tuple input) {
String word=input.getStringByField("word");
Integer count=1;
if(map.containsKey(word))
{
count=map.get(word);
count++;
}
map.put(word,count);
System.out.println("统计结果输出------------------");
for (Map.Entry<String, Integer> stringIntegerEntry : map.entrySet()) {
System.out.println(stringIntegerEntry.getKey()+" 出现的次数="+stringIntegerEntry.getValue());
}
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
}
}
public static void main(String[] args) {
TopologyBuilder builder=new TopologyBuilder();
builder.setSpout("DataSourceSpout",new DataSourceSpout());
builder.setBolt("SplitLineBolt",new SplitLineBolt()).shuffleGrouping("DataSourceSpout");
builder.setBolt("CountWordBolt",new CountWordBolt()).shuffleGrouping("SplitLineBolt");
LocalCluster localCluster=new LocalCluster();
localCluster.submitTopology("WordCountStormTopolopy",new Config(),builder.createTopology());
}
}
.setSpout(“DataSourceSpout”,new DataSourceSpout());
builder.setBolt(“SplitLineBolt”,new SplitLineBolt()).shuffleGrouping(“DataSourceSpout”);
builder.setBolt(“CountWordBolt”,new CountWordBolt()).shuffleGrouping(“SplitLineBolt”);
LocalCluster localCluster=new LocalCluster();
localCluster.submitTopology("WordCountStormTopolopy",new Config(),builder.createTopology());
}
}