Storm

2、Storm介绍

2.1. Storm的简介

  • http://storm.apache.org/
  • Storm是Twitter开源的分布式实时大数据处理框架,被业界称为实时版Hadoop。随着越来越多的场景对Hadoop的MapReduce高延迟无法容忍。
  • 大数据实时处理解决方案(流计算)的应用日趋广泛,目前已是分布式技术领域最新爆发点,而Storm更是流计算技术中的佼佼者和主流。

2.3. Storm的特性

  • 适用场景广泛
    • storm可以实时处理消息和更新DB,对一个数据量进行持续的查询并返回客户端(持续计算)
    • 对一个耗资源的查询作实时并行化的处理(分布式方法调用,即DRPC),storm的这些基础 API可以满足大量的场景。
  • 可伸缩性高:
    • Storm的可伸缩性可以让storm每秒可以处理的消息量达到很高。
    • Storm使用ZooKeeper来协调集群内的各种配置使得Storm的集群可以很容易的扩展。
  • 保证无数据丢失
    • 实时系统必须保证所有的数据被成功的处理。storm保证每一条消息都会被处理。
  • 异常健壮
    • storm集群非常容易管理,轮流重启节点不影响应用。
  • 容错性好
    • 在消息处理过程中出现异常, storm会进行重试 。
  • 语言无关性
    • Storm的topology和消息处理组件(Bolt)可以用任何语言来定义, 这一点使得任何人都可以使用storm。

3、Storm的物理架构

在这里插入图片描述

在这里插入图片描述

3.1. nimbus

  • Storm的Master,负责资源分配和任务调度。一个Storm集群只有一个Nimbus。
  • 集群的主节点,对整个集群的资源使用情况进行管理
  • 但是nimbus是一个无状态的节点,所有的一切都存储在Zookeeper

3.2. supervisor

  • Storm的Slave,负责接收Nimbus分配的任务,管理所有Worker
  • 一个Supervisor节点中包含多个Worker进程
  • 默认是4个, 一般情况下一个topology对应一个worker

3.3. woker

  • 工作进程(Process),每个工作进程中都有多个Task

3.4. Task

  • 在 Storm 集群中每个 Spout 和 Bolt 都由若干个任务(tasks)来执行
  • worker中每一个spout/bolt的线程称为一个task
  • 同一个spout/bolt的task可能会共享一个物理线程(Thread),该线程称为executor

3.5. Storm的并行机制

  • Topology由一个或多个Spout/Bolt组件构成。运行中的Topology由一个或多个Supervisor节点中的Worker构成。
  • 默认情况下一个Supervisor节点运行4个Worker
    • 由defaults.yaml/storm.yaml中的属性决定:supervisor.slots.ports:6700 6701 6702 6703
    • 在代码中可以使用new Config().setNumWorkers(3),最大数量不能超过配置的 supervisor.slots.ports数量。
  • Worker为特定拓扑的一个或多个组件Spout/Bolt产生一个或多个Executor默认情况下一个Worker运行一个Executor
  • Executor为特定拓扑的一个或多个组件Spout/Bolt实例运行一个或多个Task默认情况下一个 Executor运行一个Task

在这里插入图片描述

4、Storm的计算架构

流式计算框架

  • 客户端将数据发送给MQ(消息队列),然后传递到Storm中进行计算
  • 最终计算的结果存储到数据库中(HBase,Mysql)
  • 客户端不要求服务器返回结果,客户端可以一直向Storm发送数据
  • 客户端相当于生产者,Storm相当于消费者

在这里插入图片描述

在这里插入图片描述

4.1. Topology

  • 计算拓扑
  • Storm 的拓扑是对实时计算应用逻辑的封装,它的作用与 MapReduce 的任务(Job)很相似
  • 区别在于 MapReduce 的一个 Job 在得到结果之后总会结束,而拓扑会一直在集群中运行,直到你手动去终止它
  • 拓扑还可以理解成由一系列通过数据流(Stream Grouping)相互关联的 Spout 和 Bolt 组成的的 拓扑结构

4.2. Stream

  • 数据流(Streams)是 Storm 中最核心的抽象概念
  • 一个数据流指的是在分布式环境中并行创建、处理的一组元组(tuple)的无界序列
  • 数据流可以由一种能够表述数据流中元组的域(fields)的模式来定义

4.3. Tuple

  • Stream中最小数据组成单元
  • 每个tuple可以包含多列,字段类型可以是: integer, long, short, byte, string, double, float, boolean和byte array。

4.4. Spout 数据源

  • Spout)是拓扑中数据流的来源
  • 一般 Spout 会从一个外部的数据源读取元组然后将他们发送到拓扑中
  • 根据需求的不同,Spout 既可以定义为可靠的数据源,也可以定义为不可靠的数据源
  • 一个可靠的 Spout能够在它发送的元组处理失败时重新发送该元组,以确保所有的元组都能得到正确的处理; storm在检测到一个tuple被整个topology成功处理的时候调用ack, 否则调用fail。
  • 不可靠的 Spout 就不会在元组发送之后对元组进行任何其他的处理。
  • 一个 Spout可以发送多个数据流

4.5. Bolt

  • 拓扑中所有的数据处理均是由 Bolt 完成的
  • 通过数据过滤(filtering)函数处理(functions)聚合(aggregations)联结(joins)数据库交互等功能 。
  • 一个 Bolt 可以实现简单的数据流转换,而更复杂的数据流变换通常需要使用多个 Bolt 并通过多个步骤完成。
  • 第一级Bolt的输出可以作为下一级Bolt的输入。而Spout不能有上一级。
  • Bolt 几乎能够完成任何一种数据处理需求。
  • Bolts的主要方法是execute(死循环)连续处理传入的tuple
  • 成功处理完每一个tuple调用OutputCollector的ack方法,以通知storm这个tuple被处理完成 了
  • 处理失败时,可以调fail方法通知Spout端可以重新发送该tuple

在这里插入图片描述

4.6. StreamGroup

  • 为拓扑中的每个 Bolt 的确定输入数据流是定义一个拓扑的重要环节
  • 数据流分组定义了在 Bolt 的不同任务(tasks)中划分数据流的方式。在 Storm 中有八种内置的数据流分组方式。

4.7. Reliablity

  • 可靠性
  • Storm 可以通过拓扑来确保每个发送的元组都能得到正确处理
  • 通过跟踪由 Spout 发出的每个元组构成的元组树可以确定元组是否已经完成处理。
  • 每个拓扑都有一个“消息延时”参数,如果 Storm 在延时时间内没有检测到元组是否处理完成,就会将该元组标记为处理失败,并会在稍后重新发送该元组。

5、Storm案例之NumberCount

  • <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.storm/storm-core -->
        <dependency>
          <groupId>org.apache.storm</groupId>
          <artifactId>storm-core</artifactId>
          <version>1.2.3</version>
        </dependency>
    
      </dependencies>
    
  • package com.yjxxt.number;
    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) {
    		//创建Topology的构建器
    		TopologyBuilder topologyBuilder = new TopologyBuilder();
    		//开始构建整个流程(Spout)
    		topologyBuilder.setSpout("numberspout", new NumberSpout());
    		//开始构建整个流程(Bolt)
    		topologyBuilder.setBolt("numberBolt", new
    		NumberBolt()).shuffleGrouping("numberspout");
    		//启动Topology
    		Config conf = new Config();
    		//创建一个topology
    		StormTopology topology = topologyBuilder.createTopology();
    		//本地模式启动集群
    		LocalCluster localCluster = new LocalCluster();
    		localCluster.submitTopology("numberTopology", conf, topology);
    	}
    }
    
  • package com.yjxxt.number;
    
    import backtype.storm.spout.SpoutOutputCollector;
    import backtype.storm.task.TopologyContext;
    import backtype.storm.topology.IRichSpout;
    import backtype.storm.topology.OutputFieldsDeclarer;
    import backtype.storm.topology.base.BaseRichSpout;
    import backtype.storm.tuple.Fields;
    import backtype.storm.tuple.Values;
    import java.util.Map;
    
    /**
    * 定义个Spout负责向Storm提供数据
    */
    public class NumberSpout extends BaseRichSpout {
    	//声明一个SpoutOutputCollector对象,用于发送数据
    	private SpoutOutputCollector collector;
    	//声明一个计数器
    	private int number;
    	/**
    	* 当我们执行任务的时候,用于初始化对象
    	*
    	* @param conf
    	* @param context
    	* @param collector 帮助我们将Tuple发送到下一个Bolt
    	*/
    	@Override
    	public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
    		//获取初始化时的发送器
    		this.collector = collector;
    	}
    	/**
    	* 重复调用这个方法从源数据获取一条记录
    	* 我们根据业务需求近期进行封装,然后通过SpoutOutputCollector发送给下一个Bolt
    	*/
    	@Override
    	public void nextTuple() {
    		//将数据发送下一个Bolt
    		this.collector.emit(new Values(number++ % 10));
    		try {
    		//限制传输速度
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    	/**
    	* 定义你输出值对应的属性
    	*
    	* @param declarer
    	*/
    	@Override
    	public void declareOutputFields(OutputFieldsDeclarer declarer) {
    		declarer.declare(new Fields("num"));
    	}
    }
    
  • package com.yjxxt.number;
    
    import backtype.storm.topology.BasicOutputCollector;
    import backtype.storm.topology.OutputFieldsDeclarer;
    import backtype.storm.topology.base.BaseBasicBolt;
    import backtype.storm.tuple.ITuple;
    import backtype.storm.tuple.Tuple;
    
    /**
    * 开始处理上游传递的数据
    */
    public class NumberBolt extends BaseBasicBolt {
    	//声明一个统计器
    	private static int count;
    	/**
    	* 处理数据的业务逻辑
    	*
    	* @param input
    	* @param collector
    	*/
    	@Override
    	public void execute(Tuple input, BasicOutputCollector collector) {
    		System.out.println("从上游获取的数据:" + input);
    		System.out.println("从上游获取的数据为:" + input.getInteger(0) + "--" +
    		input.getIntegerByField("num"));
    		//开始统计
    		count += input.getInteger(0);
    		System.out.println("截止到本次,共获取数据和为:" + count);
    	}
    	/**
    	* 如果需要向下传递数据,需要提前定义数据的格式
    	*
    	* @param declarer
    	*/
    	@Override
    	public void declareOutputFields(OutputFieldsDeclarer declarer) {
    	}
    }
    

6、Storm的数据分发策略

在这里插入图片描述

6.1. ShuffleGrouping

  • 随机分组,随机派发stream里面的tuple,保证每个bolttask接收到的tuple数目大致相同。
  • 轮询, 平均分配
  • 优点:为tuple选择task的代价小; bolt的tasks之间的负载比较均衡;
  • 缺点:上下游components之间的逻辑组织关系不明显;

6.2. FieldsGrouping

  • 按字段分组
  • 比如,按"user-id"这个字段来分组,那么具有同样"user-id"的tuple会被分到相同的Bolt里的一个 task,而不同的"user-id"则可能会被分配到不同的task。
  • 优点: 上下游components之间的逻辑组织关系显著;
  • 缺点: 付出为tuple选择task的代价; bolt的tasks之间的负载可能不均衡,根据field字段而定;

6.3. AllGrouping

  • 广播发送,对于每一个tuple,所有的bolts都会收到
  • 优点: 上游事件可以通知下游bolt中所有task;
  • 缺点: tuple消息冗余,对性能有损耗,请谨慎使用;

6.4. GlobalGrouping

  • 全局分组,把tuple分配给taskid最低的task
  • 优点: 所有上游消息全部汇总,便于合并、统计等;
  • 缺点: bolt的tasks之间的负载可能不均衡,id最小的task负载过重;

6.5. DirectGrouping

  • 指向型分组,这是一种比较特别的分组方法,用这种分组意味着消息(tuple)的发送者指定由消息接收者的哪个task处理这个消息
  • 只有被声明为DirectStream的消息流可以声明这种分组方法
  • 而且这种消息tuple必须使用emitDirect方法来发射
  • 消息处理者可以通过TopologyContext来获取处理它的消息的task的id(OutputCollector.emit方法 也会返回task的id)
  • 优点: Topology的可控性强,且组件的各task的负载可控;
  • 缺点: 当实际负载与预估不符时性能削弱;

6.6. Localorshufflegrouping

  • 本地或随机分组。如果目标bolt有一个或者多个task与源bolt的task在同一个工作进程中,tuple将会被随机发送给这些同进程中的tasks否则,和普通的ShuffleGrouping行为一致
  • 优点: 相对于ShuffleGrouping,因优先选择同进程task间传输而降低tuple网络传输代价, 但因寻找同进程的task而消耗CPU和内存资源,因此应视情况来确定选择 ShuffleGrouping或LocalOrShuffleGrouping;
  • 缺点: 上下游components之间的逻辑组织关系不明显;

6.7. NoneGrouping

  • 不分组,这个分组的意思是说stream不关心到底怎样分组。
  • 目前这种分组和Shufflegrouping是一 样的效果
  • 有一点不同的是storm会把使用nonegrouping的这个bolt放到这个bolt的订阅者同一个线程里面去执行(未来Storm如果可能的话会这样设计)。

6.8. customGrouping

  • 自定义,相当于mapreduce那里自己去实现一个partition一样。

7、Storm案例之WordCount

Storm案例之WordCount.https://blog.csdn.net/weixin_43660536/article/details/119300727

8、Storm高可用集群搭建

Storm高可用集群搭建.https://blog.csdn.net/weixin_43660536/article/details/119300522

9、Storm的通信机制

在这里插入图片描述

9.1. Worker进程间通信原理

  • worker进程间消息传递机制,消息的接收和处理的流程如下图

  • 在这里插入图片描述

  • worker进程

    • 为了管理流入和传出的消息,每个worker进程都有一个独立的接收线程和发送线程
    • 接收线程来负责将外部发送过来的消息移动到对应的executor线程的incoming-queue中
    • 发送线程负责从worker的transfer-queue中读取消息,并通过网络发送给其他worker
  • executor线程

    • 每个executor有独立的incoming-queue 和outgoing-queue
    • Worker接收线程将收到的消息通过task编号传递给对应的executor的incoming-queues
    • executor有单独的线程分别来处理spout/bolt的业务逻辑,业务逻辑输出的中间数据会存放在outgoing-queue
    • 当executor的outgoing-queue中的tuple达到一定的阀值,executor的发送线程将批量获取outgoing-queue中的tuple,并发送到transfer-queue中
  • 每个worker进程控制一个或多个executor线程,用户可在代码中进行配置。其实就是我们在代码中设置的并发度个数。

  • 在这里插入图片描述

  • 通信技术 netty:

    • Netty是一个NIO client-server(客户端服务器)框架
    • https://blog.csdn.net/qq_28959087/article/details/86501141

在这里插入图片描述

9.2. Worker进程内通信原理

  • Disruptor是一个Queue
    • Disruptor是实现了“队列”的功能,而且是一个有界队列(长度有限)
    • 而队列的应用场景自然就 是“生产者-消费者”模型
  • Disruptor一种线程之间信息无锁的交换方式
    • (使用CAS(Compare And Swap/Set)操作)
  • Disruptor主要特点
    1. 没有竞争=没有锁=非常快。
    2. 所有访问者都记录自己的序号的实现方式,允许多个生产者与多个消费者共享相同的数据结 构。
  • Disruptor 核心技术点
    • Disruptor可以看成一个事件监听或消息机制,在队列中一边生产者放入消息,另外一边消费者并行取出处理.
    • 底层是单个数据结构:一个ring buffer(环形数据缓冲区)
  • https://www.cnblogs.com/jony-zhang/p/3817208.html

10、Storm的容错机制

10.1. 集群节点宕机

  • Nimbus宕机 单点故障 从1.0.0版本以后,Storm的Nimbus是高可用的。

  • 非Nimbus节点 故障时,该节点上所有Task任务都会超时,Nimbus会将这些Task任务重新分配到其他服务器上运行

10.2. 进程故障

  • Worker

    • 每个Worker中包含数个Bolt(或Spout)任务。
    • Supervisor负责监控这些任务,当worker失败后会尝试在本机重启它
    • 如果启动过程中仍然一直失败,并且无法向Nimbus发送心跳,Nimbus会将该Worker重新分配到其他服务器上
  • Supervisor

    • 无状态(所有的状态信息都存放在Zookeeper中来管理)
    • 快速失败(每当遇到任何异常情况,都会自动毁灭)
      • 快速失败(fail-fast)
        1. 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改)
        2. 则会抛出Concurrent Modification Exception
        3. java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改
      • 安全失败(fail-safe)
        1. 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的
        2. 而是先复制原有集合内容,在拷贝的集合上进行遍历
        3. java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修 改。
  • Nimbus

    • 无状态(所有的状态信息都存放在Zookeeper中来管理)
    • 快速失败(每当遇到任何异常情况,都会自动毁灭)

10.3. 任务级容错

  • Bolt任务crash引起的消息未被应答。 此时,acker中所有与此Bolt任务关联的消息都会因为超时而失败,对应的Spout的fail方法将 被调用。
  • acker任务失败。 如果acker任务本身失败了,它在失败之前持有的所有消息都将超时而失败。Spout的fail方法 将被调用。
  • Spout任务失败。 在这种情况下,与Spout任务对接的外部设备(如MQ)负责消息的完整性。

10.4. 消息的完整性

  • 消息的完整性定义

    • 每个从Spout(Storm中数据源点)发出的Tuple(Storm中最小的消息单元)可能会生成成千上万个新的Tuple
    • 形成一颗Tuple树,当整颗Tuple树的节点都被成功处理了,我们就说从Spout发出的Tuple被 完全处理了。
    • 在这里插入图片描述
  • 消息完整性机制–Acker

    • acker的任务就是追踪从spout中流出来的每一个message id绑定的若干tuple的处理路径,
    • 如果在用户设置的最大超时时间内这些tuple没有被完全处理,那么acker就会告知spout该消 息处理失败了
    • 相反则会告知spout该消息处理成功了。

在这里插入图片描述

  • XOR异或
    • 异或的运算法则为:0异或0=0,1异或0=1,0异或1=1,1异或1=0(同为0,异为1
    • A xor B…xor B xor A = 0,其中每一个操作数出现且仅出现两次

11、Storm集成Kafka

<properties>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<maven.compiler.source>1.8</maven.compiler.source>
	<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.11</version>
		<scope>test</scope>
	</dependency>

	<!-- https://mvnrepository.com/artifact/org.apache.storm/stormcore -->
	<dependency>
		<groupId>org.apache.storm</groupId>
		<artifactId>storm-core</artifactId>
		<version>1.2.3</version>
	</dependency>

    <!-- https://mvnrepository.com/artifact/org.apache.storm/stormkafka -->
	<dependency>
	     <groupId>org.apache.storm</groupId>
		<artifactId>storm-kafka</artifactId>
		<version>1.2.3</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka -->
	<dependency>
		<groupId>org.apache.kafka</groupId>
		<artifactId>kafka_2.11</artifactId>
		<version>0.8.2.1</version>
		<exclusions>
			<exclusion>
				<groupId>org.apache.zookeeper</groupId>
				<artifactId>zookeeper</artifactId>
			</exclusion>
			<exclusion>
				<groupId>log4j</groupId>
				<artifactId>log4j</artifactId>
			</exclusion>
			<exclusion>
				<groupId>org.slf4j</groupId>
				<artifactId>slf4j-log4j12</artifactId>
			</exclusion>
		</exclusions>
	</dependency>
</dependencies>

12、记录级容错Storm的DRPC

  • DRPC (Distributed RPC) 分布式远程过程调用
  • DRPC 是通过一个 DRPC 服务端(DRPC server)来实现分布式 RPC 功能的
    1. DRPC Server 负责接收 RPC 请求 ,
    2. 并将该请求发送到 Storm中运行的 Topology ,
    3. 等待接收 Topology 发送的处理结果,并将该结果返回给发送请求的客户端。
  • DRPC设计目的
    • 为了充分利用Storm的计算能力实现高密度的并行实时计算
    • Storm接收若干个数据流输入数据在Topology当中运行完成,然后通过DRPC将结果进行输出
  • 客户端通过向 DRPC 服务器发送待执行函数的名称以及该函数的参数来获取处理结果。
    1. 实现该函数的拓扑使用一个DRPCSpout 从 DRPC 服务器中接收一个函数调用流。
    2. DRPC 服务器会为每个函数调用都标记了一个唯一的 id。
    3. 随后拓扑会执行函数来计算结果,并在拓扑的最后使用一个名为 ReturnResults 的 bolt 连接 到 DRPC 服务器 。
    4. 根据函数调用的 id 来将函数调用的结果返回。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值