在 Storm 中一个拓扑由一个Spout和多个Bolt组成,Spout主要做接收数据、数据分发的工作,Bolt主要做数据处理。


1. 案例处理流程(共需要5个类,一个Spout类、3个Bolt类,一个拓扑类)

在词频统计案例中,数据源就是各行英文短句,以下是整个数据处理的流程:

  • Spout 就负责将一行英文短句作为一条消息输出到数据流中,提供给后续的Bolt进行处理
  • 第一个 Bolt 获取到数据后,将英文短句进行分词操作(单独取出各个英文单词),将单词输出到数据流中
  • 第二个 Bolt 获取到数据后,从原先准备的map集合中根据单词作为key获取对应的value值(map存储的是键值对key-value数据,这里的key就是单词,value就是单词目前出现的次数);如果单词已经出现过就可以获取到该单词出现的次数,只需要在这个次数上+1即可,如果单词是第一次出现就新建一个键值对,key是单词,value是1,最后将元组输出到数据流
  • 第三个 Bolt 获取到已经合并后的结果(即处理的最终结果做数据输出)
  • 创建一个类(拓扑类),在该类中声明spout与bolt的拓扑关系及传输方式,提交运行方式等(最终的执行类)
2. 业务处理
(1)项目创建(创建maven项目)

将以下依赖包内容添加到pom文件中(在标签中就行)
<dependency>标签记录的是依赖jar包信息(storm中的封装好的方法或类都在storm-core依赖包中)
<plugin>标签存放的是插件信息(maven-compiler-plugin插件用于编译打包)

<dependencies>
	<dependency>
		<groupId>org.apache.storm</groupId>
		<artifactId>storm-core</artifactId>
		<version>1.0.3</version>
	</dependency>
</dependencies>

<build>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>2.3.1</version>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
			</configuration>
		</plugin>
	</plugins>
</build>
(2)在项目下的src/main/java目录下创建package用于存放类

默认项目结构如图所示

单词出现次数统计 java java词频统计_大数据

(3)Spout类、Bolt类、拓扑类的说明
①. 实现 Spout 类需要继承 BaseRichSpout 类,并需要继承实现它的三个方法分别是:
  • open():初始化方法,主要用于声明实例化的输出类(只会执行一次,在程序刚启动时执行)
  • nextTuple():针对数据创建元组,并将元组发送到数据流中
  • declareOutputFields():输出字段说明
②. 实现 Bolt 类需要继承 BaseRichBolt 类,并需要继承实现它的三个方法分别是:
  • prepare():预处理(初始化方法),主要用于声明实例化的输出类
  • execute():执行方法(数据的处理与输出都是在该方法中实现的)
  • declareOutputFields():输出字段说明(作用于Spout中相同,只要需要将数据输出到数据流都需要诗实现该方法)
③. 实现拓扑类(即topology)需要一个程序执行入口(即java的main方法),在拓扑类中主要需要完成的任务如下:
  • 声明拓扑并指定消息分发策略
  • 配置拓扑任务所需的执行器数量、消息传输语义等信息
  • 任务提交方式及路径(本地/集群)
(4)词频统计案例完整代码
  • ①. 数据分发的 Spout 类的具体实现如下(本案例使用自定义数据源,从数组datas中获取数据,每次获取一个元素即一个英文短句发送到数据流中,获取到最后一条又返回第一条循环获取)
public class DataSourceSpout extends BaseRichSpout {
	private SpoutOutputCollector collector;

	// 设置句子集(数据源)
	private String[] datas = { "This is example of chapter 4", "This is word count example", "Very basic example of Apache Storm",
			"Apache Storm is open source real time processing engine"};
	// 定义索引下标
	private int index = 0;

	public void open(@SuppressWarnings("rawtypes") Map config, TopologyContext context, SpoutOutputCollector collector) {
		// 实例化输出类
		this.collector = collector;
	}

	public void nextTuple() {
		// 获取数据
		String datasource= datas[index];
		System.out.println(datasource);
		// 将数据输出到数据流中
		this.collector.emit(new Values(datasource));
		// 当获取到最后一条数据时返回第一条数据重新获取
		index++;
		if (index >= datas.length) {
			index = 0;
		}
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		// 说明输出字段类别为 datasource
		declarer.declare(new Fields("datasource"));
	}
}
  • ②. 做字符串分割处理的 Bolt 类的具体实现如下(即将英文短句进行分词,获取到每个单词)
public class SplitWordBolt extends BaseRichBolt {

	private OutputCollector collector;
	
	public void prepare(@SuppressWarnings("rawtypes") Map config, TopologyContext context, OutputCollector collector) {
		this.collector = collector;
	}
	
	public void execute(Tuple tuple) {
		// 从数据流中获取类别为 datasource 的数据
		String datasource= tuple.getStringByField("datasource");
		// 英文短句通过空格分割获取到单词
		String[] words = datasource.split(" ");
		for (String word : words) {
			// 将单词传输到数据流中
			this.collector.emit(new Values(word));
		}
	}
	
	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		// 指定输出的单词字段类别为 word
		declarer.declare(new Fields("words"));
	}
}
  • ③. 获取到单词后将计算单词出现次数的 Bolt 类的具体实现如下(使用一个map集合存储单词及出现次数对应的键值对信息,即(word,count),如果当前单词出现过就在原有的count上+1,如果没出现过就新创建一个键值对且word为当前单词,count为1)
public class WordCountBolt extends BaseRichBolt {

	private OutputCollector collector;
	private HashMap<String, Long> counts = null;
	
	public void prepare(@SuppressWarnings("rawtypes") Map config, TopologyContext context, OutputCollector collector) {
		this.collector = collector;
		// 实例化一个空的map集合用于存储(word,count)的键值对信息
		this.counts = new HashMap<String, Long>();
	}
	
	public void execute(Tuple tuple) {
		// 从数据流中取出单词
		String word = tuple.getStringByField("words");
		// 从map集合中获取该单词对应的键值对,如果单词不存在map中会返回null
		Long count = this.counts.get(word);
		// 如果word在map集合(counts)中是不存在,给该word一个初始count值为0
		if (count == null) { 
			count = 0L;
		}
		// 在原本的count值上+1
		count++;
		this.counts.put(word, count);
		// 将键值对结果输出到数据流中
		this.collector.emit(new Values(word, count));
	}
	
	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		// 由于本次输出到数据流的字段是两个字段,所以分别为第一个字段定义类型为word,第二个字段为count
		declarer.declare(new Fields("word", "count"));
	}
}
  • ④. 获取最终处理结果并对输出到控制台的 Bolt 类的具体实现如下:
    cleanup方法:清理方法,与prepare方法类似,一个是在刚启动时执行一次,一个是在bolt最终要关闭前执行(也只会执行一次)
public class DisplayBolt extends BaseRichBolt {

	private HashMap<String, Long> counts = null;

	public void prepare(@SuppressWarnings("rawtypes") Map config, TopologyContext context, OutputCollector collector) {
		this.counts = new HashMap<String, Long>();
	}

	public void execute(Tuple tuple) {
		String word = tuple.getStringByField("word");
		Long count = tuple.getLongByField("count");
		// 获取数据流中的键值对,并将键值对存放到一个map集合中
		this.counts.put(word, count);
	}

	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		// 当前bolt是用于获取最终结果并最终处理的,不需要再将该结果输出到数据流,所以不需要实现该方法
	}

	public void cleanup() {
		System.out.println("--- FINAL COUNTS ---");
		List<String> keys = new ArrayList<String>();
		// 获取map集合中的所有key值(即获取所有单词)
		keys.addAll(this.counts.keySet());
		// 将单词进行排序
		Collections.sort(keys);
		// 根据排序的单词逐个获取每个单词出现的次数,将结果输出到控制台
		for (String key : keys) {
			System.out.println(key + " : " + this.counts.get(key));
		}
		System.out.println("--------------");
	}
}
  • ⑤. 至此,关于词频统计的整个处理逻辑已完成,最后一个类是Storm要执行一个任务需要以拓扑的方式提交,所以最后一个类是拓扑类,具体实现如下:
public class TopologyDemo {

	// java程序执行入口
	public static void main(String[] args) throws Exception {

		// 实例化一个拓扑
		TopologyBuilder builder = new TopologyBuilder();
		// 声明spout
		builder.setSpout("datasource-spout", new DataSourceSpout());
		// 声明spout到splitbolt的消息分发方式:随机分发
		builder.setBolt("split-bolt", new SplitWordBolt()).shuffleGrouping("datasource-spout");
		// 声明splitbolt到countbolt的消息分发方式:字段分发
		builder.setBolt("count-bolt", new WordCountBolt()).fieldsGrouping("split-bolt", new Fields("words"));
		// 声明countbolt到displaybolt的消息分发方式:全局分发
		builder.setBolt("display-bolt", new DisplayBolt()).globalGrouping("count-bolt");

        // 打包成jar的配置
		Config config = new Config();
		// 当前拓扑运行时 需要占用两个槽位(2个worker进程)
		config.setNumWorkers(2);
		config.setNumAckers(0); // storm可靠性处理相关
		StormSubmitter.submitTopology("word-count-topology",config,builder.createTopology());

		// 在本地直接运行的配置(本地有storm环境)
		/*
		if (args != null && args.length > 0) {
			// 提交到集群模式
			System.out.println("submitting on cluster mode");
			StormSubmitter.submitTopology("word-count-Storm-topology", config, builder.createTopology());
		} else {
			// 提交到本地模式(单机模式)
			System.out.println("submitting on local mode");
			LocalCluster cluster = new LocalCluster();
			// 提交拓扑
			cluster.submitTopology("word-count-Storm-topology", config, builder.createTopology());

			Thread.sleep(20000);
			// 关闭拓扑
			cluster.killTopology("word-count-Storm-topology");
			cluster.shutdown();
		}
		*/
	}
}

项目打包参考:
打包时需要注意将storm-core.jar从项目包中移除(由于是打包成jar提交到storm中,本地使用的storm-core.jar与storm程序会冲突,所以需要将storm-core.jar从jar信息中移除)

单词出现次数统计 java java词频统计_大数据_02


单词出现次数统计 java java词频统计_java_03

配置完成点击【Apply】、【OK】即可