背景

前期收到的问题:

1、在Topology中我们可以指定spout、bolt的并行度,在提交Topology时Storm如何将spout、bolt自动发布到每个服务器并且控制服务的CPU、磁盘等资源的?


2、Storm处理消息时会根据Topology生成一棵消息树,Storm如何跟踪每个消息、如何保证消息不丢失以及如何实现重发消息机制?

本篇看看storm是通过什么机制来保证消息至少处理一次的语义的,并回答第2个问题。

storm中的一些原语##



要说明上面的问题,得先了解storm中的一些原语,比如:

  1. tuple和message

    在storm中,消息是通过tuple来抽象表示的,每个tuple知道它从哪里来,应往哪里去,包含了其在tuple-tree(如果是anchored的话)或者DAG中的位置,等等信息。

  2. spout

    spout充当了tuple的发送源,spout通过和其它消息源,比如kafka交互,将消息封装为tuple,发送到流的下游。

  3. bolt

    bolt是tuple的实际处理单元,通过从spout或者另一个bolt接收tuple,进行业务处理,将自己加入tuple-tree(通过在emit方法中设置anchors)或DAG,然后继续将tuple发送到流的下游。

  4. acker

    acker是一种特殊的bolt,其接收来自spout和bolt的消息,主要功能是追踪tuple的处理情况,如果处理完成,会向tuple的源头spout发送确认消息,否则,会发送失败消息,spout收到失败的消息,根据配置和自定义的情况会进行消息的丢弃、重放处理。

spout、bolt、acker的关系

  1. spout将tuple发送给流的下游的bolts.
  2. bolt收到tuple,处理后发送给下游的bolts.
  3. spout向acker发送请求ack的消息.
  4. bolt向acker发送请求ack的消息.
  5. acker向bolt和spout发送确认ack的消息.

简单的关系如下所示:

上图展示了spout、bolts等形成了一个DAG,如何追踪这个DAG的执行过程,就是storm保证仅处理一次消息的语义的机制所在。

storm如何追踪消息(tuple)的处理##

spout在调用emit/emitDirect方法发送tuple时,会以单播或者广播的方式,将消息发送给流的下游的component/task/bolt,如果配置了acker,那么会在每次emit调用之后,向acker发送请求ack的消息:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; spout向acker发送请求ack消息
;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; rooted?表示是否设置了acker
(if (and rooted?
(not (.isEmpty out-ids)))
(do
(.put pending root-id [task-id
message-id
{:stream out-stream-id :values values}
(if (sampler) (System/currentTimeMillis))])
(task/send-unanchored task-data
;;表示这是一个流初始化的消息
ACKER-INIT-STREAM-ID
;;将下游组件的out-id和0组成一个异或链,发送给acker用于追踪
[root-id (bit-xor-vals out-ids) task-id]
overflow-buffer)) ;; 如果没有配置acker,则调用自身的ack方法
(when message-id
(ack-spout-msg executor-data task-data message-id
{:stream out-stream-id :values values}
(if (sampler) 0) "0:")))

从上面的代码可以看出,每次emit tuple后,spout会向acker发送一个流ID为ACKER-INIT-STREAM-ID的消息,用于将DAG或者tuple-tree中的节点信息交给acker,acker会利用这个信息来追踪tuple-tree或DAG的完成。

而spout调用emit/emitDirect方法,将tuple发到下游的bolts,也同时会发送用于追踪DAG完成情况的信息:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; spout向流的下游emit消息
;;;;;;;;;;;;;;;;;;;;;;;;;;;; (let [tuple-id (if rooted?
;; 如果有acker,tuple的MessageId会包含一个<root-id,id>的哈希表
;; root-id和id都是long型64位整数
(MessageId/makeRootId root-id id)
(MessageId/makeUnanchored))
;;实例化tuple
out-tuple (TupleImpl. worker-context
values
task-id
out-stream-id
tuple-id)] ;; 发送至队列,最终发送给流的下游的task/bolt
(transfer-fn out-task
out-tuple
overflow-buffer)
))

这个追踪信息是什么呢?

如果是spout -> bolt或者bolt -> bolt,这个信息就是tuple的MessageId,其内部维护一个哈希表:

// map anchor to id
private Map<Long, Long> _anchorsToIds;

键为root-id,表示spout,值表示tuple在tuple-tree或者DAG的根(spout)或者经过的边(bolt),但这里没有利用任何常规意义上的“树”的算法,而是采用异或的方式来存储这个值:

  1. spout -> bolt,值被初始化为一个long型64位整数.
  2. bolt -> bolt,值被初始化为一个long型64位整数,并和_anchorsToIds中的旧值进行按位异或,将结果更新到_anchorsToIds中.

如果是spout -> acker,或者bolt -> acker,那么用于追踪的是tuple的values:

  1. spout -> acker : [root-id (bit-xor-vals out-ids) task-id]
  2. bolt -> acker : [root (bit-xor id ack-val) ..]

下面给出上面调用的bit-xor-vals和bit-xor方法的代码:

(defn bit-xor-vals
[vals]
(reduce bit-xor 0 vals)) (defn bit-xor
"Bitwise exclusive or"
{:inline (nary-inline 'xor)
:inline-arities >1?
:added "1.0"}
([x y] (. clojure.lang.Numbers xor x y))
([x y & more]
(reduce1 bit-xor (bit-xor x y) more)))

示例##

说起来有点抽象,看个例子。

假设我们有1个spout,n个bolt,1个acker:

1.spout###

spout发送tuple到下游的bolts:

;; id_1是发送到bolt_1的tuple-id,依此类推
spout :
->bolt_1 : id_1
->bolt_2 : id_2
..
->bolt_n : id_n

2.bolt###

bolt收到tuple,在execute方法中进行必要的处理,然后调用emit方法,最后调用ack方法:

;; bolt_1调用emit方法,追踪消息的这样一个值:让id_1和bid_1按位进行异或.
;; bid_1和id_1类似,是个long型的64位随机整数,在emit这一步生成
bolt_1 emit : id_1 ^ bid_1 ;; bolt_1调用ack方法,并将值表达为如下方式的异或链的结果
bolt_1 ack : 0 ^ bid_1 ^ id_1 ^ bid_1 = 0 ^ id_1

以上,可以看出bolt进行了emit-ack组合后,其自身在异或链中的作用消失了,也就是说tuple在此bolt得到了处理。

(当然,此时的ack还没有得到acker的确认,假设acker确认了,那么上面所说的tuple在bolt得到了处理就成立了。)

来看看acker的确认。

3.acker###

acker收到来自spout的tuple:

;; spout发消息给acker,tuple的MessageId包含下面的异或链的结果
spout -> acker : 0 ^ id_1 ^ id_2 ^ .. ^ id_n ;; acker收到来spout的消息,对tuple的ackVal进行处理,如下所示:
acker : 0 ^ (0 ^ id_1 ^ id_2 ^ .. ^ id_n) = 0 ^ id_1 ^ id_2 ^ .. ^ id_n

acker收到来自bolt的tuple:

;; bolt_1发消息给acker:
bolt_1 -> acker : 0 ^ id_1 ;; acker维护的对应此tuple的源spout的ackVal :
ackVal : 0 ^ id_1 ^ id_2 ^ .. ^ id_n ;; acker进行确认,也就是拿上面的两个值进行异或:
acker : (0 ^ id_1) ^ (0 ^ id_1 ^ id_2 ^ .. ^ id_n) = 0 ^ id_2 ^ .. ^ id_n

可以看出,bolt_1向acker请求ack,acker收到请求ack,异或之后,id_1的作用消失。也就是说,bolt_1已处理完毕这个tuple。

所以,在acker看来,如果某个bolt的处理完成,则此bolt在异或链中的作用就消失了。

如果所有的bolt 都得到处理,那么acker将会观察到ackVal值变成了0:

ackVal = 0
= (0 ^ id_1) ^ (0 ^ id_1 ^ .. ^ id_n) ^ .. ^ (0 ^ id_n)
= (0 ^ 0) ^ (id_1 ^ id_1) ^ (id_2 ^ id_2) ^ .. ^ (id_n ^ id_n)

如果出现了ackVal = 0,说明两个可能:

  1. spout发送的tuple都处理完成,tuple-tree或者DAG已完成。
  2. 概率性出错,也就是说在极小的概率下,即使不按上面的确认流程来走,异或链的结果也可能出现0.但这个概率极小,小到什么程度呢?

    用官方的话说就是,如果每秒发送1万个ack消息,50,000,000年时才可能发生这种情况。

如果ackVal不为0,说明tuple-tree或DAG没有完成。如果长时间不为0,通过超时,可以触发一个超时回调,在这个回调中调用spout的fail方法,来进行重放。

如此,就保证了消息处理不会漏掉,但可能会重复。

结语##

以上,就是storm保证消息至少处理一次的语义的机制 。

storm如何保证at least once语义?的更多相关文章

  1. Storm如何保证可靠的消息处理

    作者:Jack47 PS:如果喜欢我写的文章,欢迎关注我的微信公众账号程序员杰克,两边的文章会同步,也可以添加我的RSS订阅源. 本文主要翻译自Storm官方文档Guaranteeing messag ...

  2. 【转】Twitter Storm如何保证消息不丢失

    Twitter Storm如何保证消息不丢失 发表于 2011 年 09 月 30 日 由 xumingming 作者: xumingming | 可以转载, 但必须以超链接形式标明文章原始出处和作者 ...

  3. Strom 消息处理机制 中英对照翻译 (Storm如何保证消息被完全处理)

    官方链接: http://storm.incubator.apache.org/documentation/Guaranteeing-message-processing.html What does ...

  4. storm是怎样保证at least once语义的

    背景 本篇看看storm是通过什么机制来保证消息至少处理一次的语义的. storm中的一些原语 要说明上面的问题,得先了解storm中的一些原语,比方: tuple和message 在storm中,消 ...

  5. storm是如何保证at least once语义的?

    storm中的一些原语: 要说明上面的问题,得先了解storm中的一些原语,比如: tuple和messagetuple:在storm中,消息是通过tuple来抽象表示的,每个tuple知道它从哪里来 ...

  6. Twitter Storm如何保证消息不丢失

    storm保证从spout发出的每个tuple都会被完全处理.这篇文章介绍storm是怎么做到这个保证的,以及我们使用者怎么做才能充分利用storm的可靠性特点. 一个tuple被”完全处理”是什么意 ...

  7. Storm入门(五)Twitter Storm如何保证消息不丢失

    转自:http://xumingming.sinaapp.com/127/twitter-storm如何保证消息不丢失/ storm保证从spout发出的每个tuple都会被完全处理.这篇文章介绍st ...

  8. Storm如何保证消息不丢失

    storm保证从spout发出的每个tuple都会被完全处理.这篇文章介绍storm是怎么做到这个保证的,以及我们使用者怎么做才能充分利用storm的可靠性特点. 一个tuple被"完全处理 ...

  9. storm基础框架分析

    背景 前期收到的问题: 1.在Topology中我们可以指定spout.bolt的并行度,在提交Topology时Storm如何将spout.bolt自动发布到每个服务器并且控制服务的CPU.磁盘等资 ...

随机推荐

  1. 超链接的那些事(三): 属性target

    a标签的属性之一 target 1. 定义     规定在何处打开链接文档. 如果a标签中有target属性,浏览器将会载入和显示用这个标签的 href 属性命名的.名称与这个目标吻合的框架或者窗口中 ...

  2. CentOS下一键安装Openstack

    CentOS下一键安装Openstack 系统环境:Oracle VirtualBox 4.38CentOS-6.5-x86_64-bin-DVD1.iso 安装前需要修改 /etc/hosts文件, ...

  3. 面试之jsp、Servlet相关知识——生命周期, 区别等

    1.servlet生命周期 所谓生命周期,指的是servlet容器如何创建servlet实例.分配其资源.调用其方法.并销毁其实例的整个过程. 阶段一: 实例化(就是创建servlet对象,调用构造器 ...

  4. CI 框架中 AR 操作

    Model 层中的部分代码 /** * CI 中的 AR 操作 * @author zhaoyingnan **/ public function mAR() { /*************** 查 ...

  5. 制作nginx和php的rpm包

    rpm包的制作真几把烦,制作php的rpm花了我3天时间,因为是根据线上环境来做的,依赖的第三方库太多,本来想把所有的第三方库做进php包,后来发现在rpmbuild -bb的时候非常耗时,而且乱七八 ...

  6. 按照索引的细化提取骨架算法的java实现

    近期研究验证码识别,也就看了一些图像识别的资料,其中一种字体细化提取骨架的算法网上没有java版的实现,所以就选取了一个python实现版本进行java代码的改写.. python版实现的地址: ht ...

  7. sqlite-jdbc jar包下载过程笔记

    在网络上找一些开源的jar包和对应的源码时,官网上往往是最为安全,版本最新.但大部分的网站都为英文网站,有时候定位下载地址稍有困难,下面是网上找sqlite-jdbc jar包过程,记录一下,以供参考 ...

  8. 单元测试---googletest

    单元测试概述 测试并不只是测试工程师的责任,对于开发工程师,为了保证发布给测试环节的代码具有足够好的质量( Quality ),为所编写的功能代码编写适量的单元测试是十分必要的. 单元测试( Unit ...

  9. 2014 Super Training #9 C E - Cup 2 --记忆化搜索

    原题:ZOJ 3681 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3681 题意:给一个m,n,m表示m个人,可以把m个 ...

  10. 链表面试题Java实现【重要】

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...