15.大数据学习之旅——Storm

Storm介绍


介绍

15.大数据学习之旅——Storm_第1张图片

官方网址:http://storm.apache.org/
官方对于Storm的介绍:
Apache Storm is a free and open source distributed realtime computation system.
Storm makes it easy to reliably process unbounded streams of data, doing for
realtime processing what Hadoop did for batch processing. Storm is simple, can be
used with any programming language, and is a lot of fun to use!
Storm has many use cases: realtime analytics, online machine learning, continuous
computation, distributed RPC, ETL, and more. Storm is fast: a benchmark clocked it
at over a million tuples processed per second per node. It is scalable, fault- -
tolerant, guarantees your data will be processed, and is easy to set up and
operate.
Storm是一个分布式的实时计算框架,具有可扩展,容错等特性。可以应用于实时计算,在线
机器学习等领域。

什么是实时流计算
近几年互联网的信息呈现出爆发式膨胀,人们获取信息的途径也更加多样、更加便捷,同时对
信息的时效性的要求也越来越高,随着时间的流逝,数据也在流逝,那么我们要充分利用数据
的价值,就要随着数据流的实时到达,实时处理。
所谓实时流计算,就是近几年由于数据得到广泛应用之后,在数据持久性建模不满足现状的情
况下,急需数据流的瞬时建模或者计算处理。

这种实时计算的应用实例有:
1)金融服务
2)网络监控
3)电信数据管理
4)Web应用
5)生产制造
6)传感检测
……等等
在这种数据流模型中,这些数据以大量、快速、时变(可能是不可预知)的数据流持续到达,
由此产生了一些基础性的新的研究问题——实时计算。实时计算的一个重要方向就是实时流
计算。

实时流计算背景

数据的价值随着时间的流逝而降低,所以事件出现后必须尽快对它们进行处理,最好事件出现
时便立刻对其进行处理,发生一个事件进行一次处理,而不是缓存起来成一批处理。
例如商用搜索引擎,像Google、Bing和Yahoo!等,通常在用户查询响应中提供结构化的
Web结果,同时也插入基于流量的点击付费模式的文本广告。为了在页面上的最佳位置展现
最相关的广告,通过一些算法来动态估算给定上下文中一个广告被点击的可能性(CTR预
估)。上下文可能包括用户偏好、地理位置、历史查询、历史点击等信息。一个主搜索引擎可
能每秒钟处理成千上万次查询,每个页面都可能会包含多个广告。为了及时处理用户反馈,需
要一个低延迟、可扩展、高可靠的处理引擎。
对于这些实时性要求很高的应用,可以采用MapReduce来处理实时数据流。但是,尽管

MapReduce做了实时性改进,也很难稳定地满足应用需求。这是因为Hadoop MapReduce
框架为批处理做了高度优化,典型的是通过调度批量任务来操作静态数据,任务不是常驻服
务,数据也不是实时流入;而数据流计算的典型范式之一是不确定数据速率的事件流流入系
统,系统处理能力必须与事件流量匹配。

实时计算处理流程
互联网上海量数据(一般为日志流)的实时计算过程可以划分为3个阶段:
在这里插入图片描述
1)数据实时采集
需求:功能上保证可以完整地收集到所有日志数据,为实时应用提供实时数据;响应时间上要
保证实时性、低延迟(在1s左右);配置简单,部署容易;系统稳定可靠等。
目前,互联网企业的海量数据采集工具有Facebook开源的Scribe、LinkedIn开源的
Kafka、Cloudera开源的Flume,淘宝开源的TimeTunnel、Hadoop的Chukwa等,它们均
可以满足每秒数百MB的日志数据采集和传输需求。
2)数据实时计算
传统的数据操作,首先将数据采集并存储在DBMS中,然后通过查询和DBMS进行交互,得到
用户想要的答案。在整个过程中,用户是主动的,而DBMS系统是被动的。但是,对于现在大
量存在的实时数据,如股票交易的数据,这类数据实时性强,数据量大,没有止境,传统的架
构并不合适。
流计算就是专门针对这种数据类型准备的。在流数据不断变化的运动过程中实时地进行分析,
捕捉到可能对用户有用的信息,并把结果发送出去。在整个过程中,数据分析处理系统是主动
的,而用户却处于被动接收的状态,
实时计算的需求:适应流式数据、不间断查询;系统稳定可靠、可扩展性好、可维护性好等。
3)实时查询服务
全内存:直接提供数据读取服务,定期转存到磁盘或数据库进行持久化。
半内存:使用Redis、Memcache、MongoDB、BerkeleyDB等内存数据库提供数据实时查
询服务,由这些系统进行持久化操作。
全磁盘:使用HBase等以分布式文件系统(HDFS)为基础的NoSQL数据库,对于KeyValue
内存引擎,关键是设计好Key的分布。

Storm的应用场景


概述
要从海量数据中提取加工对业务有用的信息,选取合适的技术将事半功倍,省去了重新造轮子的烦恼。
对海量数据进行批处理运算,Hadoop依旧保持着无法撼动的地位。但在对实时性要求较高的应用场景
中,Hadoop就显得力不从心。它需要将数据先落地存储到HDFS上,然后再通过MapReduce进行计
算。这样的批处理运算流程使它很难将延时缩小到秒级。
Storm的处理速度最快可以达到毫秒级别。Storm的QPS (Query Per Second)达到9万~10万。
JStorm QPS(12万~11万)。
此外,对于实时处理的技术,还可以用Spark Streaming。
Storm的另外一个优势在于:Storm可以一个一个tuple处理,(细粒度处理),所以像金融领域的实
时流处理,优先选择Storm。
Storm是基于数据流的实时处理系统,提供了大吞吐量的实时计算能力(因为Storm是一个分布式架
构)。每条数据到达系统时,立即在内存中进入处理流程,并在很短的时间内处理完成。实时性要求较
高的数据分析场景,都可以尝试使用Storm作为技术解决方案。

应用场景

  1. 语音实时墙
    以移动互联网行业中的智能手机移动APP为对象,实时统计用户的访问频率和访问地址,并将统计实时
    反映在前端页面的地图中,投影到大显示屏上。
    移动互联是非常热门的行业,而且这个行业也诞生了不少优秀的应用,用户量也非常巨大,微信就是其
    中的一款应用。在用户量巨大的前提下,移动APP的安装、浏览和点击的日志数量会成几何级数暴增,
    基于这些日志数据的统计分析,特别是实时计算方面,实现的难度比较高,借助实时计算框架进行开发

的门槛则会降低很多。Storm恰恰是众多实时计算框架中的首选。
语音“实时墙”项目的需求是将用户登录的地点实时显示在地图上,数据量为每天一亿,每秒峰值
20000,要求系统具备高可靠性,某些单点出现问题不能对服务造成影响,数据落地到数据展示的时延
在30s内。Hadoop处理MapReduce任务会花费分钟级别,这显然不能满足业务对数据实时性秒级别
的需求,Storm这个实时的、分布式以及具备高可靠性的计算系统,能较好地弥补Hadoop执行
MapReduce任务分钟级别的不足,全内存计算使得寻址速度是Hadoop硬盘寻址速度的百万倍以上,
因此Storm解决高并发瓶颈,能让数据的输入输出处理更加安全、快速,稳定地处理并发及安全性也将
保证复杂繁琐的数据收集准确而高效。

  1. 网络流量流向实时分析
    通过Storm实时分析网络流量流向,并将实时统计反映在前端页面的图表中以备查询。网络流量流向是
    IP网络运营管理的重要基础数据,通过采集和分析流量数据,运营商可以了解整个网络的运行态势、网
    络负载状况、网络安全状况、流量发展趋势、用户的行为模式、业务与站点的接受程度,还可以为制定
    灵活的资费策略和计费方式提供依据。
  2. 交通—基于GPS的实时路况分析
    基于GPS数据,通过Storm可以做实时路况分析系统。实时路况能实时反映区域内交通路况,指引最
    佳、最快捷的行驶路线,提高道路和车辆的使用效率。
    目前,提供路况服务的公司主要有三家:世纪高通、北大千方(收购掌城)、九州联宇。它们为百度地
    图和Google地图这些路况数据的应用服务商提供数据。

Storm架构原理


概述

Storm可以方便地在一个计算机集群中编写与扩展复杂的实时计算,Storm之于实时处
理,就好比Hadoop之于批处理。Storm保证每个消息都会得到处理,而且它很快——
在一个小集群中,每秒可以处理数以百万计的消息。Storm的处理速度非常惊人:经测
试,每个节点每秒可以处理100万个数据元组(tuple)。

设计思想
在Storm中也有对流(Stream)的抽象,流是一个不间断的、无界的连续
Tuple。Tuple是包含一个或多个键值对的集合。
Storm认为每个流都有一个Stream源,也就是原始元组的源头,所以它将这个源头抽象
为Spout(水龙头),Spout可能连接Twitter API并不断发出推文(Tweet),也可能
从某个队列中不断读取队列元素并装配为Tuple发射。
有了源头即Spout也就是有了流,同样的思想,Twitter将流的中间状态转换抽象为
Bolt,Bolt可以消费任意数量的输入流,只要将流方向导向该Bolt,同时它也可以发送
新的流给其他Bolt使用,这样一来,只要打开特定的Spout(管口),再将Spout中流
出的Tuple导向特定的Bolt,由Bolt处理导入的流后再导向其他Bolt或者目的地。
假设Spout就是一个一个的水龙头,并且每个水龙头里流出的水是不同的,想获得哪种
水就拧开哪个水龙头,然后使用管道将水龙头的水导向到一个水处理器(Bolt),水处
理器处理后使用管道导向另一个处理器或者存入容器中。如下图所以Spout、Tuple和
Bolt之间的关系和流程。
15.大数据学习之旅——Storm_第2张图片
这样一来,只要打开特定的Spout(管口),再将Spout中流出的Tuple导向特定的
Bolt,由Bolt处理导入的流后再导向其他Bolt或者目的地。
为了增大水处理效率,可以在同一个水源处接上多个水龙头并使用多个水处理器,如下
图所示:
15.大数据学习之旅——Storm_第3张图片
综上,我们可以将Storm整个的流程图看做是一个有向无环图(DAG),更抽象的来
说,是一个拓扑(Topology)图,如下图所示:
15.大数据学习之旅——Storm_第4张图片
Storm将这个图抽象为Topology(即拓扑),拓扑是Storm中最高层次的一个抽象概
念,提交拓扑到Storm集群执行。

Topology(拓扑)的概念
Storm分布式计算结构称为topology(拓扑),由stream(数据流),spout(数据源
的生成者),bolt(运算)组成,如下图所示。Storm topology大致等同与Hadoop这
类批处理运算中的job。然而,批处理运算中的job对运算的起始和终止有着明确定义,
Storm topology会一直运行下去,除非进程被杀死或被取消部署。
15.大数据学习之旅——Storm_第5张图片

1)Stream
Stream是由无限制连续的tuple组成的序列,tuple是Storm最核心的数据结构,每个
Tuple都是包含了一个或者多个键值对的列表。
2)spout
spout代表了一个Storm topology的主要数据入口,充当采集器的角色,连接到数据
源,将数据转化为一个个tuple,并将tuple作为数据流进行发射。
开发一个spout的主要工作就是编写代码从数据源或者API消费数据。数据源可能包括以
下几种:
Web或者移动程序的点击流
Twitter或其他社交网络的消息
传感器的输出
应用程序的日志事件
3)bolt
bolt可以理解为计算程序中的运算或者函数,将一个或者多个数据流作为输入,对数据
实施运算后,选择性地输出一个或者多个数据流。bolt可以订阅多个由spout或者其他
bolt发射的数据流,这样就可以建立复杂的数据流转换网络。

案例—数字打印

import java.util.Map;
import java.util.Random;

import backtype.storm.spout.SpoutOutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichSpout;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Values;

/**
 * 开发一个Spout组件的方式,是让一个类继承BaseRichSpout
 * @author admin
 *
 */
public class NumberSpout extends BaseRichSpout{
	
	//--创建数据源的发射器,作用是将tuple发射给下游
	private SpoutOutputCollector collector;
	
	/*
	 * Spout组件的初始化方法,只会被调用一次。
	 * 在此方法中做资源的初始化
	 */
	@Override
	public void open(Map arg0, TopologyContext arg1, SpoutOutputCollector collector) {
		//--把发射器做初始化
		this.collector=collector;
		
	}
	
	/*
	 * 此方法用于产生数据源,而且此方法会不断的被调用,直到整个拓扑被终止
	 */
	@Override
	public void nextTuple() {
		//--此方法每调用一次,就会产生一个100内的随机数字
		int number=new Random().nextInt(100);
		//--把数据封装到Tuple的Value对象中,Value对象的参数是可变参数
		//--即Value可以存储多个值,类型是Object
		Values value=new Values(number);
		//--通过发射器把tuple发射给下游
		collector.emit(value);
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	

	@Override
	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		//--声明tuple的key字段
		//--需要注意:tuple的key和vaule是一一对应的,而且数量关系保持一致
		declarer.declare(new Fields("number"));
		
	}

}

import java.util.Map;

import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichBolt;
import backtype.storm.tuple.Tuple;

public class PrintBolt extends BaseRichBolt{
	
	//--创建Bolt组件的Tuple发射器,用于向下游Bolt发射Tuple
	private OutputCollector collector;
	
	/*此方法时Bolt组件的初始化方法
	 * 最常用的是初始化发生器
	 */
	@Override
	public void prepare(Map arg0, TopologyContext arg1, OutputCollector collector) {
		this.collector=collector;
		
	}

	@Override
	public void execute(Tuple input) {
		//--Tuple 元组,是一种kv结构,通过key取value
		int number=input.getIntegerByField("number");
		System.out.println(number);
		
	}

	

	@Override
	public void declareOutputFields(OutputFieldsDeclarer arg0) {
		// TODO Auto-generated method stub
		
	}

}

import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.generated.StormTopology;
import backtype.storm.topology.TopologyBuilder;

public class NumberTopology {

	public static void main(String[] args) {
		//--创建Storm的环境参数对象
		Config conf=new Config();
		
		//--new出组件的实例对象
		NumberSpout spout=new NumberSpout();
		PrintBolt printBolt=new PrintBolt();
		
		//--创建拓扑构建者,用于绑定各组件之间的上下游关系
		TopologyBuilder builder=new TopologyBuilder();
		
		//--绑定数据源,①参:组件标识id,具有唯一性 ②参:组件对象
		builder.setSpout("number_spout", spout);
		//--绑定Bolt组件,并指定它的上游组件是数据源
		builder.setBolt("print_bolt", printBolt)
			   .shuffleGrouping("number_spout");
		
		//--生成拓扑对象(即生成一个job任务)
		StormTopology topology=builder.createTopology();
		
		//--创建本地测试对象
		LocalCluster cluster=new LocalCluster();
		
		//--提交拓扑运行
		cluster.submitTopology("number_topology",conf, topology);
	}
}

案例-单词统计
实现步骤:

  1. 创建普通java工程
  2. 将Storm依赖包导入
  3. 编写各组件代码
    WordCountSpout代码:
import java.util.Map;

import backtype.storm.spout.SpoutOutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichSpout;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Values;

public class WordCountSpout extends BaseRichSpout{
	
	private SpoutOutputCollector collector;
	
	private String[] lines=new String[]{
			"hello world",
			"hello hadoop",
			"hello storm",
			"hello flume"};
	
	private int i=0;
	
	@Override
	public void open(Map arg0, TopologyContext arg1, SpoutOutputCollector collector) {
		this.collector=collector;
		
	}

	@Override
	public void nextTuple() {
		String line=lines[i];
		i++;
		if(i==lines.length){
			i=0;
		}
		collector.emit(new Values(line));
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	

	@Override
	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		declarer.declare(new Fields("line"));
		
	}

}

SplitBolt代码:

import java.util.Map;

import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichBolt;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;

public class SplitBolt extends BaseRichBolt{
	
	private OutputCollector collector;
	
	@Override
	public void prepare(Map arg0, TopologyContext arg1, OutputCollector collector) {
		this.collector=collector;
		
	}

	@Override
	public void execute(Tuple input) {
		String line=input.getStringByField("line");
		String[] words=line.split(" ");
		for(String word:words){
			collector.emit(new Values(word));
		}
		
	}

	

	@Override
	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		declarer.declare(new Fields("word"));
		
	}

}

import java.util.HashMap;
import java.util.Map;

import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichBolt;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;

public class WordCountBolt extends BaseRichBolt{
	
	private OutputCollector collector;
	//--用于统计单词频次,key是单词,value是频次
	private Map<String,Integer> wordMap;
	
	@Override
	public void prepare(Map arg0, TopologyContext arg1, OutputCollector collector) {
		this.collector=collector;
		wordMap=new HashMap<>();
		
	}

	@Override
	public void execute(Tuple input) {
		String word=input.getStringByField("word");
		if(wordMap.containsKey(word)){
			wordMap.put(word, wordMap.get(word)+1);
		}else{
		   wordMap.put(word, 1);
		}
		
		collector.emit(new Values(word,wordMap.get(word)));
		
	}

	

	@Override
	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		declarer.declare(new Fields("word","count"));
		
	}

}

import java.util.Map;

import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichBolt;
import backtype.storm.tuple.Tuple;

public class PrintBolt extends BaseRichBolt{

	@Override
	public void execute(Tuple input) {
		String word=input.getStringByField("word");
		int count=input.getIntegerByField("count");
		System.out.println(word+":"+count);
		
	}

	@Override
	public void prepare(Map arg0, TopologyContext arg1, OutputCollector arg2) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void declareOutputFields(OutputFieldsDeclarer arg0) {
		// TODO Auto-generated method stub
		
	}

	
}

import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.StormSubmitter;
import backtype.storm.generated.AlreadyAliveException;
import backtype.storm.generated.InvalidTopologyException;
import backtype.storm.generated.StormTopology;
import backtype.storm.topology.TopologyBuilder;
import backtype.storm.tuple.Fields;

public class WordCountTopology {

	public static void main(String[] args) throws Exception {
		Config conf=new Config();
		
		//--设置拓扑的进程并发度,下例表示用两个进程去运行拓扑
		//--默认进程的并发度1
		conf.setNumWorkers(2);
		
		WordCountSpout spout=new WordCountSpout();
		SplitBolt splitBolt=new SplitBolt();
		WordCountBolt wordCountBolt=new WordCountBolt();
		PrintBolt printBolt=new PrintBolt();
		
		TopologyBuilder builder=new TopologyBuilder();
		
		//--表示数据源组件线程并发度是2,Task并发度是2
		builder.setSpout("wordcount_spout", spout);
		
		//--表示SplitBolt线程并发度是2,Task并发度是4
		builder.setBolt("split_bolt", splitBolt,2).setNumTasks(4)
			   //--ShuffleGrouping 随机分组
		       .shuffleGrouping("wordcount_spout");
		
		//--如果不设定setNumTasks,则Task并发度=线程并发度
		//--即一个线程运行一个Task
		builder.setBolt("wordcount_bolt",wordCountBolt,2)
		       //--fieldsGrouping 字段分组,确保指定字段相同的数据落到同一个Bolt里
			   .fieldsGrouping("split_bolt",new Fields("word"));
		
		builder.setBolt("print_bolt", printBolt)
		       //--globalGrouping全局分组,所有Tuple都会发给一个Bolt
			   .globalGrouping("wordcount_bolt");
		
		StormTopology topology=builder.createTopology();
		
		LocalCluster cluster=new LocalCluster();
		//--创建Storm集群对象,可以将拓扑提交到真正的Storm集群运行
		//StormSubmitter cluster=new StormSubmitter();
		
		cluster.submitTopology("wordcount_topology",conf, topology);
	}
}

上一篇 14.大数据学习之旅——HBASE表设计&HBase优化

你可能感兴趣的:(大数据学习之旅,大数据)