Storm批处理事务详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37142346/article/details/83387273

1.为什么需要批处理事务

在流式计算中,我们经常需要保证 exactly-once 语义。Storm的一个Spout在发送数据后如果处理失败,由于其ack/fail机制,我们可以得知是那一批数据处理失败,从而重新发送数据进行处理,但是这时会有一个问题,有可能会重复处理了同一批数据,尤其在一些要求比较高的场景(比如支付场景),这样会造成严重的后果,因此为了确保 exactly-once 语义,保证数据仅且被执行一次,在Storm 0.7.0之后引入了transactional topologies

2.事务机制原理

对于只需要处理一次的场景,为了确保 exactly-once 语义,我们可以设计为每一个tuple设置一个tid进行标识,我们可以通过tid来判断当前事务是否被执行过,所有比tid小的事务必须执行完毕。对于这种设计,如果我们需要连接数据判断tid,那么对于每一个tuple都必须和数据进行交互,消耗了大量的资源而且降低了处理速度;因此,我们可以以batch为处理单位,为每一批tuple设置一个tid。
在这里插入图片描述

虽然上面这种设置避免了资源的浪费,但是对于每一个batch都必须等待其他batch处理完毕,如下面topology:
在这里插入图片描述
为了提高并行度,storm采用了pipeline(管道)处理模型,将一个batch分为两个阶段,processing和commit阶段。在processing阶段,多个batch可以并行计算,在commit阶段,必须按照强顺序性执行,最后提交事务。

在使用Transactional Topologies的时候,Storm会做以下几点:

  1. 管理状态:Storm使用zookeeper来保存事务状态,包括一些tid以及元数据。
  2. 协调事务:Strom内部帮你管理一切事务的执行,如在任何一点是processing阶段还是commit阶段。
  3. 错误检测:Storm利用acking框架高效的来处理失败的事务,当事务失败时会replay相应的batch,你不需要手动的进行acking或者anchoring。
  4. 内置的批处理API:Storm在普通的bolt之上封装了一层API来提供对tuple事务的处理。Storm管理所有的协调工作,保证一个bolt什么时候收到一个特定的transaction的所有tuple,同时也会清除每一个transaction产生的中间数据。

3.事务API的简单介绍

与之前的Storm程序一样,我们需要使用一个TopologyBuilder来设置Spout和Bolt。相应的事务topology使用TransactionalTopologyBuilder来设置。对于Spout我们可以实现ITransactionalSpout接口,在这个接口中包含两个内部接口类,Coordinator和Emitter,ITransactionalSpout保证了相同的批处理事务必须发送相同的tid。实际上,实现ITransactionalSpout接口的Spout是一个sub-topology,如下图所示:
在这里插入图片描述

Coordinator开启一个事务准备发射一个batch的时候,进入一个事务的processing阶段,会发射一个事务tuple(包括transactionAttempt和metadata)到“batch emit”流中。(其中transactionAttempt包含"transaction id"和"attempt id","transaction id"对batch进行标识,"attempt id"唯一标识了对于同一batch发射不同的tuple;metadata中包含当前事务可以从当前哪个point进行重放数据,存放在zookeeper中,spout可以通过kryo从ZK中序列化和反序列化元数据)。

Emitter以all grouping(广播)的方式订阅coordinator的"batch emit",负责为每一个batch发射tuple。发送的tuple都必须以transactionAttempt作为第一个field,storm会根据它来判断发送的tuple属于哪一个batch。

其中Coordinator只有一个,Emitter可以通过并行度来设置。

对于普通的批处理Bolt可以实现IBatchBolt接口,对于事务Bolt可以实现BaseTransactionalBolt。在BaseTransactionalBolt接口中会继承父类的几个方法,如下:

void prepare(Map conf, TopologyContext context, BatchOutputCollector collector, T id);
    void execute(Tuple tuple);
    void finishBatch();

在处理batch的时候,处理每一个tuple的时候都可以调用execute方法,而在整个batch处理完毕(processing阶段完毕)的时候才能调用finishBatch方法。如果一个Bolt被标记为Committer,那么只有在commit阶段才能调用finshBatch方法。storm保证该bolt之前的所有bolt执行完毕。

下面有两个问题,Storm如何保证之前的所有Bolt都执行完毕?怎样将一个Bolt设置为Committer?

首先第一个问题,在bolt内部,有一个CoordinatedBolt模型,在CoordinatedBolt中记录着两个值,有哪些task给我发送了tuple,我需要给哪些task发送tuple。等所有的tuple发送完毕之后,CoordinateBolt通过另外一个特殊的stream以emitDirect的方式告诉所有发送过它tuple的task,它发送了多少tuple给这个task,下游task会将这个数字和自己接受到的tuple数进行对比,如果相等,则表示处理完了所有tuple。下游的CoordinateBolt重复上述步骤,继续通知其下游。

对于第二个问题,如何设置一个Bolt为Committer,总共有两种方式。可以实现ICommitter接口来标识为Committer或者使用TransactionalTopologyBuilder 的setCommitterBolt 方法来设置一个Committer。

最后要提的是尽管Strom的批处理事务被标记为deprecated ,使用Trident框架来替代,但是理解了批处理事务的原理也是学习Trident框架的基础和关键,因此,值得了解该部分内容。


欢迎加入大数据开发交流群 731423890:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_37142346/article/details/83387273