大数据-Storm

Storm 流式处理框架

Storm是实时的,分布式,高容错的计算系统。java+cljoure

Storm常驻内存,数据在内存中处理不经过磁盘,数据通过网络传输。

底层java+cljoure构成,阿里使用java重构Storm构建Jstorm。

数据处理分类

  • 流式处理(异步)

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

    • 数据追条处理:数据清洗或分析

    • 例:在数据统计分析中:数据存入队列,storm从MQ获取数据,storm将结果存入存储层

  • 实时请求应答(同步)

    • 客户端提交请求后立即计算并将结果返回给客户端

    • 图片特征提取: 客户端向Storm的drpc发送请求,客户端等待Storm响应结果

Storm与MR的比较

  • Storm流式处理,毫秒级响应,常驻内存

  • MR批处理TB级数据,分钟级响应,反复启停

优势

高可靠:异常处理;消息可靠性保障机制

可维护性:提供UI监控接口

架构模型

  • Nimbus:接收请求,接收jar包并解析,任务分配,资源分配

  • zookeeper:存储Nimbus分配的子任务,存储任务数据,用于解耦架构

  • Supervisor:从zk中获取子任务,启动worker任务,启停所管理的worker

  • Worker:执行具体任务(每个worker对应一个Topology的子集),分为spout任务和bolt任务,启动executor,一个executor对应一个线程,默认一个executor执行一个task任务。任务所需数据从zookeeper中获取。

storm与MR比对MRStorm
主节点 ResourceManager Nimbus
从节点 NodeManager Supervisor
应用程序 Job Topology
工作进程 Child Worker
计算模型 Map/Reduce Spout/Bolt

编程模型

DAG有向无环图,拓扑结构图,作为实时计算逻辑的封装。一旦启动会一直在系统中运行直到手动kill

Spout 数据源节点

  • 从外部读取原始数据

  • 定义数据流,可定义多个数据流向不同Bolt 发数据,读取外部数据源

  • nextTuple:被Storm线程不断调用、主动从数据源拉取数据,再通过emit方法将数据生成Tuple,发送给之后的Bolt计算

Bolt 计算节点

  • execute:该方法接收到一个Tuple数据,真正业务逻辑

  • 定义数据流,将结果数据发出去

API

以wordcount为例:Spout 获取原始行数据,将行数据Tuple作为发给bolt1,bolt1解析行数据为单词数据,bolt1将单词数据转为Tuple发给bolt2,bolt2对单词数据进行计数,将计数结果发送到存储层。

Topology类

指定计算任务的各节点及节点关联,提交任务

  • topologyBuilder的setSpout方法和setBolt方法构建各节点

    • 方法参数为:id值,bolt类,并发度(线程),task数,数据来源节点,数据流,分组字段

    • shuffleGrouping是随机分配数据

    • 多并发度

      • shuffleGrouping是将数据随机分配给线程

      • fieldsGrouping是根据指定的若干字段分组到不同的线程

  • Config对象传入配置参数

    • 超时时间 秒:conf.setMessageTimeoutSecs(3);

  • LocalCluster本地执行,对象提交任务名,参数对象,拓扑对象,最终执行任务

public class MyTopology {
   public static void main(String[] args) {
       //创建拓扑构建对象
       TopologyBuilder topologyBuilder=new TopologyBuilder();
       /**
        * 指定各节点,线程数量,上下游关联
        * 对于多线程,注意上游的结果会随机发给各个线程
        */
       //设置spout节点,指定(id值,spout类,并发度)
       topologyBuilder.setSpout("s1",new CountSpout(),1);
       //设置bolt节点,指定 (id值,bolt类,并发度,数据来源节点,分组字段)
       topologyBuilder.setBolt("b1",new CountBolt1(),2).setNumTasks(4).shuffleGrouping("s1");
       topologyBuilder.setBolt("b2",new CountBolt2(),1).fieldsGrouping("b1",new Fields("word"));
       //构建拓扑对象
       StormTopology stormTopology=topologyBuilder.createTopology();
       //设置配置信息,该对象实际是个map,向其中设置各种参数
       Config config=new Config();
       config.setNumWorkers(3);
       config.put("xxx","xxx");
       //获取本地执行集群对象
       LocalCluster lc=new LocalCluster();
       //本地执行:向集群提交任务,任务名,参数,拓扑节点
       lc.submitTopology("job-count",config,stormTopology);
  }
}

Spout类(自定义)

继承BaseRichSpout类,实际实现了IRichSpout接口,主要重写以下方法

  • open()——初始化,参数为:配置参数, 上下文,输出对象

  • nextTuple()——业务代码

  • declareOutputFields()——定义输出结果

  • 关于数据输出

    将SpoutOutputCollector输出对象转为全局变量,通过SpoutOutputCollector输出数据

    使用Values对象,存入多个结果(相当于一个list)

    通过OutputFieldsDeclarer对象,依次定义每个结果的字段名(相当于给list字段命名,形成类似map的有序集合)

import backtype.storm.topology.base.BaseRichSpout;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Values;
public class CountSpout extends BaseRichSpout {
   /** 将输出对象转为全局属性,支持数据输出 */
   private SpoutOutputCollector collector;
   //初始化
   @Override
   public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
       //将输出对象转为全局属性
       this.collector=collector;
  }
   //业务代码:随机输出一行字符串
   @Override
   public void nextTuple() {
       int index=random.nextInt(lines.length);
       String line= lines[index];
       
       //输出数据,构建value时能够设置多个数据,在Fields对象中定义字段名
       collector.emit(new Values(line,line));
  }
   @Override
   public void declareOutputFields(OutputFieldsDeclarer declarer) {
       //定义输出数据,给每个字段赋予字段名
       declarer.declare(new Fields("line1""line2"));
  }
}

Bolt类(自定义)

继承BaseRichBolt类,实际实现了IRichBolt接口,主要重写以下方法

  • prepare()——初始化

  • execute()——业务代码

  • declareOutputFields()——定义输出字段

  • 关于输出数据,与spout相同

  • 关于输入数据:通过input的各种方法获取对应类型的数据,支持通过字段所在索引下标或字段名获取指定位置的值

public class CountBolt1 extends BaseRichBolt {
/** 将输出对象转为全局属性,支持数据输出 */
   private OutputCollector collector;
   @Override
   public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
       this.collector=collector;
  }
   @Override
   public void execute(Tuple input) {
       /* 不同方法获取不同类型的值,方法中通过索引下标或字段名来取值 */
       //通过索引取值
       String line1=input.getString(0);
       //通过字段名取值
       String line2=input.getStringByField("line1");

       //以下为业务逻辑,将字符串拆成单词数组,遍历输出
       String[] words=line1.split(" ");
       for (String word:words){
           //遍历输出,输出两个字段的数据
           collector.emit(new Values(word,word));
      }
  }
   @Override
   public void declareOutputFields(OutputFieldsDeclarer declarer) {
       declarer.declare(new Fields("word","word2"));
  }
}

分组策略

  • Shuffle Grouping:轮询的方式,随机派发tuple,每个线程接收到数量大致相同。

  • Fields Grouping:按指定的字段分组,指定字段的值相同tuple,分配相同的task(处于同一个线程中)

  • 以下方式了解

    • All Grouping:广播发送,所有下游的bolt线程都会收到。还是task

    • Global Grouping:全局分组, 将tuple分配给 id最低的task,这个id是用户指定的id值。

    • None Grouping:随机分组

    • Direct Grouping:指向型分组

    • local or shuffle grouping:若目标bolt task与源bolt task在同一工作进程中,则tuple随机分配给同进程中的tasks。否则普通的Shuffle分配 Grouping行为一致

    • customGrouping:自定义

分布式搭建

环境搭建

  • 安装

    tar -zxvf apache-storm-0.10.0.tar.gz

    mv apache-storm-0.10.0 storm-0.10.0

  • 修改配置

    vim /opt/sxt/storm-0.10.0/conf/storm.yaml

    #指定zk地址
    storm.zookeeper.servers:
        - "node1"
        - "node2"
        - "node3"
       
    #指定nimbus的节点
    nimbus.host: "node1"

    #指定开放的端口(默认以下4个)
    supervisor.slots.ports:
      - 6700
      - 6701
      - 6702
      - 6703
  • 拷贝到其他节点

    cd /opt/sxt 
    scp -r storm-0.10.0/ node3:`pwd`

启动+命令

启动集群

  • 启动zookeeper集群

    zkServer.sh start

  • 启动nimbus节点 cd /opt/sxt/storm-0.10.0

    • 各节点创建logs目录:mkdir logs

    • 主节点启动:

      • 启动nimbus

        nohup ./bin/storm nimbus >> logs/nimbus.out 2>&1 &

      • 启动监控ui

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

    • 从节点启动supervisor

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

    • 注:项目的日志也就存放中logs目录下的各文件中

      可以远程查看:例如文件wc-1-1571317142-worker-6700.log

      http://node1:8080/topology.html?wc-1-1571317142-worker-6700.log

  • 网页登录设置在主节点上的UI监控地址 http://node1:8080

关闭集群

#关闭nimbus
kill `ps aux | egrep '(daemon.nimbus)|(storm.ui.core)' | fgrep -v egrep | awk '{print $2}'`
#关闭supervisor
kill `ps aux | fgrep storm | fgrep -v 'fgrep' | awk '{print $2}'`
#关闭zk
zkServer.sh stop

运行项目

从IDE中导出项目jar文件(field标签 > project structure > 指定主类创建jar配置 > bulid标签 > build project > 从项目目录中找到jar文件) 导入到/opt/sxt下

载入项目命令:./bin/storm jar /opt/sxt/wc.jar com.shsxt.storm.wordcount.WordCountTP wc

依次指定jar文件目录,主类,topology名

停止项目命令:./bin/storm kill wc (注意指定topology)

内部文件结构

  • 本地目录

    /opt/sxt/storm-0.10.0/storm-local 目录下存放项目执行时的数据

    内部为一个目录树结构

  • zookeeper目录

    进入zk客户端 zkCli.sh

    查看storm的目录 ls /storm 内部时一个目录树

    查看指定的数据 get /xxx/xxx

并发机制

  • worker进程

    • 具体执行任务的进程

    • 一个topology包含若干个worker执行,worker执行topology的子集

    • 一个worker需要绑定一个网络端口,默认一个节点最多运行4个worker,通过配置多于4个端口开放更多worker

  • Executor线程

    • execute是由worker进程创建的线程,worker包含至少一个executor线程

    • 一个线程中能够执行一个或多个task任务,默认一个线程执行一个task

    • spout / boltk 与线程是一对多的关系,一个spout 可以包含多个线程,但线程只对应一个spout,监控线程除外。

  • task

    • 数据处理的最小单元

    • 每个task对应spout或bolt的一个并发度

关于再平衡热rebalance

  • 在topology生命周期中,worker与executor的数量可变,task的数量不可变

  • 多个线程无法同时执行一个task,只能一个线程执行一个task,因此线程数应该小于task数,设置多的线程是无效的。

  • 注意:系统对每个worker都会设置一个监控线程,监控线程数量与worker相同

  • 命令修改

    ./bin/storm/rebalance wc -n 5 -e blue-spout=3 -e yellow-bolt=10

    (调整wc任务 5个worker 3个spout线程 10个bolt线程 )

  • api设置

    • worker数

      通过配置文件制定worker数量

      config.setNumWorkers(3);

    • executor数与task数量

      线程数设置 setBolt(String id, IRichBolt bolt, Number 线程数);和setSpout()方法

      设置任务数 setNumTasks(task数)

      topologyBuilder.setBolt("wcSplit",new WordCountSplit(),2).setNumTasks(4).shuffleGrouping("wcSpout");

数据传输

  • worker之间数据传输使用Netty架构

    • 接收线程监听tcp端口,并将数据存入缓冲区域

    • 发送线程使用传输队列发送数据

  • worker的内部通讯采用Disruptor的队列处理机制

    数据从缓冲区读取经过:接收队列——工作线程——发送队列——发送线程

  • tuple 在Storm中的数据的传输单元

  • Stream 数据连通的管道,多个管道时需要指定Stream的id

线程数包括:spout线程数,blot线程数,acker(每个work1个监控线程)

容错机制

宕机机制

  • nimbus宕机

    • 无状态策略,所有的状态信息都存放在Zookeeper中来管理,worker正常运行,只是无法提交新的topology

    • 快速失败策略:自检到异常后自毁并重启

  • supervisor宕机

    • 无状态策略,supervisor的所有的状态信息都存放在Zookeeper中来管理

    • 快速失败策略:自检到异常后自毁并重启

    • 节点故障状态下,若所有该节点上的任务超时,nimbus会将该节点的task分配给其他的节点

  • worker宕机

    • supervisor会重启worker进程

    • 若worker多次失败且无法向nimbus发送心跳,nimbus会将该worker的分配到其他supervisor上执行。

数据跟踪acker

消息完整性

spout中发出的tuple及其由tuple产生的子tuple,构成一个树形结构,只有当数中的所有消息都被正确处理,才能保证数据的完整性。

为了保证数据的完整性,对于处理失败的tuple需要请求上游的spout重发数据。

消息重发的机制:at least(至少一次),oncely (有且只有一次)

acker机制

acker跟踪每个spout发出的tuple。

acker记录每个trulp的id,数据处理后,通过亦或算法清空id,若id未清空则表明这条数据没有处理完成。

  • 自定义acker类,对数据进行监控

    • 在topology中添加具备acker功能的spout作为acker类

      topologyBuilder.setSpout("myack",new MyAckSpout());

    • 定义acker类

      • 实现IRichSpout接口,重写ack(成功)和fail(失败)方法,对发出的数据进程缓存

      • 设置全局缓存,数据发送时存入缓存,处理成功删除缓存,处理失败,从缓存取数据重发

      public class MyAckSpout implements IRichSpout {
         SpoutOutputCollector collector;
         //数据缓存
         Map<Object,String> map =  new HashMap<>();
         //设置全局缓存id
         long id = 0;
         @Override
         public void nextTuple() {
             //发送数据,并存入消息id
             collector.emit(new Values("你好"),id);
             //数据存入缓存
             map.put(id,line);
             //索引自增处理
             id++; }

         //数据完整处理调用ack方法,参数为消息id索引
         @Override
         public void ack(Object msgId) {
             //将缓存中的数据清除
             map.remove(msgId); }

         //数据没有完整处理调用fail方法,参数为消息id索引
         @Override
         public void fail(Object msgId) {
             //重发消息
             collector.emit(new Values(map.get(msgId)),msgId);
        }
      //省略以下重写方法open,declareOutputFields,getComponentConfiguration,close,activate,deactivate
      }
  • bolt 中代码响应,execute方法中

    • 方法末尾添加数据处理的响应

      处理成功:collection.ack(input)

      处理失败:collection.fail(input) 注意:超时也数据失败

    • 向下游发送数据时,指定acker是否继续跟踪下游数据

      跟踪:collector.emit(input,new Values(split[i]));

      不跟踪:collector.emit(new Values(split[i]));

      public void execute(Tuple input) {
         String xxx = input.getStringByField("aaa");
         //结束跟踪
         collector.emit(new Values(xxx));
         //继续collector.emit(new Values(xxx));
      //处理成功
         collector.ack(input);
         //处理失败,collector.fail(input);
      }

实时计算整合

采集层(flume):实现日志收集,使用负载均衡策略

消息队列(kafka):作用是解耦及不同速度系统缓冲

实时处理单元(Storm):用Storm来进行数据处理,最终数据流入DB中

展示单元(DB+web):数据可视化,使用WEB框架展示

整合kafka

 

 

 

 

 

posted @ 2019-10-19 09:55  小布大佬  阅读(202)  评论(0编辑  收藏  举报