Kafka+Storm+HBase项目Demo(7)--Trident使用

本文内容部分来自Trident Tutorial。

Trident是基于Storm的实时计算模型的高级抽象。它可以实现高吞吐(每秒数百万条消息)的有状态流处理和低延迟分布式查询。如果以前使用过高级批处理工具(比如Pig或Cascading),则对Trident的概念会非常熟悉,比如连接、聚合、分组、功能处理和过滤等。除此之外,Trident还增加了用于在数据库或持久化存储上进行有状态的增量处理的原语。Trident具有一致性、一次性语义,所以很容易就能够推导出Trident拓扑结构。

Trident的出现算是程序猿非常懒的又一个铁证。Strom是一个实时流处理工具,有很高的吞吐。在实际应用场景中,很多场景是借助这种实时处理能力,对实时数据进行统计,然后将统计结果实时推送到大屏或者其他可以实时浏览的地方,这样领导或者活动运营就可以实时查看销售或活动情况,比如,双十一时候的大屏,就可以使用Storm来做(我们现在就是这样做的,把全渠道的销售情况进行实时统计,然后显示在大屏上,据说领导会看)。然后,程序猿们就发现,很多统计功能非常类似,所以进行抽象,使用更加高级的功能代替一个一个的Spout、Bolt(当然,Trident拓扑结构运行的时候也是解析成Spout和Bolt运行)。

然后又有人发现,Trident这种方式也是比较麻烦,即使程序猿们通过高级抽先的Trident省去了很多麻烦,但是还是架不住运维、运营、产品等不断改变的需求,所以就有很多SQL方式解析为Trident或普通Topology的工具产生。既然运维、运营、产品等不断修改需求,那就简单的通过SQL查询(不同的SQL解析为不同的拓扑结构,在Storm中运行,可以得出不同的结果)。比如:squall。

这些都是题外话,下面继续说Trident。
接下来看一个Trident的例子:

实现单词计数的Trident

统计输入句子中单词数量
实现单词统计结果的查询
首先实现一个不断发送句子的Spout:

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"));
spout.setCycle(true);

上面的Spout循环发送句子流,下面是计算流中单词数量的代码,也就是Trident的本体:

TridentTopology topology = new TridentTopology(); // 1
TridentState wordCounts =
     topology.newStream("spout1", spout) // 2
       .each(new Fields("sentence"), new Split(), new Fields("word")) // 3
       .groupBy(new Fields("word")) // 4
       .persistentAggregate(new MemoryMapState.Factory(), new Count(), new Fields("count")) // 5
       .parallelismHint(6);

一步步说下代码:

行1,创建TridentTopology对象topology,这个对象就是Trident计算的入口。

行2,TridentTopology的newStream方法是定义Trident的输入源,这里使用的是一开始定义的FixedBatchSpout对象。当然,输入源也可以是Kafka之类的队列Broker(这个实在不知道应该翻译成什么,不过好在是说Broker,大家就都明白是干啥的)。这个地方,Trident会在Zookeeper中跟踪每个输入源的状态元数据。也就是Trident中比较重要的状态的概念,这个后面再说。Trident会将输入的流分成更小的批量数据(这里比较绕口,可以理解为Trident的入口进来一个大的批量数据,然后Trident把这个大的批量数据进行分割,变成一堆小的批量数据,比如进来的是1000条,分割成10个100条)进行处理,比如,把传入的流分成下面的样子:

这里写图片描述

通常,小批量数据的数量会是数千或数百万个,这取决于吞吐量。

Trident提供了一整套完整的批量处理API来处理这些小批量数据。类似于Hadoop的高级抽象中处理Pig或Cascading的内容:分组、连接、聚合、功能操作、过滤等。当然,分别处理每个小的批量数据并不容易(使用Hadoop处理大的矩阵乘法的就会深有体会),所以Trident提供了跨批次进行聚合处理的功能,并可以将这些聚合结果持久化在内存中、Memcached、Cassandra或其他存储中。Trident还具有一流的实时状态查询功能,这个状态可以有Trident更新,或者其他独立的状态来源。

行3,Spout发出包含名为sentence的field的流。通过使用Split函数,对每个tuple进行处理,把名为sentence的字段分割为一个个单词。将分割的单词命名为word,继续向下分发。下面是Split的定义:

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

行4,Trident进行了单词计数和结果的持续存储。先对word字段进行分组,然后用Count聚合器持续聚合。

Trident的一个很强的特性是能够完全容错和一次性处理语义。Trident能够保持状态,如果发生故障需要重试,则不会对同一源数据多次更新数据。

行5,persistentAggregate函数实现了存储和更新聚合结果的功能,不需要操心。例子中,计数结果保存在内存中,当然也可以使用Memcached、Cassandra或其他持久化存储。这里先不做讨论。persistentAggregate方法将Stream转换为TridentState对象。在这里,TridentState对象表示所有单词计数,然后使用TridentState对象来实现分布式查询。

接下来实现对单词计数实现低延迟分布式查询,以空格分割的单词列表作为输入,返回这些单词的计数总和。这个查询会在后台做并行化处理,其他的与普通的RPC调用一样。比如像下面这样调用:

DRPCClient client = new DRPCClient("drpc.server.location", 3772);
System.out.println(client.execute("words", "cat dog the man");

如上面的代码,除了它是在Storm集群中并行执行外,与普通的RPC调用没什么区别。通常简单的RPC查询,延迟在10ms左右,复杂的DRPC查询可能需要更长的时间,具体的时间取决于计算被分配的资源多少。

拓扑中分布式查询部分的实现如下所示:

topology.newDRPCStream("words")
       .each(new Fields("args"), new Split(), new Fields("word")) // 6
       .groupBy(new Fields("word")) // 7
       .stateQuery(wordCounts, new Fields("word"), new MapGet(), new Fields("count")) // 8
       .each(new Fields("count"), new FilterNull()) // 9
       .aggregate(new Fields("count"), new Sum(), new Fields("sum")); // 10

使用同一个TridentTopology对象来创建DRPC流,命名该函数为words,函数名与DRPCClient执行时的第一个参数名称相同。每个DRPC请求都是一个小的批量作业,将请求的单个tuple作为输入,tuple中包含名为args的字段,args中包含了客户端的请求参数。在这个例子中,请求参数就是“cat dog the man”。

行6,通过Split函数将请求参数分解为一个个单词,组成“word”流。

行7,将上一步分解的“word”流分组。

行8,stateQuery方法用于查询第一部分生成的TridentState对象。MapGet将被执行,根据输入的单词,查询单词的数量。因为DRPC流的分组方式与TridentState分组方式相同(都是通过word字段),所以每个单词查询会被自动路由到该单词的TridentState对象的分区。

行9,通过FilterNull函数过滤掉没有计数结果的单词。

行10,使用Sum函数对存在计数结果的进行加和,然后通过Trident自动将结果返回客户端。

Trident在如何以最大限度的提高性能来执行拓扑方面是非常智能的。比如,它会进行下面两个提高性能的自动化操作:

对状态的读取或写入操作(如persistentAggregate和stateQuery),会自动的进行批处理。比如,如果需要在当前批处理操作中执行20次更新操作,Trident会自动批量的读取或写入,仅执行一次读请求或写请求,而不是20次。
Trident聚合操作进行了大量优化。Trident会将同一个组中的所有tuple发送到同一台机器,进行部分聚合操作,然后再通过网络发送tuple。比如,Count聚合操作会先计算每个分区的数量,然后将这些统计结果通过网络传输到一起,再根据这些初始统计结果计算最后的结果。

项目需求

希望实现下图,这是一个HighChart的显示效果,
每隔一段时间从Hbase里查询数据刷新页面
这里写图片描述

这里主要讲后端实现,如何计算TOP5数据和保存到Hbase。前端内容和前面差不多,可以参考前面的文章。

项目中的Trident


DRPC方式基于hbase state方式
package cn.wh.stormtest;
import hbase.state.HBaseAggregateState;
import hbase.state.TridentConfig;
import kafka.productor.KafkaProperties;
import storm.kafka.BrokerHosts;
import storm.kafka.StringScheme;
import storm.kafka.ZkHosts;
import storm.kafka.trident.TransactionalTridentKafkaSpout;
import storm.kafka.trident.TridentKafkaConfig;
import storm.trident.TridentState;
import storm.trident.TridentTopology;
import storm.trident.operation.builtin.Count;
import storm.trident.operation.builtin.FilterNull;
import storm.trident.operation.builtin.FirstN;
import storm.trident.operation.builtin.MapGet;
import storm.trident.operation.builtin.Sum;
import storm.trident.state.StateFactory;
import storm.trident.testing.MemoryMapState;
import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.LocalDRPC;
import backtype.storm.StormSubmitter;
import backtype.storm.generated.AlreadyAliveException;
import backtype.storm.generated.InvalidTopologyException;
import backtype.storm.generated.StormTopology;
import backtype.storm.spout.SchemeAsMultiScheme;
import backtype.storm.tuple.Fields;
import backtype.storm.utils.Utils;
 
public class TridentTopo {
 
    public static StormTopology builder(LocalDRPC drpc)
    {
        TridentConfig tridentConfig = new TridentConfig("state");
        //HBaseAggregateState 是一个需要实现的基于Hbase存储的Trident state
        StateFactory state = HBaseAggregateState.transactional(tridentConfig);
       
        BrokerHosts zkHosts = new ZkHosts(KafkaProperties.zkConnect);
        String topic = "track";
        TridentKafkaConfig config = new TridentKafkaConfig(zkHosts, topic);//从Kafka获得输入的配置
        config.forceFromStart = false; //测试时用true,上线时必须改为false
        config.scheme = new SchemeAsMultiScheme(new StringScheme());
        config.fetchSizeBytes = 100 ;//batch size
       
        TransactionalTridentKafkaSpout spout  = new TransactionalTridentKafkaSpout(config) ;
       
        TridentTopology topology = new TridentTopology() ;
        //销售额
        //从Kafka获得输入
        TridentState amtState = topology.newStream("spout", spout)
        .parallelismHint(3)
        .each(new Fields(StringScheme.STRING_SCHEME_KEY),new OrderAmtSplit("\\t"), new Fields("order_id","order_amt","create_date","province_id","cf"))
        .shuffle()
        .groupBy(new Fields("create_date","cf","province_id"))
        .persistentAggregate(state, new Fields("order_amt"), new Sum(), new Fields("sum_amt"));
      //提供查询销售额的功能,这里用来做测试
        topology.newDRPCStream("getOrderAmt", drpc)
        .each(new Fields("args"), new Split(" "), new Fields("arg"))
        .each(new Fields("arg"), new SplitBy("\\:"), new Fields("create_date","cf","province_id"))
        .groupBy(new Fields("create_date","cf","province_id"))
        .stateQuery(amtState, new Fields("create_date","cf","province_id"), new MapGet(), new Fields("sum_amt"))
        .each(new Fields("sum_amt"),new FilterNull())
        .applyAssembly(new FirstN(5, "sum_amt", true))
        ;
       
        //订单数
        //从Kafka获得输入
        TridentState orderState = topology.newStream("orderSpout", spout)
        .parallelismHint(3)
        .each(new Fields(StringScheme.STRING_SCHEME_KEY),new OrderNumSplit("\\t"), new Fields("order_id","order_amt","create_date","province_id","cf"))
        .shuffle()
        .groupBy(new Fields("create_date","cf","province_id"))
        .persistentAggregate(state, new Fields("order_id"), new Count(), new Fields("order_num"));
      //提供查询订单数的功能,这里用来做测试
        topology.newDRPCStream("getOrderNum", drpc)
        .each(new Fields("args"), new Split(" "), new Fields("arg"))
        .each(new Fields("arg"), new SplitBy("\\:"), new Fields("create_date","cf","province_id"))
        .groupBy(new Fields("create_date","cf","province_id"))
        .stateQuery(orderState, new Fields("create_date","cf","province_id"), new MapGet(), new Fields("order_num"))
        .each(new Fields("order_num"),new FilterNull());
        return topology.build() ;
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        LocalDRPC drpc = new LocalDRPC();
        Config conf = new Config() ;
        conf.setNumWorkers(5);
        conf.setDebug(false);
        if (args.length > 0) {
            try {
                StormSubmitter.submitTopology(args[0], conf, builder(null));
            } catch (AlreadyAliveException e) {
                e.printStackTrace();
            } catch (InvalidTopologyException e) {
                e.printStackTrace();
            }
        }else{
            LocalCluster localCluster = new LocalCluster();
            localCluster.submitTopology("mytopology", conf, builder(drpc));
        }
    }
 
}
`` 
下面是测试程序,查询销售额,订单数
``java
----------------------------DRPC Client-------------
import backtype.storm.LocalDRPC;
import backtype.storm.utils.DRPCClient;
import backtype.storm.utils.Utils;
 
public class TridentDRPCclient {
    public static void main(String[] args) {
       
        DRPCClient client = new DRPCClient("192.168.1.107", 3772);
        try {
                while (true)
                {//生成DRPC输入数据用于测试
                    System.err.println("销售额:"+client.execute("getOrderAmt", "2014-09-13:cf:5 2014-09-13:cf:8")) ;
                    System.err.println("订单数:"+client.execute("getOrderNum", "2014-09-13:cf:1 2014-09-13:cf:2")) ;
                    Utils.sleep(5000);
                }
            } catch (Exception e) {
                e.printStackTrace() ;
            }
       
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值