大数据基础学习-10.Storm1.1.1

一、Storm概述


Apache Storm是一个开源的分布式实时大数据处理系统,它是一个真正的流数据框架,实现高频数据和大规模数据实时处理。官网介绍如下。

Why use 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 integrates with the queueing and database technologies you already use. A Storm topology consumes streams of data and processes those streams in arbitrarily complex ways, repartitioning the streams between each stage of the computation however needed. 

学习网站:storm.apache.org

                GitHub: github.com/apache/storm

                https://en.wikipedia.org/wiki/Storm_(event_processor)

1.Storm历史

最早由BackType开发,后被Twitter收购,2011.09开源,后来捐献给Apache,托管在GitHub上,目前互联网中应用非常广泛。


Apache Storm对于实时大数据流处理非常有名。因此,大多数公司都将Storm用作其系统的一个组成部分。一些值得注意的例子如下 。

Twitter - Twitter正在使用ApacheStorm作为其“发布商分析产品”。“发布商分析产品”处理Twitter平台中的每个tweets和点击。 Apache Storm与Twitter基础架构深度集成。

NaviSite - NaviSite正在使用Storm进行事件日志监控/审计系统。系统中生成的每个日志都将通过Storm。Storm将根据配置的正则表达式集检查消息,如果存在匹配,那么该特定消息将保存到数据库。

Wego - Wego是位于新加坡的旅行元搜索引擎。旅行相关数据来自世界各地的许多来源,时间不同。Storm帮助Wego搜索实时数据,解决并发问题,并为最终用户找到最佳匹配。

2.Storm优势

• Storm是开源的,强大的,用户友好的。它可以用于小公司和大公司。

• 允许实时流处理。

• Storm是令人难以置信的快,因为它具有巨大的处理数据的力量。

• Storm在几秒钟或几分钟内执行数据刷新和端到端传送响应取决于问题。它具有非常低的延迟。

• Storm有操作智能。

• Storm提供保证的数据处理,即使群集中的任何连接的节点死或消息丢失。

• 高效,用ZeroMQ作为底层消息队列

• 支持本地模式,可模拟集群所有功能

• Storm可以通过线性增加资源来保持性能,即使在负载增加的情况下。它是高度可扩展的。

在 Storm集群中真正运行topology(任务)的主要有三个实体:工作进程、线程和任务。Storm集群中的每台机器上都可以运行多个工作进程,每个工作进程又可创建多个线程,每个线程可以执行多个任务,任务是真正进行数据处理的实体,spout、bolt就是作为一个或者多个任务的方式执行的。因此,计算任务在多个线程、进程和服务器之间并行进行,支持灵活的水平扩展,用户通过追加机器就能提供并发数进而提高处理能力。

• Storm是容错的,灵活的,可靠的,并且支持任何编程语言。

流处理系统的容错性比批处理系统更难实现。当批处理系统中出现错误时,只需要把失败的部分简单重启即可;但对于流处理系统,出现错误就很难恢复,因为线上许多作业都是7 x 24小时运行的,数据在源源不断地输入。另外,流处理系统面临的另外一个挑战是状态一致性,因为重启后会出现重复数据,并且不是所有的状态操作是幂等的,所以容错性很难实现。

Storm采用了上游数据备份和消息确认的机制,来保障消息在失败之后能够重新处理。Storm的消息确认原理是,每个操作都会把前一次操作处理消息的确认信息返回,同时,Topology的数据源还会备份它生成的所有数据记录。当所有数据记录的处理确认信息收到,备份即会被安全拆除;失败后,如果不是所有的消息处理确认信息被收到,那数据记录会被数据源数据替换。此举能够保障不会发生数据丢失事件,但是,也会带来数据结果重复的问题,这就是at-least once传输机制。

Storm采用了取巧的办法完成容错性。它对每个源数据记录仅仅要求几个字节存储空间来跟踪确认消息,通过纯数据记录消息确认架构。这样做,尽管性能不错,但不能保证exactly once消息传输机制,所有应用开发者都需要处理重复数据Storm存在低吞吐量和流控问题,因为消息确认机制在反压下经常误认为失败。

3.Storm和Hadoop区别

• Storm任务没有结束,Hadoop任务执行完结束

• Storm处理过程(spout、bolt),Hadoop处理过程(map、reduce)

• Storm延时更低

• Hadoop使用磁盘作为中间交换的介质,而Storm的数据是一直在内存中流转的

• Storm的吞吐能力不及Hadoop,不适合批处理

4.Storm和sparkstreaming区别

sparkstreaming是小批次的数据处理,而storm是真正的流数据处理框架。

二、核心概念

http://storm.apache.org/releases/1.2.1/Concepts.html

1.Streams

– 以Tuple为基本单位组成的一条有向无界的数据流,消息流Stream是Storm里的关键抽象。一个消息流是一个没有边界的tuple序列( tuple是一个类似于列表的东西,存储的每个元素叫做field),而这些tuple序列会以一种分布式的方式并行地创建和处理。通过对Streamtuple序列中的字段名来定义Stream

2.Tuple

– tuple支持Integer,long,short,byte,string,double,float,boolean和byte array,也可以自定义类型

3.Topology

– 计算逻辑的封装,将整个spout、bolt、streams、tuple串起来。通过Stream grouping将spouts和bolts连接起来

– 类似MapReduce中的job,但不会结束,除非主动kill

 

Topology任务执行

– Storm jar code.jar MyTopology arg1 arg2,Storm jar负责连接到Nimbus并且上传jar包

    • 运行主类MyTopology, 参数是arg1, arg2;这个类的main函数定义这个topology并且把它提交给Nimbus

4.Spout

– 消息来源,产生数据

– 可指定发送多个Stream流给多个bolt

 

5.Bolt

– 处理消息

    • 如过滤,访问数据库,聚合

– 可以发射多个数据流

    • 以tuple为输入

    • 处理具体的tuple

    • 发射0或多个tuple

6.Stream Grouping

– Shuffle Grouping:随机分组

– Fields Grouping:按指定的field分组

– All Grouping:广播分组

– Global Grouping:全局分组

三、Storm架构


1.Nimbus

– Master Node,负责资源分配和任务调度

– 类似Hadoop里的JobTracker,负责在集群里面分发代码,分配计算任务给Supervisor,并且监控其状态

2.Supervisor

– Worker Node,负责接收nimbus分配的任务,每个工作节点存在一个

– 启动和停止属于自己管理的worker进程(每一个工作进程执行一个Topology的一个子集,一个Topology由运行在很多机器上的很多worker工作进程组成)

• Nimbus和Supervisor之间的所有协调工作都是通过Zookeeper集群完成,之所以说他们是无状态的,就是因为nimbus和supervisor的元信息都存在了zookeeper上。这也就意味着你可以用kill -9结束Nimbus和Supervisor进程,然后再重启它们,就好像什么都没有发生过。这个设计使得Storm异常的稳定。

• Storm的高容错机制还体现在它能够自动处理进程、机器、网络等异常。当一个worker挂掉,那么supervisor会重新启动这个worker。如果它总是启动失败将不能发送心跳到nimbus,那么nimbus将把这个worker任务分配到另一台机器上。当一个节点挂掉,或者与节点间连接的网络出现故障时,分配给这台机器的任务将会超时,那么nimbus将会把任务重新分配到其他机器上。

• Nimbus和Supervisor被设计成快速失败的(当发生任何意外情况时进程将自己结束)和无状态的(所有的状态都存在zookeeper里或者磁盘上)。当启动一个Storm群集时,Nimbus和Supervisor进程将使用进程工具或监视工具来运行一个守护进程。因此,当Nimbus和Supervisor进程挂掉时,守护进程会重启它们就像什么都没发生过(不需要你手动重启)。如果丢失了nimbus节点,worker将会继续运行程序,另外,supervisors 将会继续重启失败的worker。但是,如果一直没有nimbus,当需要的时候worker将不能被分配给其他机器(例如有一台worker机器丢失的情况)。因此,nimbus近乎是一个单点故障,在实践中,这并不是一个大问题,当nimbus节点丢失时,并不会有什么灾难发生。Storm也有计划在未来提高nimbus的高可用性。

3.Worker

– 运行具体处理组件逻辑的进程

– 一个Topology可能会在一个或者多个worker里面执行

– 每个worker是一个物理JVM并且执行整个Topology的一部分

– 采取JDK的Executor

比如,对于并行度是300的topology来说,如果我们使用50个工作进程来执行,那么每个工作进程会处理其中的6个tasks,Storm会尽量均匀的工作分配给所有的worker。

4.Task

– Worker中的每一个spout/bolt的线程称为一个task

– 每一个spout和bolt会被当作很多task在整个集群里执行

– 每一个executor对应到一个线程,在这个线程上运行多个task

– Stream grouping则是定义怎么从一堆task发射tuple到另外一堆task

– 可以调用TopologyBuilder类的setSpout和setBolt来设置并行度(也就是有多少个task同时进行)

四、Storm编程

了解了基本的架构和组件之后,现在编程来模拟spout和bolt的数据处理流程。

1.创建maven工程

采用IDEA工具,创建maven工程,并添加storm依赖,在pom.xml中添加如下。

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <storm.version>1.1.1</storm.version>
</properties>
<dependency>
  <groupId>org.apache.storm</groupId>
  <artifactId>storm-core</artifactId>
  <version>${storm.version}</version>
  <!--<scope>provided</scope>--> 先注释掉,否则程序运行会出现找不到包的问题
</dependency>

2.ISPout核心接口

负责将数据发送到topology处理,storm会跟踪spout发出去的tuple的DAG(ack/fail),如果每个tuple都被成功处理,storm将会返回一个ack信息给spout。ack/fail/nextTuple是在同一个线程中执行的,所以不用考虑线程安全的问题。

public interface ISpout extends Serializable 

核心方法:

void open(Map conf, TopologyContext context, SpoutOutputCollector collector); 初始化操作
void close(); 资源释放操作
void nextTuple(); 发送数据,这是最核心的
void ack(Object msgId);tuple成功处理,storm返回给spout成功消息
void fail(Object msgId);tuple成功处理,storm返回给spout失败消息

实现类:

public interface IRichSpout extends ISpout, IComponent
public abstract class BaseRichSpout extends BaseComponent implements IRichSpout
public class DRPCSpout extends BaseRichSpout
public class ShellSpout implements ISpout 

3.IComponent核心接口(了解)

public interface IComponent extends Serializable 为topology中所有可能的组件提供公用的方法。

核心方法:

void declareOutputFields(OutputFieldsDeclarer declarer); 设定当前tuple名称和outputfielddeclare配合使用。

实现类:

public abstract class BaseComponent implements IComponent 
public abstract class BaseBasicBolt extends BaseComponent implements IBasicBolt

4.IBolt核心接口

public interface IBolt extends Serializable 

接收tuple数据,进行相应处理(filter、join),IBolt会在一个运行的机器上创建,使用Java序列化它,然后提交到主节点(nimbus),nimbus会启动worker反序列化,调用prepare方法,然后才开始处理tuple。

核心方法:

void prepare(Map stormConf, TopologyContext context, OutputCollector collector);
void execute(Tuple input);
void cleanup();

实现类:

public interface IRichBolt extends IBolt, IComponent 
public abstract class BaseRichBolt extends BaseComponent implements IRichBolt 

5.求和操作

首先在pom.xml中添加依赖

    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.4</version>
    </dependency>
package com.immor.storm;

import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import org.apache.storm.utils.Utils;

import java.util.Map;
/**
 * 使用Storm实现积累求和的操作
 */
public class LocalSumStormTopology {
    /**
     * Spout需要继承BaseRichSpout
     * 数据源需要产生数据并发射
     */
    public static class DataSourceSpout extends BaseRichSpout {
        private SpoutOutputCollector collector;
        /**
         * 初始化方法,只会被调用一次
         * @param conf  配置参数
         * @param context  上下文
         * @param collector 数据发射器
         */
        public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
            this.collector = collector;
        }
        int number = 0;
        /**
         * 会产生数据,在生产上肯定是从消息队列中获取数据,这里只是用number模拟
         * 这个方法是一个死循环,会一直不停的执行
         */
        public void nextTuple() {
            this.collector.emit(new Values(++number));
            System.out.println("Spout: " + number);
            // 防止数据产生太快
            Utils.sleep(1000);
        }
        /**
         * 声明输出字段
         * @param declarer
         */
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("num"));
        }
    }
    /**
     * 数据的累积求和Bolt:接收数据并处理
     */
    public static class SumBolt extends BaseRichBolt {
        /**
         * 初始化方法,会被执行一次
         * @param stormConf
         * @param context
         * @param collector
         */
        public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
        }
        int sum = 0;
        /**
         * 其实也是一个死循环,职责:获取Spout发送过来的数据
         * @param input
         */
        public void execute(Tuple input) {
            // Bolt中获取值可以根据index获取,也可以根据上一个环节中定义的field的名称获取(建议使用该方式)
            Integer value = input.getIntegerByField("num");
            sum += value;
            System.out.println("Bolt: sum = [" + sum + "]");
        }
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
        }
    }
    public static void main(String[] args) {

        // TopologyBuilder根据Spout和Bolt来构建出Topology
        // Storm中任何一个作业都是通过Topology的方式进行提交的
        // Topology中需要指定Spout和Bolt的执行顺序
        TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("DataSourceSpout", new DataSourceSpout());
        builder.setBolt("SumBolt", new SumBolt()).shuffleGrouping("DataSourceSpout");

        // 创建一个本地Storm集群:本地模式运行,不需要搭建Storm集群
        LocalCluster cluster = new LocalCluster();
        cluster.submitTopology("LocalSumStormTopology", new Config(), builder.createTopology());
    }
}

6.求词频操作

package com.imooc.bigdata;

import org.apache.commons.io.FileUtils;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;

import java.io.File;
import java.io.IOException;
import java.util.*;
/**
 * 使用Storm完成词频统计功能
 */
public class LocalWordCountStormTopology {

    public static class DataSourceSpout extends BaseRichSpout {
        private SpoutOutputCollector collector;

        public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
            this.collector = collector;
        }
        /**
         * 业务:
         * 1) 读取指定目录的文件夹下的数据:/Users/rocky/data/storm/wc
         * 2) 把每一行数据发射出去
         */
        public void nextTuple() {
            // 获取所有文件
            Collection<File> files = FileUtils.listFiles(new File("/Users/rocky/data/storm/wc"),new String[]{"txt"},true);
            for(File file : files) {
                try {
                    // 获取文件中的所有内容
                    List<String> lines = FileUtils.readLines(file);
                    // 获取文件中的每行的内容
                    for(String line : lines) {
                        // 发射出去
                        this.collector.emit(new Values(line));
                    }
                    // TODO... 数据处理完之后,改名,否则一直重复执行
                    FileUtils.moveFile(file, new File(file.getAbsolutePath() + System.currentTimeMillis()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("line"));
        }
    }
    /**
     * 对数据进行分割
     */
    public static class SplitBolt extends BaseRichBolt {

        private OutputCollector collector;

        public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
        }
        /**
         * 业务逻辑:
         *   line: 对line进行分割,按照逗号
         */
        public void execute(Tuple input) {
            String line = input.getStringByField("line");
            String[] words = line.split(",");

            for(String word : words) {
                this.collector.emit(new Values(word));
           }
        }
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("word"));
        }
    }
    /**
     * 词频汇总Bolt
     */
    public static class CountBolt extends  BaseRichBolt {

        public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
        }
        Map<String,Integer> map = new HashMap<String, Integer>();
        /**
         * 业务逻辑:
         * 1)获取每个单词
         * 2)对所有单词进行汇总
         * 3)输出
         */
        public void execute(Tuple input) {
            // 1)获取每个单词
            String word = input.getStringByField("word");
            Integer count = map.get(word);
            if(count == null) {
                count = 0;
            }
            count ++;
            // 2)对所有单词进行汇总
            map.put(word, count);
            // 3)输出
            System.out.println("~~~~~~~~~~~~~~~~~~~~~~");
            Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
            for(Map.Entry<String,Integer> entry : entrySet) {
                System.out.println(entry);
            }
        }
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
        }
    }
    public static void main(String[] args) {
       // 通过TopologyBuilder根据Spout和Bolt构建Topology
        TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("DataSourceSpout", new DataSourceSpout());
        builder.setBolt("SplitBolt", new SplitBolt()).shuffleGrouping("DataSourceSpout");
        builder.setBolt("CountBolt", new CountBolt()).shuffleGrouping("SplitBolt");
        // 创建本地集群
        LocalCluster cluster = new LocalCluster();
        cluster.submitTopology("LocalWordCountStormTopology",
                new Config(), builder.createTopology());
    }
}

几个注意事项:topology名称不能重复,spout和bolt名称不能以“_”开头。

五、Storm安装配置

上面讲解的storm编程例子,是在IDEA中编写的spout和bolt,模拟了storm数据流处理的过程,上面的代码需要打成jar包后,提交到真正的storm集群中运行才有意义。现在就准备在Linux虚拟机中安装和配置storm。

1.单节点伪分布式

1)下载安装

[root@master src]# wget https://archive.apache.org/dist/storm/apache-storm-1.1.1/apache-storm-1.1.1.tar.gz

[root@master src]# tar -zxvf apache-storm-1.1.1.tar.gz 

2)配置文件

storm-env.sh 

[root@master conf]# vim storm-env.sh ,配置java

export JAVA_HOME=/usr/local/src/jdk1.8.0_144

3)启动

按照顺序启动。

[root@master apache-storm-1.1.1]# storm dev-zookeeper & 

[root@master apache-storm-1.1.1]# storm nimbus &

[root@master apache-storm-1.1.1]#  storm supervisor &

[root@master apache-storm-1.1.1]#  storm ui &

[root@master apache-storm-1.1.1]#  storm logviewer &

正常的话应该有如下的进程

[root@master apache-storm-1.1.1]# jps
43392 dev_zookeeper
43508 nimbus
43896 logviewer
43800 core
43626 Supervisor
44012 Jps

这时候,打开http://192.168.101.10:8080可以查看到storm的UI界面,可以看到nimbus有两个,localhost和master(主机名),这是因为在storm1.X之后,已经做了HA的配置,挂了一个,可以启动另一个;此外默认slot有4个。

2.提交任务

1)代码修改,在上面的编写的代码中,修改main函数

   public static void main(String[] args) {

        // TopologyBuilder根据Spout和Bolt来构建出Topology
        // Storm中任何一个作业都是通过Topology的方式进行提交的
        // Topology中需要指定Spout和Bolt的执行顺序
        TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("DataSourceSpout", new DataSourceSpout());
        builder.setBolt("SumBolt", new SumBolt()).shuffleGrouping("DataSourceSpout");

        // 代码提交到Storm集群上运行
        String topoName = ClusterSumStormTopology.class.getSimpleName();
        try {
            StormSubmitter.submitTopology(topoName,new Config(), builder.createTopology());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

2)代码打包

双击package命令,在target中会产生一个jar包。


将storm-1.0.jar包拷贝到虚拟机上,运行如下命令。

[root@master apache-storm-1.1.1]# storm jar storm-1.0.jar com.immor.storm.ClusterSumStormTopology

com.immor.storm.ClusterSumStormTopology是类的名称,进入到http://192.168.101.10:8080/index.html可以看到信息的变化。

值得注意的是:Executors会占用3个,而显示的spout和bolt各占1个,另外1个隐藏的其实是acker,这个可以算是spout和bolt的作业的监控进程,用来保证作业成功,或者是失败或返回一个消息。

[root@master logstash-2.4.1]# jps
43392 dev_zookeeper
44977 Jps
43508 nimbus
44214 worker
43896 logviewer
43800 core
43626 Supervisor
44202 LogWriter

多了一个worker进程。

3.分布式集群搭建

1)集群规划

每台机器都要搭建hadoop、zookeeper、jdk和storm,前三个的安装配置参考以前博客,这里将master节点作为nimbus,两个slave节点作为supervisor节点。

2)  storm.yaml

注意,格式要严格一样,缺少或者多余空格都会使得配置失败。可以参考网上的yaml的配置要求。

storm.zookeeper.servers:
     - "master"
     - "slave1"
     - "slave2"
storm.local.dir: "/usr/local/src/apache-storm-1.1.1/data"
supervisor.slots.ports:
    - 6700
    - 6701
    - 6702
    - 6703
3)启动相应的进程即可。

六、Storm常用命令

1.storm list

该命令用来查看当前storm集群运行的topology信息

2.停止作业

storm kill topology-name [-w wait-time-secs]

要停止集群的话,只能kill -9(十分暴力)

七、并行度

并行度对于开发出高性能的storm应用来说十分关键。

1.Worker和Task关系


– 1个worker进程执行的是1个topology的子集(注:不会出现1个worker为多个topology服务)。 1个worker进程会启动1个或多个executor线程来执行1个topology的component(spout或bolt)。因此,1个运行中的topology就是由集群中多台物理机上的多个worker进程组成的。

– executor是1个被worker进程启动的单独线程。每个executor只会运行1个topology的1个component(spout或bolt)的task(注:task可以是1个或多个,Storm默认是1个component只生成1个task,executor线程里会在每次循环里顺序调用所有task实例)。

– task是最终运行spout或bolt中代码的单元(注:1个task即为spout或bolt的1个实例,executor线程在执行期间会调用该task的nextTuple或execute方法)。 topology启动后,1个component(spout或bolt)的task数目是固定不变的,但该component使用的executor线程数可以动态调整(例如:1个executor线程可以执行该component的1个或多个task实例)。这意味着,对于1个component存在这样的条件:#threads<=#tasks(即:线程数小于等于task数目)。

默认:1个supervisor节点最多启动4个worker进程,1个topology会占用1个worker进程,1个worker启动1个executor,1个executor启动1个task。

2.代码调整worker和executor

Config conf = new Config();
TopologyBuilder topolopyBuilder = new TopologyBuilder();

conf.setNumWorkers(2);//设置Worker数量
conf.setNumAckers(0);//表示不要ack,正常还是要的,这里作为示范而已
topolopyBuilder.setSpout("BlueSpout", new BlueSpout(), 2);// 设置Executor数量为2
topolopyBuilder.setBolt("GreenBolt", new GreenBolt(), 2).setNumTasks(4).shuffleGrouping("BlueSpout"); // 设置Task数量为4

topolopyBuilder.setBolt("YellowBolt", new YellowBolt(), 6).shuffleGrouping("GreenBolt");
StormSubmitter.submitTopology(topoName,conf, topolopyBuilder.createTopology());

3.命令行调整worker和executor

重新配置Topology "myTopology"使用5个Workers,BlueSpout使用3个Executors,YellowSpout使用10个Executors

# Storm rebalance myTopology -n 5 -e BlueSpout=3 -e YellowSpout=10

总结:一个topology可以通过setNumWorkers来设置worker的数量,通过设置parallelism来规定executor的数量(一个component(spout/bolt)可以由多个executor来执行),通过setNumTasks来设置每个executor跑多少个task(默认为一对一)。task是spout和bolt执行的最小单元。

4.实例

Config conf = new Config();
conf.setNumWorkers(2); // use two worker processes
topologyBuilder.setSpout("blue-spout", new BlueSpout(), 2); // set parallelism hint to 2

topologyBuilder.setBolt("green-bolt", new GreenBolt(), 2)
               .setNumTasks(4)
               .shuffleGrouping("blue-spout");

topologyBuilder.setBolt("yellow-bolt", new YellowBolt(), 6)
               .shuffleGrouping("green-bolt");

StormSubmitter.submitTopology("mytopology", conf, topologyBuilder.createTopology()   );

## Reconfigure the topology "mytopology" to use 5 worker processes,
## the spout "blue-spout" to use 3 executors and
## the bolt "yellow-bolt" to use 10 executors.

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

八、stormgrouping

1.shuffle grouping

随机分组,比较常用

2.fields grouping

按指定的field分组,首先在spout中,定义一个新的字段

   public void nextTuple() {
            this.collector.emit(new Values(number%2, ++number));
            System.out.println("Spout: " + number);
            Utils.sleep(1000);
        }
#这里就可以实现奇偶数分开
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("flag","num"));
        }
   public static void main(String[] args) {
        TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("DataSourceSpout", new DataSourceSpout());
        builder.setBolt("SumBolt", new SumBolt(), 3)
                .fieldsGrouping("DataSourceSpout", new Fields("flag"));
        // 代码提交到Storm集群上运行
        String topoName = ClusterSumFieldGroupingStormTopology.class.getSimpleName();
        try {
            StormSubmitter.submitTopology(topoName,new Config(), builder.createTopology());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
测试就会发现,因为只要区分奇偶数就可以,所以虽然设置的线程是3个,但是实际上只有2个线程在工作。

3.Partial Key 


4.All grouping

有几个线程就会发送到几个上面,也就是说分发到所有bolt上的数据完全一样,每个bolt做同样的数据处理,没有多大的意义。

5.Global grouping


6.None grouping


7.Direct grouping


8.Local or shuffle grouping


九、Storm容错

1.架构容错

• Zookeeper

    – 存储Nimbus与Supervisor数据,能够实现快速失败机制

• Nimbus/Supervisor宕机

    – Worker继续工作,只是不能够再提交任务,并且worker需要分配到其他节点上时,无法实现

    – Worker失败,任务失败

• Worker挂掉

    – Supervisor重启Worker

• 节点挂掉

   –  跑在节点上的任务就会超时,nimbus就会重新将这个任务分到其他节点上

2.数据容错

• Storm的可靠性是指Storm会告知用户每一个消息单元是否在一个指定的时间(timeout)内被完全处理。

• Ack机制(Storm中的每一个Topology中都包含有一个Acker组件),所有的节点ack成功,任务成功

3.特殊的Task(Acker Bolt)

– Acker,跟踪每一个spout发出的tuple树,一个tuple树完成时,发送消息给tuple的创造者

– Acker的数量,默认值是1,本质是一个task,非常轻量

– Acker Task并不显式的跟踪tuple树。 对于那些有成千上万个节点的tuple树,把这么多的tuple信息都跟踪起来会耗费太多的内存。

4.Acker实现

–内存量是恒定的(20bytes),对于100万tuple,也才20M 左右

– Taskid:ackval

– Ackval所有创建的tupleid/ack的tuple一起异或

• 一个acker task存储了一个spout-tuple-id到一对值的一个mapping。这个对子的第一个值是创建这个tuple的taskid,这个是用来在完成处理tuple的时候发送消息用的。第二个值是一个64位的数字称作:ack val , ack val是整个tuple树的状态的一个表示,不管这棵树多大。它只是简单地把这棵树上的所有创建的tupleid/ack的tupleid一起异或(XOR)。



说明这个方法之前,我们来复习一个数学定理。

A xor A = 0.

A xor B…xor B xor A = 0,其中每一个操作数出现且仅出现两次。

Storm中使用的巧妙方法就是基于这个定理。具体过程是这样的:在spout中系统会为用户指定的message id生成一个对应的64位整数,作为一个root id。root id会传递给acker及后续的bolt作为该消息单元的唯一标识。同时无论是spout还是bolt每次新生成一个tuple的时候,都会赋予该tuple一个64位的整数的id。Spout发射完某个message id对应的源tuple之后,会告知acker自己发射的root id及生成的那些源tuple的id。而bolt呢,每次接受到一个输入tuple处理完之后,也会告知acker自己处理的输入tuple的id及新生成的那些tuple的id。Acker只需要对这些id做一个简单的异或运算,就能判断出该root id对应的消息单元是否处理完成了。下面通过一个图示来说明这个过程。


spout中绑定message 1生成了两个源tuple,id分别是0010和1011.


bolt1处理tuple 0010时生成了一个新的tuple,id为0110.


bolt2处理tuple 1011时生成了一个新的tuple,id为0111.


bolt3中接收到tuple 0110和tuple 0111,没有生成新的tuple.

容错过程存在一个可能出错的地方,那就是,如果生成的tuple id并不是完全各异的,acker可能会在消息单元完全处理完成之前就错误的计算为0。这个错误在理论上的确是存在的,但是在实际中其概率是极低极低的,完全可以忽略。

下面通过acker机制,来实现自己的业务逻辑,这里举个简单的例子,只判断一个数是否是大于10的,如果是就通过fail报错。代码如下。

package com.imooc.bigdata;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import org.apache.storm.utils.Utils;
import java.util.Map;

public class LocalSumStormAckerTopology {
    public static class DataSourceSpout extends BaseRichSpout {
        private SpoutOutputCollector collector;
        public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
            this.collector = collector;
        }
        int number = 0;
        public void nextTuple() {
            ++number;
            /**
             * emit方法有两个参数:
             *  1) 数据
             *  2) 数据的唯一编号 msgId    如果是数据库,msgId就可以采用表中的主键
             */
            this.collector.emit(new Values(number), number);
            System.out.println("Spout: " + number);
            Utils.sleep(1000);
        }
        @Override
        public void ack(Object msgId) {
            System.out.println(" ack invoked ..." + msgId);
        }
        @Override
        public void fail(Object msgId) {
            System.out.println(" fail invoked ..." + msgId);
            // TODO... 此处对失败的数据进行重发或者保存下来
            // this.collector.emit(tuple)
            // this.dao.saveMsg(msgId)
        }
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("num"));
        }
    }
    public static class SumBolt extends BaseRichBolt {
        private OutputCollector collector;
        public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
        }
        int sum = 0;
        public void execute(Tuple input) {
            // Bolt中获取值可以根据index获取,也可以根据上一个环节中定义的field的名称获取(建议使用该方式)
            Integer value = input.getIntegerByField("num");
            sum += value;
            // 假设大于10的就是失败
            if(value > 0 && value <= 10) {
                this.collector.ack(input); // 确认消息处理成功
            } else {
                this.collector.fail(input);  // 确认消息处理失败
            }

//            try {
//                // TODO... 你的业务逻辑
//                this.collector.ack(input);
//            } catch (Exception e) {
//                this.collector.fail(input);
//            }
            System.out.println("Bolt: sum = [" + sum + "]");
        }
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
        }
    }
    public static void main(String[] args) {
        TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("DataSourceSpout", new DataSourceSpout());
        builder.setBolt("SumBolt", new SumBolt()).shuffleGrouping("DataSourceSpout");
        LocalCluster cluster = new LocalCluster();
        cluster.submitTopology("LocalSumStormAckerTopology", new Config(),
                builder.createTopology());
    }
}

十、整合其他大数据框架

1.整合Redis

首先通过http://download.redis.io/releases/redis-3.0.6.tar.gz下载Redis3.0.6,进入目录并执行make命令。
接着添加依赖,在pom.xml添加如下。
<dependency>
  <groupId>org.apache.storm</groupId>
  <artifactId>storm-redis</artifactId>
  <version>${storm.version}</version>
  <type>jar</type>
</dependency>
[root@master redis-3.0.6]# netstat -tln | grep 6379
[root@master redis-3.0.6]# lsof -i :6379
[root@master redis-3.0.6]# kill -9 84405

这个命令可以查看哪些端口被占用,以及如何释放这个端口。

[root@master ~]# ps -ef | grep redis

查看下Redis端口。下面应用Redis的store,和storm进行整合。

http://storm.apache.org/releases/1.1.2/storm-redis.html

package com.immor.storm;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.redis.bolt.RedisStoreBolt;
import org.apache.storm.redis.common.config.JedisPoolConfig;
import org.apache.storm.redis.common.mapper.RedisDataTypeDescription;
import org.apache.storm.redis.common.mapper.RedisStoreMapper;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.ITuple;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import org.apache.storm.utils.Utils;
import java.util.*;

public class LocalSumStormTopology {
    public static class DataSourceSpout extends BaseRichSpout {
        private SpoutOutputCollector collector;
        public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
            this.collector = collector;
        }
        public static final String[] words = new String []{"apple","orange","banana"};
        public void nextTuple() {
            Random random = new Random();
            String word = words[random.nextInt(words.length)];
            this.collector.emit(new Values(word));
            System.out.println("emit:"+word);
            Utils.sleep(1000);
        }
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("line"));
        }
    }
    public static class SplitBolt extends BaseRichBolt {
        private OutputCollector collector;
        public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
        }
        public void execute(Tuple input) {
            String word = input.getStringByField("line");
            this.collector.emit(new Values(word));
        }
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("word"));
        }
    }
    public static class CountBolt extends  BaseRichBolt {
        private OutputCollector collector;
        public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
        }
        Map<String,Integer> map = new HashMap<String, Integer>();
        public void execute(Tuple input) {
            // 1)获取每个单词
            String word = input.getStringByField("word");
            Integer count = map.get(word);
            if(count == null) {
                count = 0;
            }
            count ++;
            map.put(word, count);
            this.collector.emit(new Values(word,map.get(word)));
        }
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("word","count"));
        }
    }
    public static class WordCountStoreMapper implements RedisStoreMapper {
        private RedisDataTypeDescription description;
        private final String hashKey = "wc";
        public WordCountStoreMapper() {
            description = new RedisDataTypeDescription(
                    RedisDataTypeDescription.RedisDataType.HASH, hashKey);
        }
        public RedisDataTypeDescription getDataTypeDescription() {
            return description;
        }
        public String getKeyFromTuple(ITuple tuple) {
            return tuple.getStringByField("word");
        }
        public String getValueFromTuple(ITuple tuple) {
            return tuple.getIntegerByField("count")+"";
        }
    }
    public static void main(String[] args) {
        // 通过TopologyBuilder根据Spout和Bolt构建Topology
        TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("DataSourceSpout", new DataSourceSpout());
        builder.setBolt("SplitBolt", new SplitBolt()).shuffleGrouping("DataSourceSpout");
        builder.setBolt("CountBolt", new CountBolt()).shuffleGrouping("SplitBolt");
        JedisPoolConfig poolConfig = new JedisPoolConfig.Builder()
                .setHost("192.168.101.10").setPort(6379).build();
        RedisStoreMapper storeMapper = new WordCountStoreMapper();
        RedisStoreBolt storeBolt = new RedisStoreBolt(poolConfig, storeMapper);
        builder.setBolt(" RedisStoreBolt", storeBolt).shuffleGrouping("CountBolt");
        // 创建本地集群
        LocalCluster cluster = new LocalCluster();
        cluster.submitTopology("LocalWordCountStormTopology",new Config(), builder.createTopology());
    }
}

2.整合JDBC

首先添加pom.xml依赖,加入如下的代码【注:如果添加失败,可以手动删除repository目录下的该依赖,然后重新导入】

<dependency>
      <groupId>org.apache.storm</groupId>
      <artifactId>storm-jdbc</artifactId>
      <version>1.1.1</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.31</version>
    </dependency>

打开Linux虚拟机上的mysql,不清楚mysql安装和设置的,参考hive博客中mysql部分。

[root@master ~]# mysql -uroot -p123456

接下来创建storm数据库,和wc表。

mysql> create database storm;
mysql> use storm
mysql> create table wc(
    ->     word varchar(20),
    ->     word_count int  ) ;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值