storm示例之trident

storm在0.7中引入了事务型拓扑,以满足对消息处理有着极其严格要求的场景,例如某些应用于某些统计场景,当然要求统计量必须是完全精确的,不能多也不能少。你可以联想数据库的事务特性ACID来加深对事务型拓扑的理解。在storm的0.8版本之后,事务性已经被封装到Trident中。trident提供了一套非常成熟的批处理API来批量处理元组,可以对这些元组执行分组(group by)、连接(join)、聚合(aggregation)、运行函数、运行过滤器等,trident还封装了DRPC功能,同样支持DRPC远程调用。本文通过一个示例简介trident拓扑。

trident示例
新建工程,在project中的src/的同级新建lib/文件夹,将storm安装包根目录下的lib/文件夹下的所有jar包拷贝到新建工程的lib/目录下,并在eclipse中选中所有的jar包右键选择build path-> add to build path

Spout
下文的FixedBatchSpout是storm源码中自带的spout,用于向拓扑中发送一个语句,其中setCycle()方法可以设置向拓扑中循环发送语句。

FixedBatchSpout spout = new FixedBatchSpout(new Fields("sentence"), 3, new Values("the cow jumped over the moon"),...);

FixedBatchSpout的参数sentence为输出的字段名,数字3表示将发送的元组最多分为3个Batch进行处理,剩余的参数均为spout待发送的内容列表。
这里强调下参数3是最大的batch,实际的batch还受内容列表大小的限制,这在源码中的emitBatch()方法中得以体现。这里的Batch是指将元组分批处理。每个事务处理一批元组,各批次之间可以并行处理,以提高资源的利用率,减少相关联的事务间的等待。
FixedBatchSpout继承自IBatchSpout,IBatchSpout是一个非事务Spout,每次发送一个Batch元组。
FixedBatchSpout的源码实现如下:

//FixedBatchSpout.java
package org.apache.storm.trident.testing;

import org.apache.storm.Config;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.tuple.Fields;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

import org.apache.storm.trident.operation.TridentCollector;
import org.apache.storm.trident.spout.IBatchSpout;

public class FixedBatchSpout implements IBatchSpout {

    Fields fields;
    List<Object>[] outputs;
    int maxBatchSize;
    HashMap<Long, List<List<Object>>> batches = new HashMap<Long, List<List<Object>>>();

    public FixedBatchSpout(Fields fields, int maxBatchSize, List<Object>... outputs) {
        this.fields = fields;
        this.outputs = outputs;
        this.maxBatchSize = maxBatchSize;
    }

    int index = 0;
    boolean cycle = false;

    public void setCycle(boolean cycle) {
        this.cycle = cycle;
    }

    @Override
    public void open(Map conf, TopologyContext context) {
        index = 0;
    }

    @Override
    public void emitBatch(long batchId, TridentCollector collector) {
        List<List<Object>> batch = this.batches.get(batchId);
        if(batch == null){
            batch = new ArrayList<List<Object>>();
            if(index>=outputs.length && cycle) {
                index = 0;
            }
            for(int i=0; index < outputs.length && i < maxBatchSize; index++, i++) {
                batch.add(outputs[index]);
            }
            this.batches.put(batchId, batch);
        }
        for(List<Object> list : batch){
            collector.emit(list);
        }
    }

    @Override
    public void ack(long batchId) {
        this.batches.remove(batchId);
    }

    @Override
    public void close() {
    }

    @Override
    public Map<String, Object> getComponentConfiguration() {
        Config conf = new Config();
        conf.setMaxTaskParallelism(1);
        return conf;
    }

    @Override
    public Fields getOutputFields() {
        return fields;
    }

}

批处理API
在Trident中each(),stateQuery()等操作功能类似于Storm拓扑中的BOLT,这些bolt实现对数据的分组,聚合,查询等。

TridentState wordCounts = topology.newStream("spout1", spout) 
    .parallelismHint(16)
    .each(new Fields("sentence"),new Split(), new Fields("word"))
    .groupBy(new Fields("word"))
    .persistentAggregate(new MemoryMapState.Factory(),new Count(), new Fields("count"))
    .parallelismHint(16);

TridentState对象在本例中代表了所有单词的统计,用于对外提供给DRPC服务进行查询。
newStream():newStream方法在拓扑中创建一个新的数据流以便从输入源中读取数据
parallelismHint():设置并行处理的数量
each()配合运行函数(或过滤器):
例如:each(new Fields(“sentence”),new Split(), new Fields(“word”)) //对每个输入的sentence”字段”,调用Split()函数进行处理,并生成新的字段word
例如:each(new Fields(“sentence”),new MyFilter()) //对每个输入的sentence”字段”,调用MyFilter()函数进行过滤
groupBy():分组操作,按特定的字段进行分组
persistentAggregate():聚合函数,persistentAggregate实现的是将数据持久到特定的存储介质中
stateQuery():提供对已生成的TridentState对象的查询
过滤器
FilterNull的实现如下

public class FilterNull extends BaseFilter {
    @Override
    public boolean isKeep(TridentTuple tuple) {
        for(Object o: tuple) {
            if(o==null) return false;
        }
        return true;
    }
}

完整的示例代码

//TridentWordCount.java
package org.apache.storm.starter.trident;

import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.LocalDRPC;
import org.apache.storm.StormSubmitter;
import org.apache.storm.generated.StormTopology;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
import org.apache.storm.trident.TridentState;
import org.apache.storm.trident.TridentTopology;
import org.apache.storm.trident.operation.BaseFunction;
import org.apache.storm.trident.operation.TridentCollector;
import org.apache.storm.trident.operation.builtin.Count;
import org.apache.storm.trident.operation.builtin.FilterNull;
import org.apache.storm.trident.operation.builtin.MapGet;
import org.apache.storm.trident.operation.builtin.Sum;
import org.apache.storm.trident.testing.FixedBatchSpout;
import org.apache.storm.trident.testing.MemoryMapState;
import org.apache.storm.trident.tuple.TridentTuple;

public class TridentWordCount {

    public static class Split extends BaseFunction {
    @Override
        public void execute(TridentTuple tuple, TridentCollector collector) {
            String sentence = tuple.getString(0);
            for (String word : sentence.split(" ")) {
                collector.emit(new Values(word));
            }
        }
    }

    public static StormTopology buildTopology(LocalDRPC drpc) {
        FixedBatchSpout spout = new FixedBatchSpout(new Fields("sentence"), 3, new Values("the cow jumped over the moon"),
            new Values("the man went to the store and bought some candy"), new Values("four score and seven years ago"),
            new Values("how many apples can you eat"), new Values("to be or not to be the person"));
        spout.setCycle(true);

        TridentTopology topology = new TridentTopology();
        TridentState wordCounts = topology.newStream("spout1", spout) //newStream方法在拓扑中创建一个新的数据流以便从输入源(FixedBatchSpout)中读取数据
            .parallelismHint(16)
            .each(new Fields("sentence"),new Split(), new Fields("word")) //each(),对每个输入的sentence"字段",调用Split()函数进行处理
            .groupBy(new Fields("word"))
            .persistentAggregate(new MemoryMapState.Factory(),new Count(), new Fields("count"))
            .parallelismHint(16);

        topology.newDRPCStream("words", drpc) //以words作为函数名,对应于drpc.execute("words", "cat the dog jumped")中的words名
            .each(new Fields("args"), new Split(), new Fields("word"))//对于输入参数args,使用Split()方法进行切分,并以word作为字段发送
            .groupBy(new Fields("word"))//对word字段进行重新分区,保证相同的字段落入同一个分区
            .stateQuery(wordCounts, new Fields("word"), new MapGet(), new Fields("count"))
            .each(new Fields("count"),new FilterNull())//使用FilterNull()方法过滤count字段的数据(过滤没有统计到的单词)
            .aggregate(new Fields("count"), new Sum(), new Fields("sum"));

        return topology.build();
    }

    public static void main(String[] args) throws Exception {
        Config conf = new Config();
        conf.setMaxSpoutPending(20);
        if (args.length == 0) {
            LocalDRPC drpc = new LocalDRPC();
            LocalCluster cluster = new LocalCluster();
            cluster.submitTopology("wordCounter", conf, buildTopology(drpc));
            for (int i = 0; i < 100; i++) {
                System.out.println("DRPC RESULT: " + drpc.execute("words", "cat the dog jumped"));
                Thread.sleep(1000);
            }
        } else {
            conf.setNumWorkers(3);
            StormSubmitter.submitTopologyWithProgressBar(args[0], conf, buildTopology(null));
        }
    }
}

将代码打成jar包,提交storm拓扑,可以通过storm日志 ./storm/logs/workers-artifacts/* 观察实时的统计;
也可以通过DRPC,远程查询相关字段的统计
DRPC客户端 查询

import backtype.storm.utils.DRPCClient;

public class TestDRPC {

    public static void main(String[] args) throws Exception{
        // TODO Auto-generated method stub
        DRPCClient client = new DRPCClient("localhost",3772);//3772是drpc对外默认的服务端口
        System.out.println("DRPC result:" + client.execute("words", "the man storm"));
    }
}

storm会切分查询列表”the man storm”(each(new Fields(“args”), new Split(), new Fields(“word”))),并通过stateQuery进行查询

最后,基于trident API可以实现函数功能(例如Split),过滤器(例如FilterNull()),分区聚合(例如persistentAggregate),状态查询(例如stateQuery),投影,重新分组,合并,连接等。具体可以参考相关接口,文献[1]中也有相关简介。


1.赵必夏,程丽明. 从零开始学Storm(第二版).清华大学出版社,2016.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值