Storm Trident

Trident是一个用来在storm上进行实时计算的高层抽象。它将无缝的融合超大流量以及状态流处理使我们能够以极低延迟进行分布式查询。如果你对Pig或者Cascading这种的高层批处理工具很熟悉,那么Trident的概念就很容易理解了----Tridet拥有joins(连接)、aggregations(汇聚)、grouping(分组)、functions(函数)以及filters(过滤器)。除了这些,Trident添加了原语用于在任意的数据库或持久化存储上状态、增量处理。Trident具有一致,仅处理一次(exactly-once)的语意。

示例

这个示例将会做以下两件事

1.从句子的输入流中计算流单词数

2.实现查询单词列表中计数的和。

为了实现这个目的,这个例子将会从下面这个源中读取无限的句子流:

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通过上面这几个句子不断的循环,来产生源源不断的句子流。

下面的代码是进行整个计算中的流单词计算部分:

TridentTopology topology = new TridentTopology();        
TridentState wordCounts =
     topology.newStream("spout1", spout)
       .each(new Fields("sentence"), new Split(), new Fields("word"))
       .groupBy(new Fields("word"))
       .persistentAggregate(new MemoryMapState.Factory(), new Count(), new Fields("count"))                
       .parallelismHint(6);
我们一行一行的来看这段代码。首先TridentTopology对象被创建了,它为构建Trident计算提供了接口。TridentTopology拥有一个叫做newStream的方法,用来读取输入源中的数据并在该topology中创建新的数据流(其实就是进行了数据形式转换)。其中的输入源就是前面的FixedBatchSpout定义的数据。输入源也可以是像Kestrel或者Kafka这样的代理。Trident追踪每个输入源放在Zookeeper中的少量状态(Trident消费的资源的元数据),并且这里的"spout1"字符串就表示Zookeeper中Trident用来放置元数据的节点。

Trident将数据流处理成小批量的tuples。如下图:


各个小批量数据大小依输入量的大小而定,但通常情况下,这些小批量数据之间的大小差距不会太大(也就是会比较平均的分批)。

Trident提供了丰富完整的批处理API来处理这些小批量数据。你可以进行group by(分组),joins(连接),aggregations(汇聚),运行函数,运行过滤器等等。当然,只能处理单个小批量数据并没有什么卵用,所以Trident提供了函数来进行跨批汇聚(aggregations across batches)以及持久化存储这些汇聚结果(aggregations)-无论是存在内存、Memcached、Cassandra或者其他的一些存储都是可以的。最后,Trident提供了first-class函数(?)来查询源数据的实时状态。这状态可以由Trident进行更新,或者它可以作为状态的一个独立源。

回到这个例子,spout发送了一个流,其中包含一个叫做"sentence"的字段。该topology定义的下一行就运用Split函数来分割该stream的每个tuple。使"sentence"字段分割成多个单词。下面是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));                
       }
   }
}


topology的剩下部分计算单词的计数以及将结果进行持久化存储。首先该数据流会被依据"word"字段进行分组。然后,通过Count aggregator对每组进行持续的汇聚。persistentAggregate函数知道在状态的源上如何存储以及更新汇聚的结果(即如何更新状态)。在这个例子中,单词计数将存储在内存中,当然也可以转换成Memcached,Cassandra,或者其他的持久化存储。将该topology转换成在Memcached中存储单词计数只需要见得用trident-memcached替换persistentAggregate,"serverLocations"是Memcached集群的主机/端口列表:

.persistentAggregate(MemcachedState.transactional(serverLocations), new Count(), new Fields("count"))        
MemcachedState.transactional()
存储在persistentAggregate中的值代表着从流中发送来的所有批量数据的汇聚。

另一件流弊的事是Trident是完全容错的,仅处理一次的语意。这使得实时处理更容易理解。Trident如果出现处理失败的情况,需要回退,它不会对同源数据的数据库进行重复更新。

persistentAggregate方法将一个数据流变成的TridentState对象。这种情况下,TridentState对象代表所有的单词计数结果。我们将使用TridentState对象来实现计算中的分布式查询部分。

该topology的下部分实现了对单词计数的低延迟分布式查询。这个查询以空格为分割符的单词列表(不要把它当成句子)为输入,返回的是这些单词计数的总和。这个查询执行起来就像正常的RPC调用,但是它们是在后台并发执行的。下面是调用这些查询的一个例子:

DRPCClient client = new DRPCClient("drpc.server.location", 3772);
System.out.println(client.execute("words", "cat dog the man");
// prints the JSON-encoded result, e.g.: "[[5078]]"
像这种小量的查询延迟一般是10ms。更加密集的DRPC查询将会耗费更长时间,这种延迟很大程度上取决于你分配在该计算上的资源的多少

本topology的分布式查询部分的实现如下:

topology.newDRPCStream("words")
       .each(new Fields("args"), new Split(), new Fields("word"))
       .groupBy(new Fields("word"))
       .stateQuery(wordCounts, new Fields("word"), new MapGet(), new Fields("count"))
       .each(new Fields("count"), new FilterNull())
       .aggregate(new Fields("count"), new Sum(), new Fields("sum"));
同一个TridentTopology对象被用来生成DRPC流,该函数被命名为"words"。该函数名与在使用DRPCClient的execute方法中的第一个参数相对应。

每个DRPC请求都被看作是自身的一个小批量的处理作业,该作业以输入单个的tuple来表示请求。这个tuple包含一个叫"args"的字段,该字段值为client提供的参数。本例中即为"cat dog the man"。

首先,Split函数将请求的参数分割成各个单词。形成的流将按"word"字段进行分组,stateQuery操作用来查询topology第一部分生成的TridentState对象。stateQuery接收一个状态源以及一个查询状态的函数。在本例中,MapGet function被调用,用来获取单词的计数。由于DRPC流被以与TridentState相同的方式进行了分组(按"word"字段分组),每个单词的查询将被导向TridentState相应分区(具体到哪个分区由TridentState确定,因为TridentState管理着各个单词计数状态)。


接下来,没有计数值的单词将被FilterNull过滤器过滤掉并且这些计数值将会通过Sum aggregator进行汇聚来得出最终的结果。然后,Trident自动的发送该结果到等待的客户端。

Trident在如何以最大的性能来执行一个topology这方面是智能的。有以下两件有趣的事情将会在topology中自动发生:

1.对状态的读或者写操作(例如:persistentAggregate以及stateQuery)会自动的对状态进行批量化操作。所以,如果目前有对数据库的20次更新请求,Trident会将本来要进行的20次读请求以及20次写请求自动的合并为一次读请求和一次写请求。(在许多的例子中,你可以在状态实现中使用缓存来消除读请求)。这样即方便又高效。

2.Trident的aggregators是经过大量的优化的。当在可能需要通过网络发送tuples时,Trident会先进行分区汇聚,而不是把所有分好组的tuples都传输到同一台机器上再进行汇聚。

public class One implements CombinerAggregator<Integer> {
   public Integer init(TridentTuple tuple) {
       return 1;
   }

   public Integer combine(Integer val1, Integer val2) {
       return 1;
   }

   public Integer zero() {
       return 1;
   }        
}

例如,Count aggregator首先计算每个分区的计数值并进行汇聚这些计数值,然后通过网络发送分区的计数值,然后通过对所有的分区计数值求和来获得完整的count数值。这个计算有点像MapReduce中的combiner。


我们来看Trident的下个例子

Reach

这个例子是一个纯粹的DRPC topology用来计算URL的reach( 影响力)。URL的Reach(影响力)是在Twitter上发表该URL的用户量(每个用户只算一次)。为了计算reach,我们需要获取发了这个URL的用户(tweeters),以及这些用户的跟随者(followers),然后将tweeters与followers进行合并且去重形成一个集合,这个集合里的元素个数就为该URL的reach。


下面的topology将会读取两个状态源。一个是URL与发表了该URL用户列表的映射数据库。另一个就是用户与其跟随者列表的映射数据库。:

 

TridentState urlToTweeters =
       topology.newStaticState(getUrlToTweetersState());
TridentState tweetersToFollowers =
       topology.newStaticState(getTweeterToFollowersState());

topology.newDRPCStream("reach")
       .stateQuery(urlToTweeters, new Fields("args"), new MapGet(), new Fields("tweeters"))
       .each(new Fields("tweeters"), new ExpandList(), new Fields("tweeter"))
       .shuffle()
       .stateQuery(tweetersToFollowers, new Fields("tweeter"), new MapGet(), new Fields("followers"))
       .parallelismHint(200)
       .each(new Fields("followers"), new ExpandList(), new Fields("follower"))
       .groupBy(new Fields("follower"))
       .aggregate(new One(), new Fields("one"))
       .parallelismHint(20)
       .aggregate(new Count(), new Fields("reach"));
topology用newStaticState方法来将外部数据库转换成一个TridentState对象。在topology中这些TridentState就可以用来进行查询。像所有的状态源一样,对这些数据库进行查询也会自动的进行批量操作以实现高效率。

这就是个简单的批处理作业。首先,查询urlToTweeters数据库来获取发送了该URL的用户(tweeter),这将返回一个列表,所以调用ExpandList函数为每个tweeter创建一个tuple。

接下来,每个tweeter的flollowers将会被获取。这一步是并行进行的(!!!!!),所以调用shuffle来将tweeters分布到所有的workers上。然后,查询followers的数据库来获取每个tweeter的followers列表。可以看到这一步的并发数很大。

接下来,对followers进行了去重和计数。首先,第一步"group by"进行了分组去重,然后在每个分组上运行了"One" aggregator。"One" aggreagator简单的为每个分组发送一个包含数值1的tuple。然后,这些1将被进行相加求和来获取followers集合的计数值。"One"aggregator定义如下:

public class One implements CombinerAggregator<Integer> {
   public Integer init(TridentTuple tuple) {
       return 1;
   }

   public Integer combine(Integer val1, Integer val2) {
       return 1;
   }

   public Integer zero() {
       return 1;
   }        
}
这是一个"combiner aggregator",它将会在进行网络传输tuple之前进行分区汇聚以提升效率。求和也被定义为combiner aggregator。所以全局的求和也将十分高效。

Fileds and tuples

看例子:

stream.each(new Fields("y"), new MyFilter())
假设MyFilter的实现如下:
public class MyFilter extends BaseFilter {
   public boolean isKeep(TridentTuple tuple) {
       return tuple.getInteger(0) < 10;
   }
}
该过滤器将会保留"y"字段值小于10的tuple。该TridentTuple作为MyFilter的输入时将包含"y"字段。注意Trident在我们选择了相应要输入的字段后,其映射tuple的子集形成Trident tuple效率极高。

接下来看看"function fields":

public class AddAndMultiply extends BaseFunction {
   public void execute(TridentTuple tuple, TridentCollector collector) {
       int i1 = tuple.getInteger(0);
       int i2 = tuple.getInteger(1);
       collector.emit(new Values(i1 + i2, i1 * i2));
   }
}
该函数以两个数字作为输入,然后发送2个新值。如上所示。假设我们有一个流包含字段"x","y","z"。我们应该按如下方式:

stream.each(new Fields("x", "y"), new AddAndMultiply(), new Fields("added", "multiplied"));
函数的输出是可加的:即函数输出的字段将会被加到输入的tuple中,因此tuple将会有5个字段:"x","y","z","added"以及"multiplied"。

如果使用aggregators,函数输出的字段将会代替输入tuple中的字段,所以如果我们有一个流包含字段"val1"和"val2",进行如下操作:

stream.aggregate(new Fields("val2"), new Sum(), new Fields("sum"))
输出将会变成只有单个tuple并且该tuple只有一个字段"sum",代表该批tuple中所有的"val2"字段值的和。

如果对stream进行group(分组),最终的输出就会包括用来进行分组的字段以及后面aggrefator产生的字段。例如:

stream.groupBy(new Fields("val1"))
     .aggregate(new Fields("val2"), new Sum(), new Fields("sum"))
在本例中,输出将会包含"val1"以及"sum".

State(状态)

实时计算中的一个关键问题就是如何管理状态,以使得在遇到处理失败和回退时,更新具有幂等性(idempotent)。消除处理失败是不可能的,所以当一个节点挂掉了或者出现其他意外情况时,批处理需要进行回退。问题是我们如何进行状态更新(无论是对外部的数据库还是topology的内部状态)使得消息仅仅只处理一次?

这是个麻烦的问题,我们可以以下面的例子来展示。假设我们正在对流数据进行计数汇聚,并且想将这个还将不断更新的计数结果存入到一个数据库中。如果我们只是存储该计数值到数据库,我们就需要对批数据进行一个状态更新,但是我们没有任何办法知道该状态是不是以及被更新过了。比如,一次批处理更新数据库但是没有更新状态,或者更新了状态没有更新数据库。我们无从知道。

Trident通过做两件事解决这个问题:

1.每次批处理都被赋予唯一的"transaction id"。如果一次批处理回退它会拥有相同的"transcation id"。

2.状态更新在各个批处理之间是有顺序的。如果批处理"2"的状态更新没成功,批处理"3"无法进行状态更新。

通过以上两条原语,我们可以利用状态更新实现仅处理一次的语意。我们需要将transaction id以及count(计数)作为一个原子值一起存放在数据库中。然后,当我们更新count时,我们可以将数据库中的transaction id与批处理的transaction id进行比较。如果是一样的,我们就跳过本次更新。-因为将顺序性,我们可以肯定本次的批处理已经进行了更新,如果id不同,则进行更新。

当然,这些都不需要我们手动去做。这些逻辑被包含在状态抽象中,并且会自动的去执行。如果我们不想花费存储transaction id的代价,State将会有at-least-once-processiing(至少处理一次)的语意。如果想继续了解请看how to implement a State and the various fault-tolerance tradeoffs possible in this doc

一个State对象可以用任何策略去存储状态。所以它通过外部数据库或者内存(需要HDFS)来存储状态。State对象并不需要一直保持状态数据。例如,我们可以有一个内存状态对象实现一个只保持几小时内的数据。我们可以看看Memcached integration 

Trident topologies的执行流程

Trident topologies可以编译的跟Storm topology一样的高效。Tuples只会在数据重新分布时才会在网络上进行发送,例如进行了groupBy或者shuffle。如果有如下的Trident topology:


它被编译成Storm的spouts/bolts将会如下:


总结

Trident使得实时计算更加的优雅。通过Trident API,你会看到大流量的数据流处理、状态操作以及低延迟查询的无缝结合。

Trident让你以一种自然的方式操作实时计算并发挥最大的性能。


Python网络爬虫与推荐算法闻推荐平台:网络爬虫:通过Python实现闻的爬取,可爬取闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计,皆可应用在项目、毕业设计、课程设计、期末/期/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值