Storm可靠性实例解析——ack机制
对于Storm,它有一个很重要的特性:“Guarantee no data loss” ——可靠性
很显然,要做到这个特性,必须要track每个data的去向和结果。Storm是如何做到的呢——acker机制。
先概括下acker所参与的工作流程:
- Spout创建一个新的Tuple时,会发一个消息通知acker去跟踪;
- Bolt在处理Tuple成功或失败后,也会发一个消息通知acker;
- acker会找到发射该Tuple的Spout,回调其ack或fail方法。
我们说RichBolt和BasicBolt的区别是后者会自动ack。那么是不是我们只要实现了Spout的ack或fail方法就能看到反馈了呢?
试试在RandomSpout(extends BaseRichSpout )中加入如下代码:
- public class RandomSpout extends BaseRichSpout {
- private SpoutOutputCollector collector;
- private Random rand;
- private static String[] sentences = new String[] {"edi:I'm happy", "marry:I'm angry", "john:I'm sad", "ted:I'm excited", "laden:I'm dangerous"};
- @Override
- public void open(Map conf, TopologyContext context,
- SpoutOutputCollector collector) {
- this.collector = collector;
- this.rand = new Random();
- }
- @Override
- public void nextTuple() {
- String toSay = sentences[rand.nextInt(sentences.length)];
- this.collector.emit(new Values(toSay));
- }
- @Override
- public void declareOutputFields(OutputFieldsDeclarer declarer) {
- declarer.declare(new Fields("sentence"));
- }
- }
public class RandomSpout extends BaseRichSpout
- @Override
- public void ack(Object msgId) {
- System.err.println("ack " + msgId);
- }
- @Override
- public void fail(Object msgId) {
- System.err.println("fail " + msgId);
- }
疑问:重新运行ExclaimBasicTopo,看下结果。并没有任何的ack 和 fail 出现?
分析:原因是,Storm要求如果要track一个Tuple,必须要指定其messageId,也就是回调回ack和fail方法的参数。如果我们不指定,Storm是不会去track该tuple的,即不保证消息丢失!
探讨:我们改下Spout代码,为每个消息加入一个唯一Id。同时,为了方便看结果,加入更多的打印,并且靠sleep减慢发送速度。(只是为了演示!)
- public class RandomSpout extends BaseRichSpout {
- private SpoutOutputCollector collector;
- private Random rand;
- private AtomicInteger counter;
- private static String[] sentences = new String[] {"edi:I'm happy", "marry:I'm angry", "john:I'm sad", "ted:I'm excited", "laden:I'm dangerous"};
- @Override
- public void open(Map conf, TopologyContext context,
- SpoutOutputCollector collector) {
- this.collector = collector;
- this.rand = new Random();
- counter = new AtomicInteger();
- }
- @Override
- public void nextTuple() {
- Utils.sleep(5000);
- String toSay = sentences[rand.nextInt(sentences.length)];
- int msgId = this.counter.getAndIncrement();
- toSay = "["+ msgId + "]"+ toSay;
- PrintHelper.print("Send " + toSay );
- this.collector.emit(new Values(toSay), msgId);
- }
- @Override
- public void declareOutputFields(OutputFieldsDeclarer declarer) {
- declarer.declare(new Fields("sentence"));
- }
- @Override
- public void ack(Object msgId) {
- PrintHelper.print("ack " + msgId);
- }
- @Override
- public void fail(Object msgId) {
- PrintHelper.print("fail " + msgId);
- }
- }
PrintHelper类:
- public class PrintHelper {
- private static SimpleDateFormat sf = new SimpleDateFormat("mm:ss:SSS");
- public static void print(String out){
- System.err.println(sf.format(new Date()) + " [" + Thread.currentThread().getName() + "] " + out);
- }
- }
同时把PrintBolt里面打印也换成PrintHelper.print打印
看下打印结果:
- 53:33:891 [Thread-26-spout] Send [0]ted:I'm excited
- 53:33:896 [Thread-20-print] Bolt[0] String recieved: [0]ted:I'm excited!
- 53:38:895 [Thread-26-spout] Send [1]edi:I'm happy
- 53:38:895 [Thread-22-print] Bolt[1] String recieved: [1]edi:I'm happy!
- 53:38:895 [Thread-26-spout] ack 0
- 53:43:896 [Thread-26-spout] Send [2]edi:I'm happy
- 53:43:896 [Thread-22-print] Bolt[1] String recieved: [2]edi:I'm happy!
- 53:43:896 [Thread-26-spout] ack 1
- 53:48:896 [Thread-26-spout] Send [3]edi:I'm happy
- 53:48:896 [Thread-26-spout] ack 2
- 53:48:896 [Thread-24-print] Bolt[2] String recieved: [3]edi:I'm happy!
- 53:53:896 [Thread-26-spout] Send [4]ted:I'm excited
- 53:53:896 [Thread-26-spout] ack 3
- 53:53:896 [Thread-20-print] Bolt[0] String recieved: [4]ted:I'm excited!
- 53:58:897 [Thread-26-spout] Send [5]laden:I'm dangerous
- 53:58:897 [Thread-26-spout] ack 4
- 53:58:898 [Thread-24-print] Bolt[2] String recieved: [5]laden:I'm dangerous!
很明显看到:
- 并发度为1的Spout确实是一个线程,并发度为3的Bolt确实是三个线程;
- 消息完全处理完成后,确实回调了ack(Object msgId)方法,而且msgId的值,即为我们emit的msgId;
- 虽然我们在topology中定义了两个bolt,但实际上ack对于每个tuple只调用了一次;
- spout发出tuple后,Bolt很快就完成了,但是ack直到5秒后spout醒来才打印。
Tuple树
对于Spout创建的Tuple,在topology定义的流水线中经过Bolt处理时,可能会产生一个或多个新的Tuple。源Tuple+新产生的Tuple构成了一个Tuple树。当整棵树被处理完成,才算一个Tuple被完全处理,其中任何一个节点的Tuple处理失败或超时,则整棵树失败。
超时的值,可以通过定义topology时,conf.setMessageTimeoutSecs方法指定。
Anchor
在我们例子中ExclaimRichBolt用
- public class ExclaimBasicBolt extends BaseBasicBolt {
- @Override
- public void execute(Tuple tuple, BasicOutputCollector collector) {
- //String sentence = tuple.getString(0);
- String sentence = (String) tuple.getValue(0);
- String out = sentence + "!";
- collector.emit(new Values(out));
- }
- @Override
- public void declareOutputFields(OutputFieldsDeclarer declarer) {
- declarer.declare(new Fields("excl_sentence"));
- }
- }
ExclaimBasicBolt 原实现方式
collector.emit(inputTule, new Values(newTupleValue));
发射一个新的tuple。
第一个参数是传入Bolt的tuple,第二个参数是新产生的tuple的value,这种emit的方式,在Storm中称为: "anchor"。
Tuple的ack
前面我们一直提到acker,看到这里,你应该能猜出acker其实就是Storm里面track一个Tuple保证其一定被处理的功能。acker也是一个component。
我们来看看acker的工作流程:
1. Spout在初始化时会产生一个tasksId;
2. Spout中创建新的Tuple,其id是一个64位的随机数;
3. Spout将新建的Tuple发送出去(给出了messageId来开启Tuple的追踪), 同时会发送一个消息到某个acker,要求acker进行追踪。该消息包含两部分:
- Spout的taskId:用户acker在整个tuple树被完全处理后找到原始的Spout进行回调ack或fail
- 一个64位的ack val值: 标志该tuple是否被完全处理。初始值为0。
4. 一个Bolt在处理完Tuple后,如果发射了一个新的anchor tuple,Storm会维护anchor tuple的列表;
5. 该Bolt调用OutputCollector.ack()时,Storm会做如下操作:
- 将anchor tuple列表中每个已经ack过的和新创建的Tuple的id做异或(XOR)。假定Spout发出的TupleID是tuple-id-0,该Bolt新生成的TupleID为tuple-id-1,那么,tuple-id-0XORtuple-id-0XOR tuple-id-1
- Storm根据该原始TupleID进行一致性hash算法,找到最开始Spout发送的那个acker,然后把上面异或后得出的ack val值发送给acker
6. acker收到新的ack val值后,与保存的原始的Tuple的id进行异或,如果为0,表示该Tuple已被完全处理,则根据其taskId找到原始的Spout,回调其ack()方法。
fail的机制类似,在发现fail后直接回调Spout的fail方法。
——Storm就是通过这个acker的机制来保证数据不丢失。
回头再看看上面的打印结果,b、c两条得到很好的解释了。那d是为什么呢?
在最开始时,我曾经提到过,Storm的设计模型中,Spout是源源不断的产生数据的,所以其nextTuple()方法在任何时候不应该被打断。ack,fail 和 nextTuple是在同一个线程中完成的。
所以,虽然acker发现一个Tuple已经完全处理完成,但是由于Spout线程在Sleep,无法回调。
在设计中,我们应尽量避免在Spout、Bolt中去Sleep。如果确实需要控制,最好用异步线程来做,例如用异步线程读取数据到队列,再由Spout去取队列中数据。异步线程可以随意控制速度等。
另外,
Storm是否会自动重发失败的Tuple?
这里答案已经很明显了。fail方法如何实现取决于你自己。只有在fail中做了重发机制,才有重发。
注:Trident除外。这是Storm提供的特殊的事务性API,它确实会帮你自动重发的。
Unanchor
如果我们在Bolt中用OutputCollector.emit()发射一个新的Tuple时,并没有指定输入的Tuple(IBasicBolt的实现类用的是BasicOutPutCollector,其emit方法实际上还是调用OutputCollector.emit(),只不过内部会帮你填上输入的Tuple),那么行为称之为“Unanchor”。
是否用Unanchor方式取决于你的实现。
调整可靠性
- 在build topology时,设置acker数目为0,即conf.setNumAckers(0);
- 在Spout中,不指定messageId,使得Storm无法追踪;
- 在Bolt中,使用Unanchor方式发射新的Tuple。
本文转自:Edison徐storm应用系列之——可靠性与ack机制
Storm可靠性实例解析——ack机制的更多相关文章
- Storm的BaseBasicBolt源码解析ack机制
我们在学习ack机制的时候,我们知道Storm的Bolt有BaseBasicBolt和BaseRichBolt.在BaseBasicBolt中,BasicOutputCollector在emit数据的 ...
- Storm的ack机制在项目应用中的坑
正在学习storm的大兄弟们,我又来传道授业解惑了,是不是觉得自己会用ack了.好吧,那就让我开始啪啪打你们脸吧. 先说一下ACK机制: 为了保证数据能正确的被处理, 对于spout产生的每一个tup ...
- Docker网络管理机制实例解析+创建自己Docker网络
实例解析Docker网络管理机制(bridge network,overlay network),介绍Docker默认的网络方式,并创建自己的网络桥接方式,将开发的容器添加至自己新建的网络,提高Doc ...
- 第3节 storm高级应用:4、5、ack机制,以及其验证超时
4. 消息不丢失机制 4.1.ack是什么 ack 机制是storm整个技术体系中非常闪亮的一个创新点. 通过Ack机制,spout发送出去的每一条消息,都可以确定是被成功处理或失败处理, 从而可以 ...
- kafkaspot在ack机制下如何保证内存不溢
新浪微博:intsmaze刘洋洋哥. storm框架中的kafkaspout类实现的是BaseRichSpout,它里面已经重写了fail和ack方法,所以我们的bolt必须实现ack机制,就可以 ...
- RabbitMq + Spring 实现ACK机制
概念性解读(Ack的灵活) 首先啊,有的人不是太理解这个Ack是什么,讲的接地气一点,其实就是一个通知,怎么说呢,当我监听消费者,正常情况下,不会出异常,但是如果是出现了异常,甚至是没有获取的异常,那 ...
- Android开发之IPC进程间通信-AIDL介绍及实例解析
一.IPC进程间通信 IPC是进程间通信方法的统称,Linux IPC包括以下方法,Android的进程间通信主要采用是哪些方法呢? 1. 管道(Pipe)及有名管道(named pipe):管道可用 ...
- ActiveMQ讯息传送机制以及ACK机制
http://blog.csdn.net/lulongzhou_llz/article/details/42270113 ActiveMQ消息传送机制以及ACK机制详解 AcitveMQ是作为一种消息 ...
- Storm学习笔记 - 消息容错机制
Storm学习笔记 - 消息容错机制 文章来自「随笔」 http://jsynk.cn/blog/articles/153.html 1. Storm消息容错机制概念 一个提供了可靠的处理机制的spo ...
随机推荐
- poj1703(各种姿势)
题目链接:http://poj.org/problem?id=1703 题意:有n个人分别属于两个团伙,接下来m组形如 ch, x, y的数据,ch为"D"表示 x, y属于不同的 ...
- 二、JavaScript语言--JS基础--JavaScript进阶篇--JavaScript内置对象
1.什么事对象 JavaScript 中的所有事物都是对象,如:字符串.数值.数组.函数等,每个对象带有属性和方法. 对象的属性:反映该对象某些特定的性质的,如:字符串的长度.图像的长宽等: 对象的方 ...
- 阿里云 SWAP
https://yq.aliyun.com/articles/52098 https://www.kejianet.cn/aliyun-swap/
- 闲谈SQL脚本优化
摘要: 闲来无事,便想寻找大师级别优化SQL脚本案例,也算是读后留点笔记,摘录内容都会链接到所参考网址,如有冒犯,还望博主见谅:有些文章只有多动手多动脑才能理解其中的意思,看了需要实际操作,才不枉大师 ...
- 关于python性能提升的一些方案(上)
一.函数调用优化(空间跨度,避免访问内存) 1.大数据求和,使用sum a = range(100000) %timeit -n 10 sum(a) 10 loops, best of 3: 3.15 ...
- git 本地仓库和远程仓库及本地分支和远程分支
从远程git仓库签出代码: $ git clone git://aaa.com/git_project.git (远程git服务器项目所在地址) 当你需要克隆远程项目到本地时,默认会把项目保存在名 ...
- 杂物 python (一)
python 是一门语言,有各种不同的实现.CPython 即用c语言实现Python及其解释器.
- Windows phone 8.0 本地化遇到的两个问题
基本上来说,按照msdn来讲的,本地化和全球化没有太多的问题,链接如下: http://msdn.microsoft.com/zh-cn/library/windowsphone/develop/ff ...
- Java Socket编程(转)
Java Socket编程 对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket.服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了.首 ...
- CRC校验(转)
CRC即循环冗余校验码(Cyclic Redundancy Check[1] ):是数据通信领域中最常用的一种差错校验码,其特征是信息字段和校验字段的长度可以任意选定.循环冗余检查(CRC)是一种数据 ...