Storm简介

  1. Storm
    1.1. 概述
    1.1.1. 概述
    Storm是一个开源的分布式实时计算系统,可以简单、可靠的处理大量的数据流。Storm有很多使用场景:如实时分析,在线机器学习,持续计算,分布式RPC,ETL等等。Storm支持水平扩展,具有高容错性,保证每个消息都会得到处理,而且处理速度很快(在一个小集群中,每个结点每秒可以处理数以百万计的消息)。Storm的部署和运维都很便捷,而且更为重要的是可以使用任意编程语言来开发应用。
    1.2. 结构
    1.2.1. 结构
    storm结构称为topology(拓扑),由stream(数据流),spout(喷嘴-数据流的生成者),bolt(阀门-数据流运算者)组成(参考图:Storm组成结构)。不同于Hadoop中的job,Storm中的topology会一直运行下去,除非进程被杀死或取消部署。
    图-Storm组成结构:

1.3. Stream
1.3.1. Stream
Storm的核心数据结构是tuple(元组),本质上是包含了一个或多个键值对的列表。Stream是由无限制的tuple组成的序列。
1.4. spout
1.4.1. spout
spout连接到数据源,将数据转化为一个个的tuple,并将tuple作为数据流进行发射。开发一个spout的主要工作就是利用API编写代码从数据源消费数据流。
1.4.2. spout的数据源可以有很多种来源:
web或者移动程序的点击流
社交网络的信息
传感器收集到的数据
应用程序产生的日志信息

spout通常只负责转换数据、发射数据,通常不会用于处理业务逻辑,从而可以很方便的实现spout的复用。

1.5. bolt
1.5.1. bolt
bolt主要负责数据的运算,将接收到的数据实施运算后,选择性的输出一个或多个数据流。一个bolt可以接收多个由spout或其他bolt发射的数据流,从而可以组建出复杂的数据转换和处理的网络拓扑结构。
1.5.2. bolt常见的典型功能:
过滤
连接和聚合
计算
数据库的读写
2. 入门案例
2.1. 案例结构
2.1.1. 案例结构
案例:Word Count案例
语句Spout --> 语句分隔Bolt --> 单词计数Bolt --> 上报Bolt
2.1.2. 语句生成Spout - SentenceSpout
作为入门案例,我们直接从一个数组中不断读取语句,作为数据来源。SentenceSpout不断读取语句将其作为数据来源,组装成单值tuple(键名sentence,键值为祖父穿格式的语句)向后发射。{“sentence”:“i am so shuai!”}

代码:
/**

  • BaseRichSpout类是ISpout接口和IComponent接口的一个简便的实现。采用了适配器模式,对用不到的方法提供了默认实现。
    */
    public class SentenceSpout extends BaseRichSpout {
    private SpoutOutputCollector collector;
    private String [] sentences = {
    “i am so shuai”,
    “do you like me”,
    “are you sure you do not like me”,
    “ok i am sure”
    };
    private int index = 0;

    /**

    • ISpout接口中定义的方法
    • 所有Spout组件在初始化时都会调用这个方法。
    • map 包含了Storm配置信息
    • context 提供了topology中的组件信息
    • collector 提供了发射tuple的方法
      */
      @Override
      public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
      this.collector = collector;
      }

    /**

    • 覆盖自BaseRichSpout中的方法
    • 核心方法
    • Storm通过调用此方法向发射tuple
      */
      @Override
      public void nextTuple() {
      this.collector.emit(new Values(sentences[index]));
      index = (index + 1 >= sentences.length) ? 0 : index+1;
      Utils.sleep(1000);
      }

    /**

    • IComponent接口中定义的方法
    • 所有的Storm组件(spout、bolt)都要实现此接口。
    • 此方法告诉Storm当前组件会发射哪些数据流,每个数据流中的tuple中包含哪些字段。
      */
      @Override
      public void declareOutputFields(OutputFieldsDeclarer declarer) {
      declarer.declare(new Fields(“sentence”));
      }
      }

2.1.3. 语句分隔Bolt – SplitSenetenceBolt
语句分隔Bolt订阅SentenceSpout发射的tuple,每接收到一个tuple就获取"sentence"对应的值,然后将得到的语句按照空格切分为一个个单词。然后将每个单词向后发射一个tuple。
{“word”:“I”}
{“word”:“am”}
{“word”:“so”}
{“word”:“shuai”}
代码
/**

  • BaseRichBolt 是IComponent 和 IBolt接口的一个简单实现。采用了适配器模式,对用不到的方法提供了默认实现。
    */
    public class SplitSentenceBolt extends BaseRichBolt {
    private OutputCollector collector;

    /**

    • 定义在IBolt中的方法
    • 在bolt初始化时调用,用来初始化bolt
    • stormConf 包含了Storm配置信息
    • context 提供了topology中的组件信息
    • collector 提供了发射tuple的方法
      */
      @Override
      public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
      this.collector = collector;
      }

    /**

    • 覆盖自BaseRichBolt中的方法
    • 核心方法
    • Storm通过调用此方法向发射tuple
      */
      @Override
      public void execute(Tuple input) {
      String sentence = input.getStringByField(“sentence”);
      String [] words = sentence.split(" ");
      for(String word : words){
      this.collector.emit(new Values(word));
      }
      }

    /**

    • IComponent接口中定义的方法
    • 所有的Storm组件(spout、bolt)都要实现此接口。
    • 此方法告诉Storm当前组件会发射哪些数据流,每个数据流中的tuple中包含哪些字段。
      */
      @Override
      public void declareOutputFields(OutputFieldsDeclarer declarer) {
      declarer.declare(new Fields(“word”));
      }

}

2.1.4. 单词计数Bolt – WordCountBolt
单词计数Bolt订阅SplitSentenceBolt的输出,保存每个特定单词出现次数,当接收到一个新的tuple,会将对应单词计数加一,并向后发送该单词的当前计数。{“word”:“I”,“count”:3}
代码:
public class WordCountBolt extends BaseRichBolt {
private OutputCollector collector = null;
private HashMap<String,Long> counts = null;

/**
 * 注意:
 * 	所有的序列化操作最好都在prepare方法中进行
 * 原因:
 * 	Storm在工作时会将所有的bolt和spout组件先进行序列化,然后发送到集群中,如果在序列化之前创建过任何无法序列化的对象都会造成序列化时抛出NotSerializableException。
 * 此处的HashMap本身是可以序列化的所以不会有这个问题,但是有必要养成这样的习惯 。
 */
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
	this.collector = collector;
	this.counts = new HashMap<String,Long>();
}

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

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

}

2.1.5. 上报Bolt – ReportBolt
上报Bolt订阅WordCountBolt类的输出,内部维护一份所有单词的对应计数的表,当接收到一个tuple时,上报Bolt会更新表中的计数数据,并将值打印到终端。
代码:
/**

  • 此Bolt处于数据流的末端,所以只接受tuple而不发射任何数据流。
    */
    public class ReprotBolt extends BaseRichBolt {

    private HashMap<String,Long>counts = null;

    @Override
    public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
    this.counts = new HashMap<String,Long>();
    }

    @Override
    public void execute(Tuple input) {
    String word = input.getStringByField(“word”);
    Long count = input.getLongByField(“count”);
    this.counts.put(word, count);
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
    //处于流末端的tuple,没有任何输出数据流,所以此方法为空
    }

    /**

    • Storm会在终止一个Bolt之前调用此方法。
    • 此方法通常用来在Bolt退出之前释放资源。
    • 此处我们用来输出统计结果到控制台。
    • 注意:真正集群环境下,cleanup()方法是不可靠的,不能保证一定执行,后续会讨论。
      */
      @Override
      public void cleanup() {
      System.out.println(“------统计结果------”);
      List keys = new ArrayList();
      keys.addAll(this.counts.keySet());
      Collections.sort(keys);
      for(String key : keys){
      System.out.println(key + " : " +this.counts.get(key));
      }
      System.out.println(“------------------”);
      }
      }
      2.1.6. 单词计数Topology
      通过main方法组装处理流程。此处我们使用单机模式来测试。
      代码:
      public class WordCountTopology {
      private static final String SENTENCE_SPOUT_ID = “sentence-spout”;
      private static final String SPLIT_BOLT_ID = “split-bolt”;
      private static final String COUNT_BOLT_ID = “count-bolt”;
      private static final String REPORT_BOLT_ID = “report-bolt”;
      private static final String TOPOLOGY_NAME = “word-count-topology”;

    public static void main(String[] args) throws Exception {
    //–实例化Spout和Bolt
    SentenceSpout spout = new SentenceSpout();
    SplitSentenceBolt splitBolt = new SplitSentenceBolt();
    WordCountBolt countBolt = new WordCountBolt();
    ReprotBolt reportBolt = new ReprotBolt();

     //--创建TopologyBuilder类实例
     TopologyBuilder builder = new TopologyBuilder();
     
     //--注册SentenceSpout
     builder.setSpout(SENTENCE_SPOUT_ID, spout);
     //--注册SplitSentenceBolt,订阅SentenceSpout发送的tuple
     //此处使用了shuffleGrouping方法,此方法指定所有的tuple随机均匀的分发给SplitSentenceBolt的实例。
     builder.setBolt(SPLIT_BOLT_ID, splitBolt).shuffleGrouping(SENTENCE_SPOUT_ID);
     //--注册WordCountBolt,,订阅SplitSentenceBolt发送的tuple
     //此处使用了filedsGrouping方法,此方法可以将指定名称的tuple路由到同一个WordCountBolt实例中
     builder.setBolt(COUNT_BOLT_ID, countBolt).fieldsGrouping(SPLIT_BOLT_ID, new Fields("word"));
     //--注册ReprotBolt,订阅WordCountBolt发送的tuple
     //此处使用了globalGrouping方法,表示所有的tuple都路由到唯一的ReprotBolt实例中
     builder.setBolt(REPORT_BOLT_ID, reportBolt).globalGrouping(COUNT_BOLT_ID);
     
     //--创建配置对象
     Config conf = new Config();
     
     //--创建代表集群的对象,LocalCluster表示在本地开发环境来模拟一个完整的Storm集群
     //本地模式是开发和测试的简单方式,省去了在分布式集群中反复部署的开销
     //另外可以执行断点调试非常的便捷
     LocalCluster cluster = new LocalCluster();
     
     //--提交Topology给集群运行
     cluster.submitTopology(TOPOLOGY_NAME, conf, builder.createTopology());
     
     //--运行10秒钟后杀死Topology关闭集群
     Thread.sleep(1000 * 10);
     cluster.killTopology(TOPOLOGY_NAME);
     cluster.shutdown();
    

    }
    }

  1. Storm的并发机制
    3.1. Storm集群中的topology的组成部分
    3.1.1. 由四部分组成
    (1)Nodes–服务器:配置在Storm集群中的一个服务器,会执行Topology的一部分运算,一个Storm集群中包含一个或者多个Node
    (2)Workers–JVM虚拟机、进程:指一个Node上相互独立运作的JVM进程,每个Node可以配置运行一个或多个worker。一个Topology会分配到一个或者多个worker上运行。
    (3)Executeor–线程:指一个worker的jvm中运行的java线程。多个task可以指派给同一个executer来执行。除非是明确指定,Storm默认会给每个executor分配一个task。
    (4)Task–bolt/spout实例:task是sqout和bolt的实例,他们的nextTuple()和execute()方法会被executors线程调用执行。

大多数情况下,除非明确指定,Storm的默认并发设置值是1。即,一台服务器(node),为topology分配一个worker,每个executer执行一个task。参看图(Storm默认并发机制)。此时唯一的并发机制出现在线程级。在单机模式下增加并发的方式可以体现在分配更多的worker和executer给topology。单机模式下,增加Node的数量不会有任何提升速度的效果。
3.2. 增加worker
3.2.1. 增加worker
可以通过API和修改配置两种方式修改分配给topology的woker数量。
Config config = new Config();
config.setNumWorkers(2);
3.3. 增加Executor
3.3.1. 增加Executor
builder.setSpout(spout_id,spout,executor_num)
builder.setBolt(bolt_id,bolt,executor_num)
3.4. 增加Task
3.4.1. 增加Task
builder.setSpout(…).setNumTasks(task_num);
builder.setBolt(…).setNumTasks(task_num);
3.5. 数据流分组
3.5.1. 数据流分组
数据流分组方式定义了数据如何进行分发。
3.6. Storm内置数据流分组方式
3.6.1. Storm内置数据流分组方式
(1)Shuffle Grouping(随机分组)
随机分发数据流中的tuple给bolt中的各个task,每个task接收到的tuple数量相同。
(2)Fields Grouping(按字段分组)
根据指定字段的值进行分组。指定字段具有相同值的tuple会路由到同一个bolt中的task中。
(3)All Grouping(全复制分组)
所有的tuple赋值后分发给所有的bolt task。
(4)Globle Grouping(全局分组)
这种分组方式将所有的tuple路由到唯一一个task上,Storm按照最小task id来选取接受数据的task。
这种分组方式下配置bolt的task的并发度没有意义。
这种方式会导致所有tuple都发送到一个JVM实例上,可能会引起Strom集群中某个JVM或者服务器出现性能瓶颈或崩溃。
(5)None Grouping(不分组)
在功能上和随机分组相同,为将来预留。
(6)Direct Grouping(指向型分组)
数据源会通过emitDirect()方法来判断一个tuple应该由哪个Strom组件来接受。只能在声明了是指向型数据流上使用。
(7)Local or shuffle Grouping(本地或随机分组)
和随机分组类似,但是,会将tuple分发给同一个worker内的bolt task,其他情况下采用随机分组方式。
这种方式可以减少网络传输,从而提高topology的性能。

另外可以自定义数据流分组方式
写类实现CustomStreamGrouping接口

代码:
/**

  • 自定义数据流分组方式
  • @author park

*/
public class MyStreamGrouping implements CustomStreamGrouping {

/**
 * 运行时调用,用来初始化分组信息
 * context:topology上下文对象
 * stream:待分组数据流属性
 * targetTasks:所有待选task的标识符列表
 * 
 */
@Override
public void prepare(WorkerTopologyContext context, GlobalStreamId stream, List<Integer> targetTasks) {
	
}

/**
 * 核心方法,进行task选择
 * taskId:发送tuple的组件id
 * values:tuple的值
 * 返回值要发往哪个task
 */
@Override
public List<Integer> chooseTasks(int taskId, List<Object> values) {
	return null;
}

}
4. Storm的可靠性
4.1. Storm的可靠行
4.1.1. Storm的可靠行
Storm提供了数据流处理时的可靠性,所谓的可靠性是指spout发送的每个tuple都能够执行完整的处理过程。
4.2. spout的可靠性
4.2.1. spout的可靠性
spout需要记录它发射出去的tuple,当下游bolt处理tuple或子tuple失败时,spout能够重新发射该tuple。bolt在处理tuple或子tuple时,无论是否成功都需要向上游节点进行报告或者报错。而在ISpout接口中定义了三个可靠性相关的API:
nextTuple
ack
fail
每个bolt都要向上一级负责报告自己的处理结果,如果spout的直接子bolt,都向spout进行了确认应答,表明后续处理都完成,则spout会调用ack方法来表明该消息已经完全处理了。如果任何一个bolt处理tuple报错,或者处理超时,spout会调用fail方法。
4.3. bolt的可靠性
4.3.1. bolt的可靠性
在发射衍生的tuple时,需要锚定下一级的tuple。子bolt处理消息成功或者失败时分别发送确认应答或者报错给父tuple或父spout。锚定的意思是建立tuple和衍生出的tuple之间的对应关系,这样下游的bolt可以通过应答确认/报错或超时来加入到tuple树结构中来。
collector.emit(tuple,new Values(word));

注意:此方法具有重载的方法,不锚定子节点,这种方式不会进行锚定操作,非锚定的tuple不会对数据流的可靠性起作用,如果一个非锚定的tuple在下游处理失败,原始的根tuple不会重新发送。
collector.emit(new Values(word));
当处理完成或者发送了新tuple之后,可靠数据流中的bolt需要应答读入的tuple:
this.collector.ack(tuple);
如果处理失败,则spout必须发射tuple,bolt就要明确的处理失败的tuple报错:
this.collector.fail(tuple);
如果因为超时的原因,或者显示调用OutputCollector.fail()方法,spout都会重新发送原始tuple。

  1. 安装配置Storm集群
    5.1. 概述
    5.1.1. 概述
    Storm集群遵循主/从结构。
    Storm的主节点是半容错的。
    Strom集群由一个主节点(nimbus)和一个或者多个工作节点(supervisor)组成。
    除此之外Storm集群还需要一个ZooKeeper的来进行集群协调。
    5.2. nimbus
    5.2.1. nimbus
    nimbus守护进程主要的责任是管理,协调和监控在集群上运行的topology。
    包括topology的发布,任务的指派,事件处理失败时重新指派任务。

将topology发布到Storm集群,将预先打包成jar文件的topology和配置信息提交到nimbus服务器上,一旦nimbus接收到topology的压缩包,会将jar包分发到足够数量的supervisor节点上,当supervisor节点接收到了topology压缩文件,nimbus就会指派task到每个supervisor并且发送信号指示supervisor生成足够的worker来执行指派的task。

nimbus记录所有的supervisor节点的状态和分配给他们的task,如果nimbus发现某个supervisor没有上报心跳或者已经不可达了,他将会将故障supervisor分配的task重新分配到集群中的其他supervisor节点。

nimbus并不参与topology的数据处理过程,只是负责topology的初始化、任务分发和进行监控。因此,即使nimbus守护进程在topology运行时停止了,只要分配的supervisor和worker健康运行,topology会一直继续处理数据,所以称之为半容错机制。
5.3. supervisor
5.3.1. supervisor
supervisor守护进程等待nimbus分配任务后生成并监控workers执行任务。

supervisor和worker都是运行在不同的JVM进程上,如果supervisor启动的worker进程因为错误异常退出,supervisor将会尝试重新生成新的worker进程。
5.4. 安装
5.4.1. 安装
安装JDK

安装zookeeper集群

安装Storm
解压安装包即可
配置Storm
修改$STORM_HOME/conf目录下的storm.yaml文件。

5.5. 必须修改的项:
5.5.1. 必须修改的项
storm.zookeeper.services:配置zookeeper集群的主机名称。

nimbus.host:指定了集群中nimbus的节点。

supervisor.slots.ports:配置控制每个supervisor节点运行多少个worker进程。这个配置定义为worker监听的端口的列表,监听端口的个数控制了supervisor节点上有多少个worker的插槽。默认的storm使用6700~6703端口,每个supervisor节点上有4个worker插槽。

storm.local.dir:storm工作时产生的工作文件存放的位置,注意,要避免配置到/tmp下。

5.5.2. 可选的常用修改项
nimbus.childopts(default: -Xms1024m):这项JVM配置会添加在启动nimbs守护进程的java命令行中。

ui.port(default:8080):这项配置指定了Storm UI的Web服务器监听的端口。

ui.childopts(default:-Xms1024m):这项JVM配置会添加在StormUI服务启动的Java命令行中。

supervisor.childopts(default:-Xms768m):这项JVM配置会添加Supervisor服务启动的Java命令行中。

worker.childopts(default:-Xms768m):这项JVM配置会添加worker服务启动的Java命令行中。

topology.message.timeout.secs(default:30):这个配置项定义了一个tuple树需要应答最大时间秒数限制,超过这个时间则认为超时失败。

topology.max.spout.pending(default:null):在默认值null的情况下,spout每当产生新的tuple时会立即向后端发送,由于下游bolt执行可能具有延迟,可能导致topology过载,从而导致消息处理超时。如果手动将该值改为非null正整数时,会通过暂停spout发送数据来限制同时处理的tuple不能超过这个数,从而达到为Spout限速的作用。

topology.enable.message.timeouts(default:true):这个选项用来锚定的tuple的超时时间。如果设置为false,则锚定的tuple不会超时。
6. Storm命令
6.1. 启动命令
6.1.1. 启动命令
在启动storm之前确保storm使用的zookeeper已经启动且可以使用
storm nimbus 启动nimbus守护进程
storm supervisor 启动supervisor守护进程
storm ui 启动stormui的守护进程,从而可以通过webUI界面来监控storm运行过程
storm drpc 启动一个DRPC服务守护进程
6.2. 管理命令
6.2.1. 管理命令
storm jar topology_jar topology_class[arguments…] 向集群提交topology。它会使用指定的参数运行topology_class中的main()方法,同时上传topology_jar文件到nimbus以分发到整个集群。提交后,Storm集群会激活并且开始运行topology。topology中的main()方法需要调用StormSubmitter.submitTopology()方法,并且为topology提供集群内唯一的名称。

storm kill topology_name[-w wait_time] 用来关闭已经部署的topology。

storm deactivate topology_name 停止指定topology的spout发送tuple

storm activate topology_name 恢复指定topology的spout发送tuple。

storm rebalance topology_name[-w wait_time][-n worker_count][-e component_name=executor_count] 指定storm在集群的worker之间重新平均地分配任务,不需要关闭或者重新提交现有的topology。当执行rebalance命令时,Storm会先取消激活topology,等待配置的的时间使剩余的tuple处理完成,然后再supervisor节点中均匀的重新分配worker。重新分配后,Storm会将topology恢复到之前的激活状态。

storm remoteconfvalue conf-name 用来查看远程集群中的配置参数值。
6.3. 把topology提交到集群中
6.3.1. 把topology提交到集群中
案例:改造之前的单词计数案例,将其在集群中运行。
修改提交topology的代码:
StormSubmitter.submitTopology(“mywc”, conf, topology);
将程序打成jar包,同时设置jar包的主类
将jar包上传到集群中
通过命令执行jar包
storm jar /root/work/stormwc.jar cn.tedu.storm.wc.WordCountTopology
执行一段时间后,可以通过如果下命令关闭topology
storm kill mywc
7. Strom的可靠处理引发的问题
7.1. Strom的可靠处理引发的问题
7.1.1. Strom的可靠处理引发的问题
在Storm进行可靠处理时,由于tuple可能被再次发送,所以在storm上进行统计个数之类的实现时,可能会存在重复计数问题。
Storm提供了机制可以实现"按顺序处理且只处理一次"的机制。
7.2. 方案1
7.2.1. 方案1
一次只处理一个tuple
事务型topology背后的核心思想是处理的数据必须能够保证强顺序性。最简单的实现方式就是一次只处理一个tuple,除非这个tuple处理成功,否则我们不去处理下一个tuple。
具体来说,每一个tuple都会跟一个唯一transaction id相关联,如果一个tuple处理失败了,然后需要重新发送,那么该tuple会使用完全相同的transaction id被发送。通常这个transaction id可以是一个递增的数字。但是这种设计方式有非常大的问题,主要体现在tuple的处理是完全线性的,效率非常低下,没有利用到storm的并行计算能力。
7.3. 方案2
7.3.1. 方案2
一次处理一批tuple
与每次只处理一个tuple的简单方案相比, 一个更好的方案是每个transaction里面处理一批tuple。所以如果你在做一个计数应用, 那么你每次更新到总数里面的是整个batch里面的tuple数量。如果这个batch失败了,那么你重新发送这整个batch。相应地, 我们不是给每个tuple一个transaction id而是给整个batch分配一个transaction id,batch与batch之间的处理是强顺序性的, 而batch内部是可以并行的。
虽然这个设计比第一个设计好多了, 它仍然不是一个完美的方案。topology里面的worker会花费大量的时间等待计算的其它部分完成。例如,一个topology中存在多步计算,则后续计算没有完成前,前置的计算空闲等待,只有所有计算都完成后,才能接受新的数据继续计算,这造成了计算资源的浪费。
7.4. 方案3
7.4.1. 方案3
storm使用的方式
在storm的设计中,考虑到并不是所有工作都需要强顺序性的,所以将整个计算分为两部分
阶段1:processing阶段:这个阶段可以并行的操作执行
阶段2:commit阶段:这个阶段按照强顺序性执行操作
这两个阶段合起来称为一个transaction。在一个给定的时刻,可以有很多batch处于processing阶段,但是只有一个batch可以处在commit阶段。如果一个batch在processing或者commit阶段有任何错误, 那么整个transaction需要被重新进行。

  1. StormDRPC
    8.1. StormDRPC
    8.1.1. StormDRPC
    LinearDRPCTopologyBuilder 已经过时被Trident替代,Storm里面引入DRPC主要是利用storm的实时计算能力来并行化CPU密集型(CPU intensive)的计算任务,DRPC其实不能算是storm本身的一个特性, 它是通过组合storm的原语stream、spout、bolt、 topology而成的一种模式(pattern)。DRPC的storm topology以函数的参数流作为输入,而把这些函数调用的返回值作为topology的输出流。
    Distributed RPC是由一个”DPRC服务器”协调(storm自带了一个实现)。
    DRPC服务器协调:① 接收一个RPC请求 ② 发送请求到storm topology ③ 从storm topology接收结果 ④ 把结果发回给等待的客户端。从客户端的角度来看一个DRPC调用跟一个普通的RPC调用没有任何区别。
    8.2. 案例
    8.2.1. 案例
    改造如上单词计数案例,通过DRPC机制实现单词结果查询
    8.2.2. 单机模式
    public class ExclaimBolt extends BaseBasicBolt {
    @Override
    public void execute(Tuple tuple, BasicOutputCollector collector) {
    String input = tuple.getString(1);
    collector.emit(new Values(tuple.getValue(0), input + “!”));
    }
    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declare(new Fields(“id”, “result”));
    }
    }
    public class DRPCLocalDriver {
    public static void main(String[] args) {
    //创建DRPC构建器
    LinearDRPCTopologyBuilder builder = new LinearDRPCTopologyBuilder(“exclamation”);
    //指定构建起中的bolt
    builder.addBolt(new ExclaimBolt(), 3);
    //在本地模拟drpc服务器
    LocalDRPC drpc = new LocalDRPC();
    //创建带有模拟的drpc服务器的topology
    StormTopology topology = builder.createLocalTopology(drpc);

    //启动本地集群运行topology
    Config conf = new Config();
    LocalCluster cluster = new LocalCluster();
    cluster.submitTopology("drpc-demo", conf,topology);
    
    //模拟调用远程方法
    String result = drpc.execute("exclamation", "hello");
    System.out.println("---------------------------Results for 'hello':" + result);
    
    //关闭资源
    cluster.shutdown();
    drpc.shutdown();
    

    }
    }
    8.2.3. 集群模式
    启动drpc服务器:
    storm drpc
    配置storm.yaml中的drpc服务器地址:
    **注意所有storm集群服务器中的配置都要修改
    drpc.servers:

  • “hadoop01”
    服务器端
    /**
    在执行DRPC的过程中,execute方法接受的tuple中具有n+1个值,第一个为request-id即请求的编号,后n个字段是请求的参数
    同时要求我们topology的最后一个bolt发送一个形如[id, result]的二维tuple:第一个field是request-id,第二个field是这个函数的结果。最后所有中间tuple的第一个field必须是request-id。
    */
    public class ExclaimBolt extends BaseBasicBolt {
    @Override
    public void execute(Tuple tuple, BasicOutputCollector collector) {
    String input = tuple.getString(1);
    collector.emit(new Values(tuple.getValue(0), input + “!”));
    }
    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declare(new Fields(“id”, “result”));
    }
    }
    public class DRPCRemoteDriver {
    public static void main(String[] args) throws Exception {
    //–指定一个被调用方法的名字
    LinearDRPCTopologyBuilder builder = new LinearDRPCTopologyBuilder(“myMethod”);
    //–设置要被调用topology中的执行bolt,可以设置多个
    builder.addBolt(new ExclaimBolt(), 3);
    //–提交topology到集群中运行
    Config conf = new Config();
    StormSubmitter.submitTopology(“drpc-demo”, conf, builder.createRemoteTopology());
    }
    }
    客户端
    public class DRPCClientDriver {
    public static void main(String[] args) throws Exception {
    DRPCClient client = new DRPCClient(“192.168.242.101”, 3772);
    String result = client.execute(“myMethod”, “abcd”);
    System.out.println(result);
    }
    }
  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值