转载请注明出处:http://blog.csdn.net/lonelytrooper/article/details/12434915

第八章 事务性Topologies

在Storm中,正如本书前边提到的,你可以通过使用ack和fail策略来确保消息处理。但是如果元组被重放了会发生什么?你怎样确保你不会计数过多?

事务性Topologies是包含在Storm0.7.0版本中的新特性,它激活消息语义来确保你以一种安全的方式重放元组并且它们只会被处理一次。没有事务性topologies的支持,你不可能以一种完全精确、可扩展和容错的方式计数。

事务性Topologies是建立标准Storm spout和bolts之上的一个抽象。

设计

在事务性topology中,Storm使用并行和顺序元组处理的混合模式。Spout产生的批量的元组被bolts并行的处理。这些bolts中的一部分被认为是提交者,它们以某种严格排序的方式提交处理过的批量元组。这意味着如果你有两个批量,每个批量包含五个元组,两边的元组会被bolts以并行的方式处理,但是提交者bolts直到第一个元组被提交成功后才会提交第二个元组。

当处理事务性Topology时,可以从源重放批量的元组,甚至有时候是多次重放是非常重要的。所以确保你的数据源--你的spout将要连接的那个--具备这个能力。

这可以被描述成两个不同的步骤,或者阶段:

处理阶段

完全并行的阶段,许多批量被同时执行。

提交阶段

严格排序的阶段,第二个批量直到第一个批量被提交成功后才提交。

把这两个阶段称为Storm事务。

storm使用zookeeper来保存事务元数据。缺省的情况下就使用为topology服务的那个zookeeper来保存元数据。你可以通过覆盖配置键transactional.zookeeper.

servers 和 transactional.zookeeper.port.来更改。

事务实战

为看清事务怎样工作,你将建立一个Twitter分析工具。你会读取存储在Redis中的tweets,通过一系列bolts处理他们,然后存储--在另一个Redis数据库中--所有标签和它们在tweets中的频率的列表,所有用户和他们出现在tweets中的总计的列表和一个用户及他们标签和频率的列表。

你即将创建的这个工具的topology由图8-1描述.

图8-1 Topology视图

正如你看到的,TweetsTransactionalSpout会连接到你的tweets数据库并且在topology中发射批量的元组。两个不同的bolts,UserSplitterBolt 和 HashtagSplitterBolt,将从spout接收元组。UserSplitterBolt会分析tweet并且查找用户--@后边的单词--并发送这些词一个叫做users的自定义流。HashatagSplitterBolt也分析tweet,查找#以前的单词,并且发送这些单词到一个叫做hashtags的自定义流。第三个bolt,UserHashtagJoinBolt,会接收两个流并且计算在一个命名用户的tweet中一个hashtag出现了多少次。为了计数和发送结果,该bolt会是BaseBatchBolt(后边介绍更多)。

最终,最后一个叫做RedisCommitterBolt 的bolt,接收这三个--由UserSplitterBolt, HashtagSplitterBolt和 UserHashtagJoinBolt产生的流。在同一个事物中,它会完成所有计数,并且一旦完成了元组批次的处理就会发送到Redis。该bolt被认为是一种特殊的bolt,叫做提交者bolt,后续的章节中会解释它。

为了构建这个topology,使用TransactionalTopologyBuilder,类似下边的代码块:

TransactionalTopologyBuilder builder =

newTransactionalTopologyBuilder("test","spout",newTweetsTransactionalSpout());

).shuffleGrouping("spout");

builder.setBolt("hashtag-splitter",

).shuffleGrouping("spout");

)

.fieldsGrouping("users-splitter","users", newFields("tweet_id"))

.fieldsGrouping("hashtag-splitter", "hashtags", newFields("tweet_id"));

builder.setBolt("redis-committer", newRedisCommiterCommiterBolt())

.globalGrouping("users-splitter","users")

.globalGrouping("hashtag-splitter", "hashtags")

.globalGrouping("user-hashtag-merger");

我们看一下怎么在事务topology中实现spout.

The Spout

事务topology中的spout与标准的spout完全不同。

public class TweetsTransactionalSpoutextends

BaseTransactionalSpout<TransactionMetadata> {

正如你在类定义中看到的,TweetsTransactionalSpout继承自BaseTransactionalSpout并带一个泛型类型。你设置在这里的类型被认为是事务元数据。它将在后边从源发送批量的元组时被使用。

在这个例子中,TransactionMetadata被定义为:

public class TransactionMetadataimplementsSerializable{

private static final longserialVersionUID=1L;

long from;

int quantity;

public TransactionMetadata(longfrom,intquantity) {

this.from=from;

this.quantity=quantity;

}

}

这里你存储了from和quantity,它们会告诉你具体怎样产生批量的元组。

为完成spout的实现,你需要实现如下方法:

@Override

publicITransactionalSpout.Coordinator<TransactionMetadata>getCoordinator(

Map conf,TopologyContextcontext) {

returnnew TweetsTransactionalSpoutCoordinator();

}

@Override

publicbacktype.storm.transactional.ITransactionalSpout.Emitter<TransactionMetadata>

getEmitter(

Map conf,TopologyContextcontext) {

returnnew TweetsTransactionalSpoutEmitter();

}

@Override

publicvoiddeclareOutputFields(OutputFieldsDeclarer declarer) {

declarer.declare(newFields("txid","tweet_id","tweet"));

}

在getCoordinator方法中,你告诉Storm哪个类将协调批量元组的产生。在getEmitter中,你告诉Storm哪个类负责从源读取批量的元组并且发射它们到topology的一个流中。最后,如你之前所做的,你需要声明发射了哪些域。

The RQ class

为了使例子更简单些,我们决定封装所有的Redis相关的操作在一个类中。

public class RQ{

public static final String NEXT_READ="NEXT_READ";

public static final String NEXT_WRITE="NEXT_WRITE";

Jedis jedis;

public RQ() {

jedis =newJedis("localhost");

}

public longgetAvailableToRead(longcurrent) {

return getNextWrite() -current;

}

publiclonggetNextRead() {

String sNextRead =jedis.get(NEXT_READ);

if(sNextRead==null)

;

returnLong.valueOf(sNextRead);

}

publiclonggetNextWrite() {

returnLong.valueOf(jedis.get(NEXT_WRITE));

}

publicvoidclose() {

jedis.disconnect();

}

publicvoidsetNextRead(longnextRead) {

jedis.set(NEXT_READ,""+nextRead);

}

publicList<String>getMessages(longfrom,intquantity) {

String[]keys=newString[quantity];

;i<quantity;i++)

keys[i] =""+(i+from);

returnjedis.mget(keys);

}

}

仔细阅读每个方法的实现,确保你明白了他们在做什么。

协调器

我们看一下这个例子中协调器的实现。

public static class TweetsTransactionalSpoutCoordinatorimplements

ITransactionalSpout.Coordinator<TransactionMetadata> {

TransactionMetadata lastTransactionMetadata;

RQ rq =newRQ();

;

public TweetsTransactionalSpoutCoordinator() {

nextRead =rq.getNextRead();

}

@Override

public TransactionMetadatainitializeTransaction(BigInteger txid,

TransactionMetadata prevMetadata) {

long quantity=rq.getAvailableToRead(nextRead);

quantity =quantity>MAX_TRANSACTION_SIZE?MAX_TRANSACTION_SIZE:quantity;

TransactionMetadata ret =newTransactionMetadata(nextRead, (int)quantity);

nextRead +=quantity;

returnret;

}

@Override

publicbooleanisReady() {

;

}

@Override

publicvoidclose() {

rq.close();

}

}

很重要的需要提醒的一点是在整个topology中只有一个协调器实例。当协调器被初始化后,它从Redis检索一个时序,该时序告诉协调器下一条要读取的tweet是哪条。第一次时,该值为1,意味着接下来要读取的tweet是第一条。

第一个要被调用的方法是isReady。它在initializeTransaction之前总是会被调用来确保源已经准备好被读取数据。你要相应的返回true或者false。在这个例子中,检索tweets的数量并把他们和你读到的tweets数量做比较。它们之间的差异是可以读取的tweets数量。如果它大于0,说明你还有tweets可读。

最后,initializeTransaction被执行。正如你看到的,你用txid和prevMetadata作为参数。第一个参数是一个由Storm产生的唯一的事务ID,它代表产生的元组的批次。prevMetadata是前一个事务的协调器产生的元数据。

在这个例子中,首先确保有多少tweets可读取。一旦你整理好了,创建一个新的TransactionMetadata,指明哪个是第一个要读的tweet,要读取的量是多少。

你一返回元数据,Storm就把它和txid存到zookeeper中。这确保了一旦有错误,Storm有能力来使发射器重新发送元组。

发射器

创建事务spout的最后一步是实现发射器。

我们从下边的实现开始:

public static class TweetsTransactionalSpoutEmitterimplements

ITransactionalSpout.Emitter<TransactionMetadata> {

RQ rq =newRQ();

public TweetsTransactionalSpoutEmitter() {

}

@Override

publicvoidemitBatch(TransactionAttempt tx,

TransactionMetadata coordinatorMeta,BatchOutputCollector collector) {

rq.setNextRead(coordinatorMeta.from+coordinatorMeta.quantity);

List<String>messages=rq.getMessages(coordinatorMeta.from,

coordinatorMeta.quantity);

longtweetId=coordinatorMeta.from;

for(String message:messages) {

collector.emit(newValues(tx,""+tweetId,message));

tweetId++;

}

}

@Override

publicvoidcleanupBefore(BigInteger txid) {

}

@Override

publicvoidclose() {

rq.close();

}

}

发射器读取源并发射元组到一个流。对于相同的transaction id 和 transaction metadata,发射器总是可以发射相同批次的元组是非常重要的。这样,如果处理一个批次的过程中出错了,Storm可以通过发射器重放相同的transaction id 和 transaction metadata并且确保这个批次被重放了。Storm会增加TransactionAttempt中的attempt id。这样你就可以知道该批次被重放了。

这里emitBatch是一个重要的方法。在这个方法中,使用元数据来作为参数,从Redis中读取tweets。同时增加在Redis中的序列,该序列记录了你到目前为止已读取了多少tweets。当然,还要发射tweets到topology。

getting start with storm 翻译 第八章 part-1的更多相关文章

  1. getting start with storm 翻译 第八章 part-2

    转载请注明出处:http://blog.csdn.net/lonelytrooper/article/details/12435641 The Bolts 首先我们看一下该topology中的标准bo ...

  2. 大数据入门第十六天——流式计算之storm详解(一)入门与集群安装

    一.概述 今天起就正式进入了流式计算.这里先解释一下流式计算的概念 离线计算 离线计算:批量获取数据.批量传输数据.周期性批量计算数据.数据展示 代表技术:Sqoop批量导入数据.HDFS批量存储数据 ...

  3. The Django Book

    The Django Book Table of contents 2.0, English -> Chinese Django book 2.0 的中文翻译. 最近更新 - 贡献者 方便自己也 ...

  4. 深入理解Magento – 第四章 – 模型和ORM基础

    深入理解Magento 作者:Alan Storm 翻译:Hailong Zhang 第四章 – 模型和ORM基础 对于任何一个MVC架构,模型(Model)层的实现都是占据了很大一部分.对于Mage ...

  5. 深入理解Magento – 第三章 – 布局,块和模板

    深入理解Magento 作者:Alan Storm 翻译:Hailong Zhang 第三章 – 布局,块和模板 我们接着研究Magento.根据我们第二章讲的Magento MVC的架构,我们接下来 ...

  6. 深入理解Magento – 第二章 – Magento请求分发与控制器

    深入理解Magento 作者:Alan Storm 翻译:Hailong Zhang 第二章 – Magento请求分发与控制器 Model-View-Controller (MVC) ,模型-视图- ...

  7. 深入理解Magento - 第一章 - Magento强大的配置系统

    深入理解Magento 作者:Alan Storm翻译:zhlmmc 前言第一章 - Magento强大的配置系统第二章 - Magento请求分发与控制器第三章 - 布局,块和模板第四章 - 模型和 ...

  8. 《Entity Framework 6 Recipes》中文翻译系列 (42) ------ 第八章 POCO之使用POCO

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第八章 POCO 对象不应该知道如何保存它们,加载它们或者过滤它们.这是软件开发中熟 ...

  9. 《Entity Framework 6 Recipes》中文翻译系列 (46) ------ 第八章 POCO之领域对象测试和仓储测试

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-8  测试领域对象 问题 你想为领域对象创建单元测试. 这主要用于,测试特定的数 ...

随机推荐

  1. MySQL 5.6 my.cnf 参数详细说明

    # 以下选项会被MySQL客户端应用读取.# 注意只有MySQL附带的客户端应用程序保证可以读取这段内容.# 如果你想你自己的MySQL应用程序获取这些值.# 需要在MySQL客户端库初始化的时候指定 ...

  2. Ubuntu 12.04下解决Rhythmbox Music Player乱码问题

    1.打开终端输入如下信息: $ sudo gedit ~/.profile 2.在打开的文档末尾加上如下两句: export GST_ID3_TAG_ENCODING=GBK:UTF-8:GB1803 ...

  3. 判断奇数,java陷阱

    /** 判断是否为奇数 @author husky / public class IsOdd { public static void main(String[] args) { int demo1 ...

  4. 【BZOJ1050】【枚举+并查集】旅行comf

    Description 给你一个无向图,N(N<=500)个顶点, M(M<=5000)条边,每条边有一个权值Vi(Vi<30000).给你两个顶点S和T,求一条路径,使得路径上最大 ...

  5. JS获取浏览器可视区域的尺寸

    所谓可视区域是指能看得见的区域,即在浏览器中能看到页面的区域(高度与宽度).刚刚使用 document.body.clientHeight 来获取可视区域的高度得到的却是整个文档的高度,然后在cnbl ...

  6. extend简单用法

    eg:var obj1=[{a:1,b:2},{a:2,b:3}] var obj2=[{c:3,d:2},{c:4,d:3}] var resultArray=[]; for (var i = 0; ...

  7. Bootstrap_表单_图像

    在Bootstrap框架中对于图像的样式风格提供以下几种风格: 1.img-responsive:响应式图片,主要针对于响应式设计2.img-rounded:圆角图片3.img-circle:圆形图片 ...

  8. Linux之Vim编辑器使用

    vim文本编辑器用于建立 编辑 显示文本文件,vim没有菜单,只有命令 在windows 平台下可使用gvim进行编写 Vim三种工作模式: 常有命令: 1.INSERT插入命令 i 在光标前插入 I ...

  9. QQ空间API接口

    (以下内容可能会随着时间改变而改变!) 查看对方QQ空间的背景音乐 http://qzone-music.qq.com/fcg-bin/cgi_playlist_xml.fcg?json=0& ...

  10. flask mysql

    sudo apt-get install mysql-servermysql -u root -p sudo apt-get install python-mysqldb sudo apt-get i ...