Storm——2、Storm原生API编程、并发机制、可靠性与DRPC详解

来源:互联网 发布:淘宝用绒里图片 编辑:程序博客网 时间:2024/05/01 14:33

Storm分布式计算结构称为topology(拓扑),由stream(数据流)、spout(数据流的生成者)、bolt(运算)组成。Strom的核心数据结构是tuple。tuple是包含了一个或多个键值对的列表,strom是由无限制的tuple组成的序列。spout代表了一个Strom topology的主要数据入口,充当采集器的角色,连接到数据源,将数据源转化为一个个tuple,并将tuple作为数据流进行发射。bolt可以理解为计算程序中的运算或者函数,将一个或者多个数据流作为输入,对数据实施运算后,选择性的输出一个或多个数据流。bolt可以订阅多个由spout或其他bolt发射的数据流。

HelloWord入门

首先编写数据源Spout,可以使用两种方式:

(1)实现IRichSpout接口。

(2)继承BaseRichSpout类。

需要对其中重点的几个方法进行重写或实现:open、nextTuple、declareOutputFields。

其次编写数据处理类Bolt,同样也有两种方式:

(1)实现RichBolt接口。

(2)继承BaseBasicBolt类。

需要对其中重点的几个方法进行重写或实现:execute、declareOutputFields。

最后编写主函数(Topology)进行提交任务,在使用Topoloty时,Storm提供了两种模式:本地模式和集群模式:

本地模式:无需Storm集群,直接在java中运行即可,一般用于开发和测试阶段。

集群模式:需要Storm集群,把实现的java程序打成jar包,然后使用storm命令把Topology提交到集群中去。

pom.xml文件,该文件中只需引入storm的核心文件即可,相关依赖会自动下载:

<dependency>    <groupId>org.apache.storm</groupId>    <artifactId>storm-core</artifactId>    <version>1.0.2</version></dependency>
SentenceSpout:
public class SentenceSpout extends BaseRichSpout {    private static final long serialVersionUID = 1L;    private SpoutOutputCollector collector;    private static final Map<Integer, String> sentences = new HashMap<Integer, String>();    static {        sentences.put(0, "my dog has fleas");        sentences.put(1, "i like cold beverages");        sentences.put(2, "the dog ate my homework");        sentences.put(3, "don't have a cow man");        sentences.put(4, "i don't think i like fleas");    }    public void open(Map map, TopologyContext context, SpoutOutputCollector collector) {        this.collector = collector;    }    public void nextTuple() {        final Random random = new Random();        int num = random.nextInt(5);        try {            Thread.sleep(500);        } catch (Exception e) {            e.printStackTrace();        }        collector.emit(new Values(sentences.get(num)));    }    public void declareOutputFields(OutputFieldsDeclarer declarer) {        declarer.declare(new Fields("sentences"));    }}

SplitSentenceBolt:
public class SplitSentenceBolt extends BaseBasicBolt {    private static final long serialVersionUID = 1L;    public void execute(Tuple tuple, BasicOutputCollector collector) {        String sentences = tuple.getStringByField("sentences");        String[] words = sentences.split(" ");        for(String word : words) {            collector.emit(new Values(word));        }    }    public void declareOutputFields(OutputFieldsDeclarer declarer) {        declarer.declare(new Fields("word"));    }}

WordCountBolt:
public class WordCountBolt extends BaseBasicBolt {    private static final long serialVersionUID = 1L;    private Map<String, Integer> counts = null;    @Override    public void prepare(Map stormConf, TopologyContext context) {        super.prepare(stormConf, context);        counts = new HashMap<String, Integer>();    }    public void execute(Tuple tuple, BasicOutputCollector collector) {        String word = tuple.getStringByField("word");        Integer count = counts.get(word);        if(count == null) {            count = 0;        }        count ++;        counts.put(word, count);        collector.emit(new Values(word, count));    }    public void declareOutputFields(OutputFieldsDeclarer declarer) {        declarer.declare(new Fields("word", "count"));    }}

ReportBolt:
public class ReportBolt extends BaseBasicBolt {    private static final long serialVersionUID = 1L;    private Map<String, Integer> counts = null;    @Override    public void prepare(Map stormConf, TopologyContext context) {        super.prepare(stormConf, context);        counts = new HashMap<String, Integer>();    }    public void execute(Tuple tuple, BasicOutputCollector collector) {        String word = tuple.getStringByField("word");        Integer count = tuple.getIntegerByField("count");        counts.put(word, count);    }    public void declareOutputFields(OutputFieldsDeclarer declarer) {    }    @Override    public void cleanup() {        super.cleanup();        System.out.println("--- FINAL COUNTS ---");        List<String> keys = new ArrayList<String>();        keys.addAll(counts.keySet());        Collections.sort(keys);        for(String key : keys) {            System.out.println(key + ":" + counts.get(key));        }        System.out.println("--------------------");    }}

ReportBolt:
public class ReportBolt extends BaseBasicBolt {    private static final long serialVersionUID = 1L;    private Map<String, Integer> counts = null;    @Override    public void prepare(Map stormConf, TopologyContext context) {        super.prepare(stormConf, context);        counts = new HashMap<String, Integer>();    }    public void execute(Tuple tuple, BasicOutputCollector collector) {        String word = tuple.getStringByField("word");        Integer count = tuple.getIntegerByField("count");        counts.put(word, count);    }    public void declareOutputFields(OutputFieldsDeclarer declarer) {    }    @Override    public void cleanup() {        super.cleanup();        System.out.println("--- FINAL COUNTS ---");        List<String> keys = new ArrayList<String>();        keys.addAll(counts.keySet());        Collections.sort(keys);        for(String key : keys) {            System.out.println(key + ":" + counts.get(key));        }        System.out.println("--------------------");    }}

WordCountTopology:

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 InterruptedException, InvalidTopologyException, AuthorizationException, AlreadyAliveException {        SentenceSpout spout = new SentenceSpout();        SplitSentenceBolt splitBolt = new SplitSentenceBolt();        WordCountBolt countBolt = new WordCountBolt();        ReportBolt reportBolt = new ReportBolt();        TopologyBuilder builder = new TopologyBuilder();        builder.setSpout(SENTENCE_SPOUT_ID, spout, 2);        builder.setBolt(SPLIT_BOLT_ID, splitBolt, 2).setNumTasks(4).shuffleGrouping(SENTENCE_SPOUT_ID);        builder.setBolt(COUNT_BOLT_ID, countBolt, 4).fieldsGrouping(SPLIT_BOLT_ID, new Fields("word"));        builder.setBolt(REPORT_BOLT_ID, reportBolt).globalGrouping(COUNT_BOLT_ID);        Config config = new Config();        config.setNumWorkers(2);        /*LocalCluster cluster = new LocalCluster();        cluster.submitTopology(TOPOLOGY_NAME, config, builder.createTopology());        Thread.sleep(100000);        cluster.killTopology(TOPOLOGY_NAME);        cluster.shutdown();*/        StormSubmitter.submitTopology(TOPOLOGY_NAME, config, builder.createTopology());    }}

WordCountTopology的并发机制:

在没有配置topology的并发度之前,先来看一下默认配置下topology的执行过程。假设有一台服务器(node),为topology分配了一个worker,并且每个executor执行一个task:


在上面的代码中使用config.setNumWorkers(2)为topology分配了两个worker。Storm给topology中定义的每个组件建立了一个task,默认情况下,每个task分配一个executor,在定义数据流分组时,可以设置给一个组件指派的executor的数量,允许设定每个task对应的executor个数和每个executor可执行的task的个数:builder.setSpout(SENTENCE_SPOUT_ID, spout, 2)。如果只是用一个worker,topology执行过程如下图:


builder.setBolt(SPLIT_BOLT_ID, splitBolt, 2).setNumTasks(4).shuffleGrouping(SENTENCE_SPOUT_ID):设置了4个task和2个executor,每个executor线程指派了2个task来执行(4/2=2)。

builder.setBolt(COUNT_BOLT_ID, countBolt, 4).fieldsGrouping(SPLIT_BOLT_ID, new Fields("word")):设置4个executor,每个task由一个executor线程执行。

在2个worker下,topology执行过程如下图:


在实际开发中,重要的并不是逻辑代码的编写,而是worker、task、executor的配置。

数据流分组:

数据流分组定义了一个数据流中的tuple如何分发给topology中不同bolt的task。Storm中定义了7种内置数据流分组方式:

(1)Shuffle grouping(随机分组):这种方式会随机分发tuple给bolt的各个task,每个bolt实例接收到相同数量的tuple。

(2)Fields grouping(按字段分组):根据指定字段的值进行分组。

(3)All grouping(全复制分组):将所有的tuple复制后分发给所有的bolt task。每个订阅数据流的task都会接收到tuple的拷贝。

(4)Globle grouping(全局分组):这种分组方式将所有的tuples都路由到唯一一个task上。Storm按照最小的task ID来选取接收数据的task。注意,当使用全局分组方式时,设置bolt的task并发度是没有意义的,因为所有tuple都转发到同一个task上了。

(5)None grouping(不分组):在功能上与随机分组完全相同。

(6)Direct grouping(指向型分组):数据源会调用emitDirect()方法来判断一个tuple应该由哪个Storm组件来接收。只能在声明了是指向型的数据流上使用。

本地模式的话,直接Run as Java Application即可。

(7)Partial Key grouping(部分关键字分组):这种分组方式跟字段分组很相似,不同的是,这种方式会考虑下游bolt数据处理的均衡性问题,在输入数据源关键字不平衡时会有更好的性能。

(8)Local or Shuffle grouping(本地或随机分组):如果在源组件的worker进程里目标bolt有一个或更多的任务线程,元祖会被随机分配到那些同进程的任务中。

集群模式,进入项目目录,使用mvn package -DskipTests命令,将项目打成jar包。然后将jar包放入storm集群中nimbus所在的服务器,使用命令storm jar 项目.jar 全路径.main方法所在的类(比如:storm jar storm.jar com.hyy.PWTopology1)来提交topology。


查看任务命令:list


另外两个supervisor节点jps显示:


有保障机制的数据处理:

(1)spout的可靠性

在Storm中,可靠的消息处理机制是从spout开始的。一个提供了可靠的处理机制的spout需要记录它发射出去的tuple,当下游bolt处理tuple或者子tuple失败时,spout能够重新发射。


图中实线部分表示从spout发射的原始主干tuple,虚线部分表示的子tuple都是源自于原始tuple。这样产生的图形叫做tuple树。在有保障数据的处理过程中,bolt每收到一个tuple,都需要向上游确认应答(ask)或者报错(fail)。对主干tuple中的一个tuple,如果tuple树上的每个bolt进行了确认应答,spout会调用ask方法来标明这条消息已经完全处理了。如果树种任何一个bolt处理tuple报错,或者处理超时,spout会调用fail方法。

前面讲过,Storm通过调用spout的nextTuple()方法发送一个tuple。为了实现可靠的消息处理,首先要给每个发射的tuple带上唯一的ID,并且将ID作为参数传递给SpoutOutputCollector的emit方法。给tuple指定id是为了告诉Storm系统,无论执行成功还是失败,spout都要接收tuple树上所有节点返回的通知。

(2)bolt的可靠性

bolt要实现可靠的消息处理机制包含以下两个步骤:

①当发射衍生的tuple时,需要锚定读入的tuple。

②当处理消息成功或者失败时分别确认应答或者报错。

锚定tuple的意思是建立读入tuple和衍生tuple之间的对应关系,这样下游的bolt就可以通过应答确认、报错或者超时来加入到tuple树结构中。

接下来我们来看代码:

MessageSpout:

public class MessageSpout implements IRichSpout {private static final long serialVersionUID = 1L;private int index = 0;private String[] subjects = new String[]{"groovy,oeacnbase","openfire,restful","flume,activiti","hadoop,hbase","spark,sqoop"};private SpoutOutputCollector collector;@Overridepublic void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {this.collector = collector;}@Overridepublic void nextTuple() {if(index < subjects.length){String sub = subjects[index];//发送信息参数1 为数值, 参数2为msgIdcollector.emit(new Values(sub), index);index++;}}@Overridepublic void declareOutputFields(OutputFieldsDeclarer declarer) {declarer.declare(new Fields("subjects"));}@Overridepublic void ack(Object msgId) {System.out.println("【消息发送成功!!!】 (msgId = " + msgId +")");}@Overridepublic void fail(Object msgId) {System.out.println("【消息发送失败!!!】  (msgId = " + msgId +")");System.out.println("【重发进行中...】");collector.emit(new Values(subjects[(Integer) msgId]), msgId);System.out.println("【重发成功!!!】");}@Overridepublic void close() {}@Overridepublic void activate() {}@Overridepublic void deactivate() {}@Overridepublic Map<String, Object> getComponentConfiguration() {return null;}}
SpliterBolt:
public class SpliterBolt implements IRichBolt {private static final long serialVersionUID = 1L;private OutputCollector collector;@Overridepublic void prepare(Map config, TopologyContext context, OutputCollector collector) {this.collector = collector;}private boolean flag = false;@Overridepublic void execute(Tuple tuple) {try {String subjects = tuple.getStringByField("subjects");//模拟出错if(!flag && subjects.equals("flume,activiti")){flag = true;int a = 1/0;}String[] words = subjects.split(","); for (String word : words) {//注意这里循环发送消息,要携带tuple对象,用于处理异常时重发策略collector.emit(tuple, new Values(word));}collector.ack(tuple);} catch (Exception e) {e.printStackTrace();collector.fail(tuple);}}@Overridepublic void declareOutputFields(OutputFieldsDeclarer declarer) {declarer.declare(new Fields("word"));}@Overridepublic void cleanup() {}@Overridepublic Map<String, Object> getComponentConfiguration() {return null;}}
WriterBolt:
public class WriterBolt implements IRichBolt {private static final long serialVersionUID = 1L;private FileWriter writer;private OutputCollector collector;@Overridepublic void prepare(Map config, TopologyContext context, OutputCollector collector) {this.collector = collector;try {writer = new FileWriter("d://message.txt");} catch (IOException e) {e.printStackTrace();}}private boolean flag = false;@Overridepublic void execute(Tuple tuple) {String word = tuple.getString(0);try {if(!flag && word.equals("hadoop")){flag = true;int a = 1/0;}writer.write(word);writer.write("\r\n");writer.flush();} catch (Exception e) {e.printStackTrace();collector.fail(tuple);}collector.emit(tuple, new Values(word));collector.ack(tuple);}@Overridepublic void cleanup() {}@Overridepublic void declareOutputFields(OutputFieldsDeclarer declarer) {}@Overridepublic Map<String, Object> getComponentConfiguration() {return null;}}
MessageTopology:
public class MessageTopology {public static void main(String[] args) throws Exception {TopologyBuilder builder = new TopologyBuilder();builder.setSpout("spout", new MessageSpout());builder.setBolt("split-bolt", new SpliterBolt()).shuffleGrouping("spout");builder.setBolt("write-bolt", new WriterBolt()).shuffleGrouping("split-bolt");        //本地配置        Config config = new Config();        config.setDebug(false);        LocalCluster cluster = new LocalCluster();        System.out.println(cluster);        cluster.submitTopology("message", config, builder.createTopology());        Thread.sleep(10000);        cluster.killTopology("message");        cluster.shutdown();}}

Strom的DRPC:

Storm是一个分布式实时处理框架,它支持以DRPC方式调用.可以理解为Storm是一个集群,DRPC提供了集群中处理功能的访问接口。其实即使不通过DRPC,而是通过在Topoloye中的spout中建立一个TCP/HTTP监听来接收数据,在最后一个Bolt中将数据发送到指定位置也是可以的。这是后话,后面再进行介绍。而DPRC则是Storm提供的一套开发组建,使用DRPC可以极大的简化这一过程。Storm里面引入DRPC主要是利用storm的实时计算能力来并行化CPU intensive的计算。DRPC的storm topology以函数的参数流作为输入,而把这些函数调用的返回值作为topology的输出流。DRPC其实不能算是storm本身的一个特性, 它是通过组合storm的原语spout,bolt, topology而成的一种模式(pattern)。本来应该把DRPC单独打成一个包的, 但是DRPC实在是太有用了,所以我们我们把它和storm捆绑在一起。

DRPC工作流程:


DRPC与一般的RPC没有区别,只不过它是分布式的。对于用户而言,他是向一个DRPC服务器发送请求,然后等待DRPC的返回结果。整个流程如下:
1、用户向DRPC服务器发送请求,请求内容主要包括请求的方法名以及请求的参数。 
2、DRPC服务器接收到这个请求后,将请求发送到拓扑的spout。 
3、拓扑计算完成后,将结果返回到DRPC服务器。结果是以请求参数作为key,计算结果作为value的一个tuple(即有2个域)。 
4、DRPC服务器将结果返回用户。
简单的说,就是用户的请求作为spout的数据源。
对于高计算密度的RPC应用,DRPC可以将请求均匀的分布在整个storm集群进行计算。
在storm0.9以后,drpc都使用了trident的API来创建拓扑,不再使用原有API。

首先,修改Storm/conf/storm.yaml中的drpc server地址;需要注意的是:必须修改所有Nimbus和supervisor上的配置文件,设置drpc server地址。否则在运行过程中可能无法返回结果。
然后,通过 storm drpc命令启动drpc server。

RemoteDRPCDemo:

public class RemoteDRPCDemo {    public static void main(String[] args) throws InvalidTopologyException, AuthorizationException, AlreadyAliveException {        Config config = new Config();        config.setNumWorkers(2);        TridentTopology topology = new TridentTopology();        topology.newDRPCStream("exclaimation").each(new Fields("args"), new ExclaimBolt(), new Fields("words")).parallelismHint(5);        StormSubmitter.submitTopology("drpc-demo", config, topology.build());    }}class ExclaimBolt extends BaseFunction {    public void execute(TridentTuple tuple, TridentCollector collector) {        String input = tuple.getString(0);        collector.emit(new Values(input + "!"));    }}
DrpcExclam:

public class DrpcExclam {    public static void main(String[] args) throws TException {        Config config = new Config();        config.setDebug(true);        config.put("storm.thrift.transport", "org.apache.storm.security.auth.SimpleTransportPlugin");        config.put(Config.STORM_NIMBUS_RETRY_TIMES, 3);        config.put(Config.STORM_NIMBUS_RETRY_INTERVAL, 10);        config.put(Config.STORM_NIMBUS_RETRY_INTERVAL_CEILING, 20);        config.put(Config.DRPC_MAX_BUFFER_SIZE, 5);        DRPCClient client = new DRPCClient(config, "192.168.3.58", 3772);        String ss = "the cow jumped over the moon";        for(String s :ss.split(" ")){            //返回结果是一个KV格式,KEY为请求的id, V为最后一个bolt的返回结果。被封装成一个2个值的tuple。            System.out.println(client.execute("exclaimation", s));        }    }}




0 0
原创粉丝点击