Storm整理(上)

Storm整理(上)

一,Storm认识

Storm是个实时的分布式以及具备高容错的计算框架。

主要有两个特点:Storm进程常驻内存,Storm数据不经过磁盘,在内存中处理
在这里插入图片描述

应用:

双十一实时更新成交额:
在这里插入图片描述

QQ实时在线人数统计:
在这里插入图片描述

Storm架构简单介绍,后面有详细解释

架构:

  • Nimbus

  • Supervisor

  • Worker

编程模型:

  • DAG(Topology)
  • Spout
  • Bolt

数据传输:

  • ZMQ

ZeroMQ开源的消息传递框架,并不是一个MessageQueue

  • Netty

Netty是基于NIO的网络框架,更加高效(之所以Storm0.9版本之后没用ZMQ,而是使用了Netty,只要原因是ZMQ的licese和Storm的licese不兼容)。

高可靠性:

  • 异常处理
  • 消息可靠性保障机制

可维护性:

  • StormUI图形化监控接口
    在这里插入图片描述

Storm - 流式处理:

  • 流式处理(异步)

客户端提交数据进行结算,并不会等待数据计算结果

  • 逐条处理

例如:ETL

  • 统计分析

例:计算 PV,UV,访问热点以及某些数据的聚合,加和,平均等

客户端提交数据之后,计算完成结果存储到Redis,Mysql,Hbase…

客户端并不关心最终结果是多少。
在这里插入图片描述

Storm - 实时请求

  • 实时请求应答服务(服务)

客户端提交数据后,立刻取得结果结果并返回给客户端

  • Drpc
  • 实时请求处理

例:图片特征提取
在这里插入图片描述

Storm和MapReduce对比:

Storm: 进程,线程常驻内存中运行,数据不进入到磁盘,数据通过网络传递。

MapReduce: 为TB,PB级别数据设计的批处理计算框架。
在这里插入图片描述

Storm和Spark Streaming 对比

Storm:纯流式处理

  • 专门为流式处理设计
  • 数据传输模式更为简单,很多地方也更为高效
  • 并不是不能做批处理,他也可以做微量批处理,来提高吞吐

Spark Streaming :微批处理

  • 将RDD做的很小来用小的批处理来接近流式处理
  • 基于内存和DAG
    在这里插入图片描述

二,概念加深,实例

2.1 Storm计算模型

在这里插入图片描述

  • Topology - DAG有向无环图的实现

---- 对于Storm实时计算逻辑的封装

---- 即,由一系列通过数据流相互关联的Spout, Bolt 所组成的拓扑结构

---- 声明周期:此拓扑只要启动就会一直在集群中运行,直到手动将其kill,否则不会终止(区别与Mapreduce当中的Job,MR当中的Job在计算执行完成就会终止)

  • Tuple - 元组

---- Stream中最小的数据组成单元

  • Stream - 数据流

---- 从Spout中源源不断传递数据给Bolt,以及上一个Bolt传递给下一个Blot.所形成的这些数据通道叫做Stream

---- Stream声明时需给其指定一个id(默认为Default),实际开发场景中,多使用单一数据流,此时不需要单独指定StreamId
在这里插入图片描述

  • Spout - 数据源

---- 拓扑中数据流的来源。一般会从指定外部的数据源读取元组(Tuple)发送到拓扑中(Topology)

---- 一个Spout可以发送多个数据流(Stream)

​ 可以通过OutPutFieldsDeclare中的declare方法声明定义的不同数据流,发送数据时通过SpoutOutPutCollector中的emit指定数据流id(StreamId)参数将数据发送出去。

---- Spout中最核心的方法是nextTuple,该方法会被Storm线程不断调用,主动从数据源拉取数据,再通过emit方法将数据生成元组(Tuple)发送给之后的Bolt计算

  • Bolt - 数据流处理组件

---- 拓扑中数据处理均由Bolt处理。对于简单的任务或者数据流转换,单个Blot可以简单实现,更加复杂的场景需要多个Bolt分多个步骤完成。

---- 一个Bolt可以发送多个数据流(Stream)

​ 可先通过OuPutFieldsDeclare的declare方法声明定义的不同数据流,发送数据时通过SpoutOutPutCollector中的emit方法指定数据流id(StreamId)参数将数据发送出去。

---- Bolt中最核心的方法是execute方法,该方法负责接收到一个元组(Tuple)数据,真正实现核心的业务的逻辑。

2.2 Strom 数据累加

先看MyTopology.java

package com.shsxt.api;


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

public class MyTopology {

    public static void main(String args[]){

        //实例化 TopologyBuilder 对象
        TopologyBuilder topologyBuilder = new TopologyBuilder();
		//设置,Spout相关信息
        topologyBuilder.setSpout("myspout",new MySpout());
        //设置,Bolt相关信息
        topologyBuilder.setBolt("mybolt",new MyBolt()).shuffleGrouping("myspout");  //shuffleGrouping 设置负责策略是随机分组,同时绑定Spout和Bolt的关系
        
		//通过 TopologyBuilder 对象 构建StormTopology对象 
        StormTopology topology = topologyBuilder.createTopology();

        Config config =  new Config();
        LocalCluster cluster = new LocalCluster();
		//本地提交
        cluster.submitTopology("sum",config,topology);


    }
}

MySpout.java

package com.shsxt.api;

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;

public class MySpout extends BaseRichSpout {

    private SpoutOutputCollector spoutOutputCollector;

    int sum = 0;

    /**
     * 初始化方法,框架在执行任务的时候,会先执行此方法
     * @param map  得到spout的配置
     * @param topologyContext  上下文环境
     * @param spoutOutputCollector  往下游发送数据
     */
    @Override
    public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) {
        this.spoutOutputCollector = spoutOutputCollector;
    }

    /**
     * 此方法是Spout的核心方法
     * 框架会一直调用这个方法,每当调用此方法时,往下游发送数据
     */
    @Override
    public void nextTuple() {
        sum++;

        spoutOutputCollector.emit(new Values(sum));

        try {
            //发送一个Tuple,停一秒
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("spout发送......"+sum);
    }

    /**
     * 当需要往下游发送数据时,就要声明字段个数和字段名字
     * @param outputFieldsDeclarer
     */
    @Override
    public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
        outputFieldsDeclarer.declare(new Fields("number"));
    }

}

MyBolt.java

package com.shsxt.api;

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

import java.util.Map;


public class MyBolt extends BaseRichBolt {

    int sum = 0;

    /**
     * bolt初始化方法
     * @param map
     * @param topologyContext
     * @param outputCollector
     */
    @Override
    public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {

    }

    /**
     * blot最核心的方法
     * storm框架会一直调用该方法,每次调用就传一个数据进来
     * @param tuple
     */
    @Override
    public void execute(Tuple tuple) {
        Integer count = tuple.getInteger(0);

        sum+=count;

        System.out.println("exexute...:"+count+"  sum:"+sum);
    }


    @Override
    public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {

    }
}

运行效果:
在这里插入图片描述

不手动停止的话,会一直跑下去…

2.3 Storm WordCount

先看下图,
在这里插入图片描述

可以看到流程中两个Bolt,一个负责切分数据,并把数据传递给下一个Bolt,接下来看代码实现

WcTopology.java

package com.shsxt.storm;


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 WcTopology {

    public static void main(String args[]){
        TopologyBuilder topologyBuilder = new TopologyBuilder();

        topologyBuilder.setSpout("wcspout",new WcSpout());
        
        topologyBuilder.setBolt("spiltBolt",new SpiltBolt(),4).shuffleGrouping("wcspout").setNumTasks(8);
        
        //接收SpiltBolt发送出去的数据,指定并发数,和根据字段值分组
        topologyBuilder.setBolt("countBolt",new CountBolt(),5).fieldsGrouping("spiltBolt",new Fields("word"));

        StormTopology topology = topologyBuilder.createTopology();

        Config config = new Config();
        config.setNumWorkers(3);
		
        //如果不传参数,就是在本地运行,传参数,就是在集群运行
        if (args.length>0){
            try {
                StormSubmitter.submitTopology(args[0],config,topology);
            } catch (AlreadyAliveException e) {
                e.printStackTrace();
            } catch (InvalidTopologyException e) {
                e.printStackTrace();
            }
        }else {
            LocalCluster cluster = new LocalCluster();
            cluster.submitTopology("mytopology",config,topology);
        }

    }
}

WcSpout.java

package com.shsxt.storm;

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;
import java.util.Random;


public class WcSpout extends BaseRichSpout {

    private SpoutOutputCollector collector;
	
    //随机设置些数据
    String [] lines = {
            "i like play",
            "i not like study",
            "not eat ",
            "day day up"
    };

    Random random = new Random();

    @Override
    public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) {
        this.collector = spoutOutputCollector;
    }

    @Override
    public void nextTuple() {
        int index = random.nextInt(lines.length);
        
		//发送tuple出去
        collector.emit(new Values(lines[index]));

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
        //指定发送出去的tuple的字段
        outputFieldsDeclarer.declare(new Fields("line"));
    }
}

SplitBolt.java

package com.shsxt.storm;

import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.IBasicBolt;
import backtype.storm.topology.IRichBolt;
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;

import java.util.Map;


public class SpiltBolt extends BaseRichBolt {

    private OutputCollector collector;

    @Override
    public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
        this.collector = outputCollector;
    }

    @Override
    public void execute(Tuple tuple) {
        //根据上面Spout指定的字段获取tuple,也可以根据下标获取
        String line = tuple.getStringByField("line");
        String[] split = line.split(" ");

        for (String word: split){
            //切分每一行数据,发送到下游
            collector.emit(new Values(word));
        }
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
        //指定发送出去的字段
        outputFieldsDeclarer.declare(new Fields("word"));
    }
}

CountBolt.java

package com.shsxt.storm;

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

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


public class CountBolt extends BaseRichBolt {
	
    //定义一个Map 用来记录单词的数量
    HashMap<String,Integer> map = new HashMap<>();

    @Override
    public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {

    }

    @Override
    public void execute(Tuple tuple) {
        String word = tuple.getStringByField("word");
		
        //Map中存在就累加,不存在就Put,起始值为1
        if (map.containsKey(word)){
            map.put(word,map.get(word)+1);
        }else {
            map.put(word,1);
        }

        System.out.println(word+"------>"+map.get(word));

    }

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

运行:
在这里插入图片描述

三,深入Storm架构

在这里插入图片描述

  • Nimbus

---- 资源的调度

---- 任务的分配

---- 接收jar包

  • Supervisor

---- 接收zookeeper上Nimbus分配的任务

---- 启动,停止自己管理的worker进程(当前Supervisor上Worker数量由配置文件决定)

  • Worker

---- 运行具体处理运算组件的过程(每个Worker对应执行一个Topology的子集)

---- worker任务类型:Spout类型Bolt类型

---- 启动 executor (executor是Worker JVM中的一个Java进程,一般默认每个executor负责执行一个task任务)

  • Zookeeper

3.1 Storm和MapReduce

MapReduce Storm
主节点 ResurceManager Nimbus
从节点 NodeManager supervisor
应用程序 JobTracker Topolopy
工作进程 YarnChild Worker
计算模型 Map/Reduce Spout/Bolt

3.2 Strom任务提交流程

在这里插入图片描述

  • 客户端将提交的jar包上传至Nimbus服务器 nimbus/inbox目录下

  • 对topology进行一些校验(是否同名),检查Strom的集群状态是否是Active,Spout,Blot的id不能以“__”开头,这种命名方式是系统保留的。

  • 建立topology在本地的存放目录nimbus/stormdist/topology-id,该目录下包含三个文件

    stormjar.jar --从nimbus/inbox目录下移动来的topology的jar包

    stormcode.ser --对topology对象的序列化方法

    stormconf.ser --对topology的运行配置

  • nimbus分配任务,即根据代码初始化spout/bolt的task数目,并分配给对应的task-id,最后将这些信息写入到zk的/task节点下

  • nimbus在zk上创建taskbeats节点,监控task的心跳

  • 将任务分配信息写入到assignment/topology-id节点中,此时即可认为任务提交完毕

  • 在zk的storm/topology-id 节点下存放任务的运行时间,状态等信息

  • Supervisor定期检查zk上的storm节点,是否有新任务提交

  • 删除本地不再运行的任务

  • 根据Nimbus指定的任务信息,启动该节点上的Worker

  • Worker需要查看执行的task任务信息

  • 获取到相应的Task信息,即Spout/Blot任务信息

  • 执行具体运算,并根据Ip以及端口发送消息数据

3.3 Storm目录树

在这里插入图片描述

3.4 Storm Zk目录树

在这里插入图片描述

四,Storm部署

部署Zookeeper集群,可查看本人相关博客

节点:node01,node02,node03

集群规划:node01为nimbus,其余两台为Supervisor

4.1 上传解压

三台集群都需要…

tar -zvxf  ....

4.2 在Storm目录中新建logs目录

存放运行的日志

4.3 修改配置文件

  • strom.yarm
    在这里插入图片描述

三台节点配置文件一致

4.4 启动Storm集群

  • 启动ZK集群
  • 在Node01上启动Nimbus
./bin/storm nimbus >>  ./logs/nimbus.out 2>&1 &
  • 在node02,node03启动supervisor

    ./bin/storm supervisor >> ./logs/supervisor.out 2>&1 &
    

Jps查看进程是否成功启动

4.5 Storm UI启动

在任意一台节点启动即可,我这里选的是node02

./storm ui >> ./logs/ui.out 2>&1 &

通过 node02:8080 访问
在这里插入图片描述

五,Storm Grouping

5.1 Shuffle Grouping

随机分组,随机分派Stream里面的tuple,保证每个bolt task接收到的tuple数目大致相同

轮询,平均分配

5.2 Fields Grouping

按字段分组,比如,按"user-id"这个字段来分组,那么具有同样"user-id"的 tuple 会被分到相同的Bolt里的一个task, 而不同的"user-id"则可能会被分配到不同的task。

5.3 All Grouping

广播发送,对于每一个tuple,所有的Bolt都会收到

5.4 Global Grouping

全局分组,把tuple分配给task id最低的task

5.5 None Grouping

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

5.6 Direct Grouping

指向型分组, 这是一种比较特别的分组方法,用这种分组意味着消息(tuple)的发送者指定由消息接收者的哪个task处理这个消息。只有被声明为 Direct Stream 的消息流可以声明这种分组方法。而且这种消息tuple必须使用 emitDirect 方法来发射。消息处理者可以通过TopologyContext 来获取处理它的消息的task的id(OutputCollector.emit方法也会返回task的id)

5.7 Local or shuffle Grouping

本地或随机分组。如果目标bolt有一个或者多个task与源bolt的task在同一个工作进程中,tuple将会被随机发送给这些同进程中的tasks。否则,和普通的Shuffle Grouping行为一致.

5.8CustomGrouping

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

六,并发机制

在这里插入图片描述

  • Worker - 进程

---- 一个Topology拓扑会包含一个或多个Worker(每个Worker进程只能从属于一个Topology)

---- 这些Worker进程会并行跑在集群中不同的服务器上,即一个Topology拓扑其实是由并行在Strom集群中多台服务器上的进程所组成

  • Executor - 线程

---- Executor是由Worker进程中生成的一个线程

---- 每个Worker进程中会运行拓扑当中的一个或多个Executor线程

---- 一个Executor线程中可以执行一个或多个Task任务(默认每个Executor只执行一个Task任务),但是这些Task任务都是对应着同一个组件(Spout,Bolt)

  • Task

---- 实际执行数据处理的最小单元

---- 每个Task即为一个Spout或者一个Bolt

  • Task数量在整个Topology声明周期中保持不变,Executor数量可以变化或手动调整
  • 默认情况下,Task数量和Executor是相同的,即每个Executor中运行一个Task任务
  • 设置Worker进程数

Config.setNumWorkers(int  workers)
  • 设置Executor线程数
TopologyBuilder.setSpout(String id, IRichSpout spout, Number parallelism_hint)
TopologyBuilder.setBolt(String id, IRichBolt bolt, Number parallelism_hint)
## 其中, parallelism_hint即为executor线程数
  • 设置Task数量
ComponentConfigurationDeclarer.setNumTasks(Number val)
Config conf = new Config() ;
conf.setNumWorkers(2);
TopologyBuilder topologyBuilder = new TopologyBuilder();
topologyBuilder.setSpout("spout", new MySpout(), 1);
topologyBuilder.setBolt("green-bolt", new GreenBolt(), 2)
.setNumTasks(4)
.shuffleGrouping("blue-spout);

在这里插入图片描述

  • Rebalance - 再平衡

---- 即,动态调整拓扑的Worker进程数量,以及Executor线程的数量

  • 支持两种调整方式

---- 通过Storm UI

---- 通过Strom CLI

storm rebalance mytopology -n 5 -e blue-spout=3 -e yellow-bolt=10

将mytopology拓扑worker的个数调整为5个

“blue-spout”所使用的线程数量调整为3个

"yellow-bolt"所使用的线程数量调整为10个

七,通信机制

  • Worker进程间的数据通信

---- ZMQ

ZeroMQ开源的消息传递框架,并不是一个MessageQueue

---- Netty

Netty是基于NIO的网络框架,更加高效(Storm0.9版本之后使用Netty,因为ZMQ的licenese和Storm的license不兼容)

  • Worker内部的数据通信

---- Disruptor

实现了队列的功能

可以理解为一种事件监听或者消息处理机制,即在队列一端生产消息数据,另一边消费者并行取出消息数据来处理
在这里插入图片描述

八,容错机制

8.1 集群节点宕机

-Nimbus服务器

  • 单点故障

-非Nimbus服务器

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

8.2 进程挂掉

-Worker

  • 挂掉时,Superivsor会重新启动这个进程。如果启动过程中仍然一直失败,并且无法向Nimbus发送心跳,Nimbus会将Worker分配到其他服务器上

-Superivsor

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

-Nimbus

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

8.3 消息的完整性

  • 从Spout中发成的Tuple,以及基于它所产生的Tuple

  • 由这些消息就构成了一颗Tuple树

  • 当这棵树发送完成,并且数当中每一条消息都被正确处理,就表明Spout发送的消息被完整处理,即消息的完整性。

  • Acker – 消息完整性的实现机制

---- Storm的拓扑当中特殊的一些任务

---- 负责跟踪每个Spout发出的Tuple的DAG(有向无环图)

猜你喜欢

转载自blog.csdn.net/weixin_43270493/article/details/86766131