Storm 调研(Draft)

Storm 调研(Draft)

需求

因为花果山经常遇到ddos攻击,我们当前的BI统计平台无法提供实时的web日志。在无法更改现有框架的情况下,考虑加入实时计算平台。

对比

在实时处理这块,相似的项目还是有 yahoo 的S4。Storm 和 S4 相比,除去工作流程不一样外,最大的不同在于 Storm 提供了消息追踪机制,能确保消息不被丢失。

选用 Storm 的理由

  1. Storm 支持多种语言(java,ruby,python等),可以很容易的按照它的标准扩展。
  2. Storm 配置简单,无需复杂的xml配置
  3. 容易部署

Storm 的框架

Storm 依赖 Zookeeper 和 zeromq。它的框架相对简单。 框架

配置

配置分2部分,一部分是配置 zookeeper 集群;另外一部分对 storm 本身的配置。

Zookeeper 的配置:

tickTime=2000

initLimit=10

syncLimit=5

dataDir=/home/datastream/cluster_data/zookeeper

clientPort=2181

server.2=download-1.popo.163.org:2181:3888

server.3=download-2.popo.163.org:2181:3888

server.4=download-3.popo.163.org:2181:3888

server.5=download-4.popo.163.org:2181:3888

server.6=download-5.popo.163.org:2181:3888

需要注意的是在dataDir目录中,不要忘记创建myid,里面的内容需要和配置里面的保持一致。例如download-1.popo.163.org上的myid内容应该是2

Storm 的配置:

storm.zookeeper.servers:

    - "download-1.popo.163.org"

    - "download-2.popo.163.org"

    - "download-3.popo.163.org"

    - "download-4.popo.163.org"

    - "download-5.popo.163.org"

nimbus.host: "download-1.popo.163.org"

# topology.serializations:

#     - "org.mycompany.MyObjectSerialization"

#     - "org.mycompany.MyOtherObjectSerialization"

drpc.servers:

     - "download-1.popo.163.org"

整个配置文件一般是在~/.storm/storm.yaml 或者 $storm_home/conf/storm.yaml

Storm 命令

./storm

Commands:

        activate

        classpath

        deactivate

        dev-zookeeper

        drpc

        help

        jar

        kill

        list

        localconfvalue

        nimbus

        rebalance

        remoteconfvalue

        repl

        shell

        supervisor

        ui

        version



Help:

        help

        help <command>



Documentation for the storm client can be found at https://github.com/nathanmarz/storm/wiki/Command-line-client

启动/管理 Storm 集群

Storm 集群中有多个角色。

  1. supervisor 是计算节点,直接用./storm supervisor启动
  2. nimbus 是主控节点,用于调度分配任务负载。直接用./storm nimbus启动
  3. ui 是web显示页面,用于web页面显示。直接用./storm ui启动

除去服务器上的 Storm 平台,开发者需要在本地搭建自己的开发环境。整个开发环境也需要storm.yaml,和线上的保持一致。

  1. 上传一个任务使用 ./storm jar all-my-code.jar backtype.storm.MyTopology arg1 arg2
  2. 停止一个任务使用 ./storm kill taskname, taskname 基本就是 StormSubmitter.submitTopology函数的第一个参数。

本地开发

Storm 支持 maven2 或者 lein 管理项目。

目录结构:

src/jvm #javacode

multilang/resources #other language

lib/ #jar depends

简单的lein配置(project.clj):

(defproject logstream "0.0.1-SNAPSHOT"

  :java-source-path "src/jvm"

  :javac-options {:debug "true" :fork "true"}

  :jvm-opts ["-Djava.library.path=/usr/local/lib:/opt/local/lib:/usr/lib:/usr/local/diablo-jdk1.6.0/lib"]

  :aot :all

  :dependencies [[org.clojure/clojure "1.2.0"]

                [org.clojure/clojure-contrib "1.2.0"]

                [com.googlecode.json-simple/json-simple "1.1"]

                ]

  :dev-dependencies [[storm "0.6.0"]

                ])

工作原理

Storm 基于消息流处理,它称之为stream。主要由: spout 和 bolt 2部分组成。 Storm 上面的图中的 Spout 和 Bolt 的关系被称为 topology。

基本元素

Spout

在 Storm 的结构中 spout 承担了消息的生成功能。spout 有多个封装好的接口。

public interface ISpout extends Serializable {

    void open(Map conf, TopologyContext context, SpoutOutputCollector collector);

    void close();

    void nextTuple();

    void ack(Object msgId);

    void fail(Object msgId);

}



public class WebLogSpout implements ISpout {

  ....

}

nextTuple函数将在一个 while(ture) 中被调用。你可以使用IRichSpout代替ISPout,这样你可以设置isDistributed()函数,让spout允许多个。 一个 spout 是 topology 的一个 stream source。 一个 Spout 可以设置为 reliable 或者 unreliable。通过设置declareOutputFields函数,Spouts 可以 emit 多个 stream。 最组要的功能函数是nextTuple,nextTuple可以 emits 一个 tuple 到 topology 或者直接返回。需要注意的是nextTuple一定是非阻塞的,因为所有的 spout 都是跑在同一个线程的。 ackfail的作用是处理消息在整个 topology 的完成情况。只有需要确保消息完整处理的时候才需要用到。

Bolt

Bolt 是基本的消息处理单元。

public class SplitSentence implements IRichBolt {

 OutputCollector _collector;

 public void prepare(Map conf, TopologyContext context, OutputCollector collector) {

   _collector = collector;

 }

 public void execute(Tuple tuple) {

   String sentence = tuple.getString(0);

   for(String word: sentence.split(" ")) {

     _collector.emit(tuple, new Values(word));

   }

   _collector.ack(tuple);

 }

 public void cleanup() { }

 public void declareOutputFields(OutputFieldsDeclarer declarer) {

   declarer.declare(new Fields("word"));

 }

}

Bolt 可以处理所有的逻辑操作。Bolt 必须在处理结束的时候显式的调用 ack,这样 topology 可以知道哪些消息已经被处理完成了。反之如果在一个对消息完整性有要求的 topology 中,会触发一个消息的重发需求。

Tuple

在 Storm 中,每个消息单元被称之为 tuple。因为 storm 使用内存追踪消息状态,每个 tuple 必须被ack或者fail,否则很容易 OOM。如果在不需要确保消息完整性的 topology 里面,tuple 只是单纯的直接passdown,即使丢失也不会重发。

_collector.emit(new Values(word));

_collector.emit(new Values(word,count));

anchored,Multi-anchoring :

List<Tuple> anchors = new ArrayList<Tuple>(); 

anchors.add(tuple1);

 anchors.add(tuple2);

 _collector.emit(anchors, new Values(1, 2, 3));

Tuple Tree

当 emit 一个 tuple 的时候, spout生成的 tuple id会从被处理的 tuple的 anchors 里面复制到新的 tuple。如果一个 tuple 被ack了,它会发送一个消息到ackertasks,通知 tuple tree 改变了。

例如: 如果tuples "D" and "E"是基于 tuple "C", 当 "C" 被 acked时: tuple tree

既然 "C" 从 tree 中 被移除的同时, "D" 和 "E" 加入了 tree, 整棵树不能过早的完成。

Acker task

一个 storm toplogy有一组特殊的acker任务,用于追踪每个 spout tuple生成的 tuple tree。一个 acker task 存储了一个 spout id到一对数值的映射。第一个数值是任务id,主要是spout的时候生成的,随机会被用于发送完整的消息。第二个值是一个64bit的数字,称为ack val。整个 ack val用于表达整个tuple tree的状态。如果多少,整个数字只是单纯的xor所有已经创建的 tuple id。 当一个acker任务看到一个ack val变为0的时候,整个 tuple tree就算完成了。由于tuple id是一个随机的64bit的数字,ack val刚好变成0的概率很低。通过计算,如果每秒ack 10k,要触发一个错误需要花费50,000,000年。而且只有当 tuple 刚好fail的时候才会触发数据丢失。

Stream Group

  1. shuffle group tuple被随机的发送到bolt

  2. fields group 按照field发送 tuple

  3. all group 发送到所有的bolt

  4. global group 整个stream进入到一个bolt中。

  5. none group 整个只有当你不在乎group的时候使用。当前情况下这个和 shuffle group的效果一样。

  6. direct group 指定特定的bolt处理消息,需要使用emitDirect函数。

group

Fields

Fields主要的作用是对消息做分类处理。例如在bolt中:

public void execute(Tuple input) {

    int val = input.getInteger(0);

    _collector.emit(input, new Values(var, var2));

    _collector.ack(input);

}

public void declareOutputFields(OutputFieldsDeclarer declarer) { 

    declarer.declare(new Fields("word","count"));

}

emit的数据和fields的个数要对应。

builder.setBolt("rank", new RankObjects(), n).fieldsGrouping("objects", new Fields("value"));

这个调用的作用是创建使用 RankObjects 一个叫 rack 的 bolt,同时从叫 objects 的bolt中订阅叫 value 的 fields。

Topoloy

TopologyBuilder builder = new TopologyBuilder();

builder.setSpout("words", new TestWordSpout(), 10);

builder.setBolt("exclaim1", new ExclamationBolt(), 3) .shuffleGrouping("words");

builder.setBolt("exclaim2", new ExclamationBolt(), 2) .shuffleGrouping("exclaim1");

上面这些语句就是创建了一个 topoloy。 一个 bolt 可以同时的订阅多个上游的消息来源,例如:

builder.setBolt("exclaim2", new ExclamationBolt(), 5) .shuffleGrouping("words") .shuffleGrouping("exclaim1");

topoloy config

Config conf = new Config();

conf.setDebug(true); //是否设置调试

conf.setNumWorkers(2);  //设置最大worker数

if(args!=null && args.length > 0) {

    conf.setNumWorkers(5);

    StormSubmitter.submitTopology(args[0], conf, builder.createTopology());  //将topology提交到线上

} else {

    conf.setMaxTaskParallelism(3);

    LocalCluster cluster = new LocalCluster();

    cluster.submitTopology("rolling-site", conf, builder.createTopology());  //本地调试

    Thread.sleep(10000);

    cluster.shutdown();

  }

}

设置默认的消息超时和acker任务数:

Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS  #set message timeout,default 30s

Config.TOPOLOGY_ACKERS #set acker number,default 1

你可能感兴趣的:(storm)