欢迎转载,转载请注明出处,徽沪一郎。

本文从外部消息在worker进程内部的转化,传递及处理过程入手,一步步分析在worker-data中的数据项存在的原因和意义。试图从代码实现的角度来回答,如果是从头开始实现worker的话,该如何来定义消息接口,如何实现各自接口上的消息处理。

Topology到Worker的映射关系

Topology由Spout,Bolt组成,其逻辑关系大体如下图所示。

无论是Spout或Bolt的处理逻辑都需要在进程或线程内执行,那么它们与进程及线程间的映射关系又是如何呢。有关这个问题,Understanding the Parallelism of a Storm Topology 一文作了很好的总结,现重复一下其要点。

  1. worker是进程,executor对应于线程,spout或bolt是一个个的task
  2. 同一个worker只会执行同一个topology相关的task
  3. 在同一个executor中可以执行多个同类型的task, 即在同一个executor中,要么全部是bolt类的task,要么全部是spout类的task
  4. 运行的时候,spout和bolt需要被包装成一个又一个task

worker,executor, task三者之间的关系可以用下图表示

小结一下,Worker=Process, Executor=Thread, Task=Spout or Bolt.

每一个executor使用的是actor pattern,high level的处理逻辑如下图所示

外部消息的接收和处理

在源码走读之四一文中总结了worker进程内的各种类型的thread,也即executor,这个等同于定义了进程内部和进程间的接口类型。那么这些接口上的消息在具体流传和处理过程中需要定义哪些数据结构,针对这些数据结构,又要做哪些必要的处理呢?

换句话说,就是为什么在worker.clj中有哪些数据和函数存在,不这样做,可以不?

先图示一下,外部消息处理的大概流程。

注:圈起来的数字表示消息转换和处理的序列。

步骤一

监听端口准备就绪,接收线程在收到外部的消息后,面临的问题就是如何确定由哪个task来处理该消息。接收到的tuple中含有task-id,根据task-id可以知道运行该task的executor,executor中有receive-message-queue即(incoming queue)来存放外部的tuple. 定义的数据结构需要反映这个转换过程task-id->executor->receive-queue-map.

那么在worker-data中哪些数据项与这个过程相关呢

  1. :port
  2. :executor-receive-queue-map
  3. :short-executor-receive-queue-map
  4. :task->short-executor
  5. :transfer-local-fn

transfer-local-fn将数据从接收线程发送到spout或bolt所在的executor线程。

步骤二

接下来数据会被传递到executor,于是又牵涉到executor的数据结构问题。executor-data由函数mk-executor-data创建,其内容与worker-data比较起来相对较少。

executor收到tuple之后,第一步需要进行反序列化,storm中使用kyro来进行序列化和反序列化,这也是为什么在executor中有该数据项的原因。

executor中与步骤2相关的数据项

  1. :type executor-type
  2. :receive-queue
  3. :deserializer (executor-data中的数据项)

步骤三:

步骤2处理结束,会产生相应的tuple发送到外部。这个过程需要多解释一下,首先tuple不是直接发送给worker的transfer-thread(负责向其它进程发送消息),而是发送给send-handler线程,每一个executor在创建的时候最起码会有两个线程被创建,一个用于运行bolt或spout的处理逻辑,另一个用以负责缓存bolt或spout产生的对外发送的tuple。

一旦snd-hander中的tuple数量达到阀值,这些被缓存的tuple会一次性发送给worker级别的transfer-thread.

executor中与步骤3相关的数据项

  1. :transfer-fn (mk-executor-transfer-fn batch-transfer->worker)
  2. :batch-transfer-queue

在步骤3中生成outgoing的tuple,tuple生成的时候需要回答两个基本问题

  1. tuple中含有哪些字段 --   该问题的解答由spout或bolt中的declareOutFields来解决
  2. 由哪个node+port来接收该tuple -- 由grouping来解决,这个时候就可以看出为什么需要task这一层的逻辑抽象了,有关grouping的详细解释,请参考fxjwind撰写的Storm-源码分析-Streaming Grouping (backtype.storm.daemon.executor)

步骤四:

处理逻辑很简单,先将数据缓存,然后在达到阀值之后,一起传送给transfer-thread.

start-batch-transfer->worker-handler

(defn start-batch-transfer->worker-handler! [worker executor-data]
(let [worker-transfer-fn (:transfer-fn worker)
cached-emit (MutableObject. (ArrayList.))
storm-conf (:storm-conf executor-data)
serializer (KryoTupleSerializer. storm-conf (:worker-context executor-data))
]
(disruptor/consume-loop*
(:batch-transfer-queue executor-data)
(disruptor/handler [o seq-id batch-end?]
(let [^ArrayList alist (.getObject cached-emit)]
(.add alist o)
(when batch-end?
(worker-transfer-fn serializer alist)
(.setObject cached-emit (ArrayList.))
)))
:kill-fn (:report-error-and-die executor-data))))

worker-transfer-fn是worker中的transfer-fn,由mk-transfer-fn生成。

(defn mk-transfer-fn [worker]
(let [local-tasks (-> worker :task-ids set)
local-transfer (:transfer-local-fn worker)
^DisruptorQueue transfer-queue (:transfer-queue worker)]
(fn [^KryoTupleSerializer serializer tuple-batch]
(let [local (ArrayList.)
remote (ArrayList.)]
(fast-list-iter [[task tuple :as pair] tuple-batch]
(if (local-tasks task)
(.add local pair)
(.add remote pair)
))
(local-transfer local)
;; not using map because the lazy seq shows up in perf profiles
(let [serialized-pairs (fast-list-for [[task ^TupleImpl tuple] remote] [task (.serialize serializer tuple)])]
(disruptor/publish transfer-queue serialized-pairs)
)))))

步骤五:

处理函数mk-transfer-tuples-handler,主要进行序列化,将序列化后的数据发送给目的地址。

(defn mk-transfer-tuples-handler [worker]
(let [^DisruptorQueue transfer-queue (:transfer-queue worker)
drainer (ArrayList.)
node+port->socket (:cached-node+port->socket worker)
task->node+port (:cached-task->node+port worker)
endpoint-socket-lock (:endpoint-socket-lock worker)
]
(disruptor/clojure-handler
(fn [packets _ batch-end?]
(.addAll drainer packets)
(when batch-end?
(read-locked endpoint-socket-lock
(let [node+port->socket @node+port->socket
task->node+port @task->node+port]
;; consider doing some automatic batching here (would need to not be serialized at this point to remove per-tuple overhead)
;; try using multipart messages ... first sort the tuples by the target node (without changing the local ordering) (fast-list-iter [[task ser-tuple] drainer]
;; TODO: consider write a batch of tuples here to every target worker
;; group by node+port, do multipart send
(let [node-port (get task->node+port task)]
(when node-port
(.send ^IConnection (get node+port->socket node-port) task ser-tuple))
))))
(.clear drainer))))))

tuple发送的时候需要用到connection,但目前只知道task-id,所以在worker中需要保存task-id到node+port的映射,node+port与outgoing connections之间的映射。

worker中与步骤5相关的数据项:

  1. :cached-node+port->socket
  2. :cached-task->node+port
  3. :component->stream->fields
  4. :component->sorted-tasks
  5. :endpoint-socket-lock
  6. :transfer-queue (线程内部的消息队列)
  7. :task->component

其它的数据项

上述五个步骤并没有涵盖worker-data所有的数据项,那么其它的数据项归一归类,大体如下

timer相关,timer相关的数据项包括timer及其对应的处理句柄

  1. :heartbeat-timer
  2. :refresh-connection-timer
  3. :refresh-active-timer
  4. :executor-heartbeat-timer
  5. :user-timer

zk相关

  1. :storm-cluster-state
  2. :storm-active-atom
  3. :cluster-state

配置相关

  1. :conf
  2. :mq-context 在transport layer是使用zmq还是netty

Assignment相关

  1. :storm-id
  2. :assigment-id
  3. :worker-id
  4. :executors
  5. :task-ids
  6. :storm-conf
  7. :topology
  8. :system-topology

进程关闭相关

  1. :suicide-fn

其它的其它

  1. :uptime 运行时间,统计用
  2. :default-shared-resources 线程池
  3. :user-shared-resources 未启用

小结

设计的时候,一定是先画出一个大概的蓝图,然后逐步的细化并加以实现。具体来说,步骤如下

  1. manifest 定义主要的功能
  2. draw skeleton 画出实现草图,定义主要的接口
  3. discussion 与团队讨论
  4. data structures 数据结构
  5. function 函数实现
  6. testing 测试

twitter storm 源码走读之5 -- worker进程内部消息传递处理和数据结构分析的更多相关文章

  1. twitter storm源码走读之4 -- worker进程中线程的分类及用途

    欢迎转载,转载请注明出版,徽沪一郎. 本文重点分析storm的worker进程在正常启动之后有哪些类型的线程,针对每种类型的线程,剖析其用途及消息的接收与发送流程. 概述 worker进程启动过程中最 ...

  2. twitter storm源码走读之2 -- tuple消息发送场景分析

    欢迎转载,转载请注明出处源自徽沪一郎.本文尝试分析tuple发送时的具体细节,本博的另一篇文章<bolt消息传递路径之源码解读>主要从消息接收方面来阐述问题,两篇文章互为补充. worke ...

  3. twitter storm源码走读之3--topology提交过程分析

    概要 storm cluster可以想像成为一个工厂,nimbus主要负责从外部接收订单和任务分配.除了从外部接单,nimbus还要将这些外部订单转换成为内部工作分配,这个时候nimbus充当了调度室 ...

  4. twitter storm源码走读之1 -- nimbus启动场景分析

    欢迎转载,转载时请注明作者徽沪一郎及出处,谢谢. 本文详细介绍了twitter storm中的nimbus节点的启动场景,分析nimbus是如何一步步实现定义于storm.thrift中的servic ...

  5. twitter storm源码走读之7 -- trident topology可靠性分析

    欢迎转载,转载请注明出处,徽沪一郎. 本文详细分析TridentTopology的可靠性实现, TridentTopology通过transactional spout与transactional s ...

  6. twitter storm源码走读之8 -- TridentTopology创建过程详解

    欢迎转载,转载请注明出处,徽沪一郎. 从用户层面来看TridentTopology,有两个重要的概念一是Stream,另一个是作用于Stream上的各种Operation.在实现层面来看,无论是str ...

  7. twitter storm源码走读之6 -- Trident Topology执行过程分析

    欢迎转载,转载请注明出处,徽沪一郎. TridentTopology是storm提供的高层使用接口,常见的一些SQL中的操作在tridenttopology提供的api中都有类似的影射.关于Tride ...

  8. 【原】storm源码之mac os x编译twitter storm源码

    twitter storm是由backtype公司创始人nathanmarz一手研发和开源的流计算(实时计算)框架,堪称实时计算领域的hadoop.nathanmarz也是在mac os x环境下开发 ...

  9. Storm源码分析--Nimbus-data

    nimbus-datastorm-core/backtype/storm/nimbus.clj (defn nimbus-data [conf inimbus] (let [forced-schedu ...

随机推荐

  1. 打开genesis时一直在等待,后出现Timeout in communication read解决方法

    运行输入:netsh winsock reset 然后重启电脑

  2. kvm与selinux

    Desktop virsh start 16lnmp error: Failed to start domain 16lnmp error: unsupported configuration: Un ...

  3. Android中make命令

    转自:http://blog.sina.com.cn/s/blog_abc7e49a01011y0n.html 1.make -jXX  XX表示数字,这个命令将编译Android系统并生成镜像,XX ...

  4. 亿条数据在PHP中实现Mysql数据库分表100张

    当数据量猛增的时候,大家都会选择库表散列等等方式去优化数据读写速度.笔者做了一个简单的尝试,1亿条数据,分100张表.具体实现过程如下: 首先创建100张表: $i=0; while($i<=9 ...

  5. php加密解密

    <?php . [代码][PHP]代码      <?php , ;         return setcookie($name, $value, $expire, $path, $do ...

  6. hdu 1755 数学 ***

    链接:点我 题解可以看这里:点我和这里 #include<cstdio> #include<cstring> #include<algorithm> #define ...

  7. MATLAB学习笔记(二)——主要是MATLAB的矩阵知识

    PS:主要是讲解矩阵的相应的实现方法,其实MATLAB的很大一部分的优势,就是集成了矩阵级别的运算,并以此为特点,可以进行多维空间上的验证. 让我们懂得了原来线性代数如此有用= - =. (一)MAT ...

  8. JavaScript案例五:下拉列表左右选择

    用JavaScript实现下拉列表左右选择,很简单,不过要特别注意循环时要注意变量是否发生了变化(见代码) <!DOCTYPE html> <html> <head> ...

  9. 输入框提示文字js

    <input style="margin-right: 0px; padding-right: 0px;" class="text" required=& ...

  10. 贪心 POJ 1328 Radar Installation

    题目地址:http://poj.org/problem?id=1328 /* 贪心 (转载)题意:有一条海岸线,在海岸线上方是大海,海中有一些岛屿, 这些岛的位置已知,海岸线上有雷达,雷达的覆盖半径知 ...