Storm transaction topology
Storm 可以保 证Tuple至少被处理一次,最常见的关于的Storm的问题是: 既然失败的Tuple可以被Replay, 那么它如何处理像计数统计这样的问题呢? 这样不会重复计数吗?在Storm0.7版本中提供的transaction Topology机制可以保证每个Tuple 精确的被处理一次。
本文主要阐述
- transaction Topology基本概念和原理
- 使用transaction Topology API
- Transaction Topology的实现
transaction Topology基本概念和原理
下面我们一步一步介绍Transaction Topology怎么实现, 先从最简单的实现模型开始
Design 1
Transaction Topology 最核心的理念是:对处理的数据提供一个 Strong Ordering. 最基本的实现是一次只处理一个Tuple,直到目前处理的Tuple被Topology处理完毕才开始下一个.每个Tuple和一个Transaction Id 关联, 如果这个Tuple处理失败, 我们需要用同样的Transaction Id 去 Replay Tuple.
下面看一个例子。
假设你想要对Steam里面的Tuples做全局计数。我们不仅仅需要在数据库中存储目前的计数值, 还需要记录最新的Transaction Id。 当你需要更新数据库中的计数值时,你首先要判断数据库中Transaction Id 和目前正在处理的Tuple的Transaction Id 是否一样, 如果一样说明这个Tuple已经在数据库中被统计过,不需要进行加1跳过这次更新。我们可是推断Tuple在成功更新数据库后返回成功消息给Storm的时候出错了。如果不一样,我们就可以安全的更新数据库的存储值了。
这样的设计我们可以保证全局统计的结果是准确的, 能保证exactly-once的语义,但是这种方法存在一个问题,由于每次只能处理一个Tuple,并且只有这一个Tuple处理成功之后才可以开始下一个导致数据处理的效率不高,从而不能充分利用Storm的高并发能力。
Design 2
除了一次处理一个Tuple,一个更好的方法是每个Transaction 一次处理一批Tuple。在全局计数的例子中,我们可以用整个Batch 中Tuple的个数更新计数值。 如果批处理失败,我们应该relay整批Tuple。我们给每批Tuple分配一个 Transaction Id,这种批处理也是strong ordering的。
如果我们每批处理1000个tuple, 那么和每次只处理一个tuple相比,我们的应用将要减少1000X的数据库操作。另外,由于批量计算可以并行化,我们可以充分利用Strom的高并发特性。虽然这种方法效率比每次只处理一个Tuple的效率高,但是它也没用尽可能充分的使用资源。整个Topology的worker大部分处于等待其他部分完成的闲置状态。例如,在一个这样的Topology中, 在bolt1 完成他的处理任务后 一直处于闲置状态直到bolt2,bolt3, bolt4完成并且Spout排出下批tuples。
Design 3
我们知道不是批量处理任务的所有工作都需要Strong Ordering ,比如当我们的全局计数的例子中,可以拆分为两部分的计算工作,一是计算批量Tuple的计数, 二是更新数据库的全局计数。第二部分工作在批量Tuple之间是需要StrongOrdering的,但是第一部分工作在批量tuple之间是可以并行计算的,因此在批量1进行更新数据库的同时,批量2至批量10可以进行第一部分工作,最大限度地利用空闲的资源。
Storm就是按照这个方法把一个批量的计算分为2个阶段
第一个阶段是processing处理阶段: 这个阶段允许多个批量任务可以同时并行计算。
第二个阶段是commit提交阶段:这个阶段是对多个批量处理任务是StrongOrdering的。直到批量任务1的Commit提交阶段成功处理完毕,批量处理任务2的提交阶段才开始执行
这两个阶段合在一起成为“事务”。很多批量任务能并行处理在第一处理阶段,但是只有一个批量任务执行在第二个提交阶段。如果一个批量任务在处理阶段或者提交阶段发生了错误,整个事务过程需要Replay(两个阶段都需要Replay)。
使用transaction Topology API
详细设计
当使用Transaction Topology 的时候, Storm为我们提供了以下:
1 状态管理: Storm把所有TransactionTopology的状态信息存储在Zookeeper中,包括目前的transactionId, 每个Batch定义的参数的Metadata信息。
2 协调多个Transaction: Storm 管理了所有决定哪些Transactions应该被处理 和 什么时候提交的必要的信息
3 错误发现: Storm 借助 Acking框架高效的判断什么时候Batch被成功的处理,什么时候成功的被提交和失败。Storm会适当的自动的Replay失败的Transaction,我们不必自己Ack,Storm帮我们管理这些。
4 一流Batch处理API:在常规的Bolts的基础上抽象出一个API来支持批量的Tuple处理, Storm管理了所有的状态来判断哪个Transcation已经收到所有Batch中的Tuple。Storm也负责清理每个Transaction累积的状态信息。
最后需要说明的是Transaction Topology需要一个数据源队列来支持,以便能够Replay一个确切的Batch。Kestrel不支持这样,但是ApacheKafka完美的适合这类Spout,Storm-Kafka包含了一个KafkaTransaction Spout 的实现
实例解析
MemoryTransactionalSpout spout = new MemoryTransactionalSpout(DATA, new Fields("word"), PARTITION_TAKE_PER_BATCH);
TransactionalTopologyBuilder builder = new TransactionalTopologyBuilder("global-count", "spout", spout, 3);
builder.setBolt("partial-count", new BatchCount(), 5).noneGrouping("spout");
builder.setBolt("sum", new UpdateGlobalCount()).globalGrouping("partial-count");