storm-[3]-Trident Tutorial 与调优

源文档:http://storm.apache.org/releases/1.1.0/Trident-tutorial.html

Trident是Storm顶层实时计算的高度抽象,无缝处理数以百万每秒的高吞吐量,提供低延迟高可用性的分布式查询。

  • 提供 joins, aggregations, grouping, functions, and filters操作,此外,可在任何数据库或持久存储层提供有状态的增量处理
  • 提供一致性的exactly-once 原语

例子

该示例主要完成:

  1. 计算数据源句子中的单词数
  2. 对一组words查询累计统计结果 

1-单词计算

通过下面的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);
计算部分如下:
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);

上述代码中:

  1. 首先创建一个TridentTopology对象,为构建Trident计算提供接口。
  2. TridentTopology的newStream用来从输入数据源(spout 上例中即为spout,前面创建的FixedBatchSpout,数据源也可为queue brokers like Kestrel or Kafka)中创建一个新的数据流。
  3. Trident持续跟踪每一个元数据(metadata信息)在Zookeeper中一定量的状态信息,如上,"spout1"用于表示Trident持续跟踪的元数据信息(metadata)在Zookeeper中的node。

Trident用以处理批数据的API已相对成熟,这些API 类似于Hadoop高层抽象家族中的 Pig or Cascading。可以执行 joins, aggregations, run functions, run filters操作。

Trident拥有first-class functions 用于查询数据源实时状态。

Trident以tuples批为单位处理stream流,例如,输入句子流可能被分批如下(根据输入一般吞吐量,以数千计或数以百万计为单位顺序分批):

Batched stream

继续分析上例spout emits包含"sentence" field 的数据流后,topology定义对每个tuple中应用到了 Split function切分单词,具体如下:

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));                
       }
   }
}

接下来就是计算和结果的持久化存储,具体如下:

  1. 首先stream以"word" field分组(grouped)
  2. 组内以Count aggregator持续聚合
  3. persistentAggregate function知道如何存储和更新聚合结果

上例中结果存于内存中,也可使用 Memcached, Cassandra或其他持久存储工具以Memcached为例:

(using trident-memcached), where the "serverLocations" is a list of host/ports for the Memcached cluster:

.persistentAggregate(MemcachedState.transactional(serverLocations), new Count(), new Fields("count"))        
MemcachedState.transactional()

值得一提的是Trident有fault-tolerant, exactly-once原语,在出现失败是会进行有必要的重试,而不是对数据库中同一数据源的多multiple updates。persistentAggregate将一个流转换成一个TridentState对象,以TridentState对象代表所有word的计算。使用TridentState对象实现计算中分布式查询部分的需求。

2-低延迟结果查询

topology的另一部分是实现低延迟的分布式单词计算结果的查询

以空白分割的一串词(如"cat dog the man")作为输入,输出这些词的计算结果。查询过程类似一般的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]]"

The implementation of the distributed query portion of the topology looks like this:

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"));

上例function TridentTopology常用于创建一个DRPC流,这个function命名为"words",在DRPCClient的execute中作为第一个参数。

每一DRPC请求被视为一个小的批处理job,输入的单个tuple作为request。tuple包含一个名为 "args" 的field,内容为client提供的argument。在此例中argument是空白分割的一串词。

  1. 首先arguments输入被分词,Fields("word")
  2. 流以"word"分组
  3. stateQuery用来查询上一个topology(wordCounts 生成的)TridentState 对象,MapGet用来获取单词统计结果,因为DRPC stream以TridentState相同的分组方式,每一个单词查询被路由到管理单次更新的TridentState对象的确定分区
  4. 没有统计结果的单词被FilterNull过滤,统计结果用Sum aggregator 聚合得到最终的结果
  5. 最后,Trident自动将结果发送给等待中的client

Trident在执行topology时的自适应优化:

  1. 对于繁多的读写状态操作(like persistentAggregate and stateQuery) 自动的转换为批操作。
  2. Trident aggregators以最优化,避免一次性将所有tuples转移到一台机器后做聚合,Trident先在单机中做预聚合,然后将预聚合结果网络传输到一个节点做最后的聚合操作,这类似与MapReduce的combiners

Reach

一个 DRPC topology  统计链接的URL:取得所有曾经tweeted  URL的用户,以及这些用户的followers,followers去重然后计算。

topology读取两个源的state:一个数据库为URLs 和访问用户用户映射关系,另一为用户 和该用户的followers列表。

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"));

细节简述:

  1. topology 以newStaticState建立代表每个外部数据库TridentState objects
  2. topology的定义很直接,就是一个简单的批处理job。首先,请求urlToTweeters database得到这个request下tweeted URL的用户列表。ExpandList function 调用来建立为每一个tweeter建立一个tuple
  3. 获取每一个tweeter的followers。这一步必须是并行的,所以shuffle()被调用,获取followers database中每一个tweeter的followers,这一步是计算部分最耗资源部分并行度也设置的相对较高
  4. followers结果集的去重和计算:先按 "follower"分组,对组运行“One" aggregator( "One" aggregator simply emits a single tuple containing the number one for each group),然后count聚合
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;
   }        
}

Fields and tuples

Trident的数据model为TridentTuple,TridentTuple是一组被命名的values列表。topology中tuples由一组不断增长的操作序列构建,通常操作由一组输入fields  emit出来一组"function fields".

输入fields用来选择tuple的一个子集作为操作的输入,"function fields"命名操作的输出。

例如:

(1)BaseFilter

设已有一个名为 "stream" 的流,包含 fields "x", "y", "z". 利用筛选去MyFilter将"y" 作为输入:stream.each(new Fields("y"), new MyFilter())

This will keep all tuples whose "y" field is less than 10.

public class MyFilter extends BaseFilter {
   public boolean isKeep(TridentTuple tuple) {
       return tuple.getInteger(0) < 10;
   }
}
(2)BaseFunction

看下"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));
   }
}
设有一个流中包含fields "x", "y", and "z":
stream.each(new Fields("x", "y"), new AddAndMultiply(), new Fields("added", "multiplied"));

输出tuples将含有 fields "x", "y", "z", "added", and "multiplied". "added"

(3)aggregators

对于aggregators,函数的fields将替代输入tuples,设有一个流包含fields "val1" and "val2":

stream.aggregate(new Fields("val2"), new Sum(), new Fields("sum"))

输出将仅为"sum" field,代表 所有"val2"在批数据中的和

(4)grouped

grouped streams中grouping fields 也将随aggregator的输出已同作为输出:

stream.groupBy(new Fields("val1"))
     .aggregate(new Fields("val2"), new Sum(), new Fields("sum"))

In this example, the output will contain the fields "val1" and "sum".

State

Trident 中state的维护:

  1. Each batch is given a unique id called the "transaction id". If a batch is retried it will have the exact same transaction id.
  2. State updates are ordered among batches. That is, the state updates for batch 3 won't be applied until the state updates for batch 2 have succeeded.

 if you don't want to pay the cost of storing the transaction id in the database, you don't have to. In that case the State will have at-least-once-processing semantics in the case of failures (which may be fine for your application). You can read more about how to implement a State and the various fault-tolerance tradeoffs possible in this doc.

A State is allowed to use whatever strategy it wants to store state. So it could store state in an external database or it could keep the state in-memory but backed by HDFS (like how HBase works). State's are not required to hold onto state forever. For example, you could have an in-memory State implementation that only keeps the last X hours of data available and drops anything older. Take a look at the implementation of the Memcached integration for an example State implementation.

Execution of Trident topologies

 Tuples are only sent over the network when a repartitioning of the data is required, such as if you do a groupBy or a shuffle. So if you had this Trident topology:

Compiling Trident to Storm 1

It would compile into Storm spouts/bolts like this:

Compiling Trident to Storm 2 

storm
hadoop
Nimbus JobTracker
Supervisor TaskTracker
Topology Job

 

client--→Nimbus---zookeeper→Supervisor-→Worker-→Executor

UI中参数

Topologystats中complete latency <=500ms,Bolts中capacity 0.x或0.0x,Execute  latency<=20ms

 Kakfa spout 的并发

        设置为对应 topic 并发度(partition)的 1/2

io 密集型

如果 bolt 有大量的外部服务调用,比如访问 tair,mysql,hbase 等,而 cpu 计算较少,可以将 bolt 的并发开大一些,最大为 worker 数 的 8 倍

cpu 密集型

如果 bolt 的 cpu 计算比较多,比如 json 解析,类型转换等,最大并发可以开到 worker 数 的 4 倍

最大积压值&超时时间

最大积压值

每个 spout 的 task 实例内部都维护了一个 pending RotatingMap 超时的队列,其内部存储了 已经接收的 但尚未 成功处理{指从这里发送到下游的tuple树状结构的每个叶子节点都 ack 了} 的 消息,若一段时间没收到 ack 消息,则认为该消息处理超时了,会被强制 fail,从 pending 队列里移除,并加入 spout 实例的 fail 队列待处理,spout 获取下个 batch 的消息时会优先从 fail 队列中获取,但是因为重试策略的原因【比如时间指数回退】不一定会绝对在下一个 batch 优先重发

超时时间

配置消息的过期时间,其和上面的最大积压值息息相关,队列里的消息多,单个消息从接收到处理的耗时就会增加,就会更容易超时,所以

结论:如果你的单条消息处理本身比较耗时,比如有网络 io 或者下游链路特别长,请调小 最大积压值 调大 超时时间

经验值:最大积压 2000 超时 120

acker数量

一般设置为 worker 数量的 1/2

性能瓶颈

出现性能最直接的影响是消费延迟,如果某个 topic  partition 延迟消费

如果是某个组件队列比较拥堵,在 Storm UI 拓扑图上组件会显示为 红色,数字为延迟时间

优化的前提是拓扑没有出现运行时异常,导致 wroker 重启从而影响了性能, 日志观察有无 worker 重启异常

如果出现 Async loop died! xxxxx 表明拓扑 worker 在重启,有 exception throw 出来!

如果没有异常信息,很可能拓扑真的性能不够了 优先调整组件的并发度 后重启,观察 整体的处理时间,及有无 红色 的组件!如果还是有加 worker 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值