網址: http://xumingming.sinaapp.com/727/twitter-storm-code-analysis-tuple-send-proc/
這篇文章裏面我們來看一下Storm裏面的tuple到底是如何從一個tuple是怎麼從一個bolt到另一個bolt上去的。
首先Bolt在發射一個tuple的時候是調用OutputCollector的emit或者emitDirect方法,
而這兩個方法最終調用的是clojure代碼裏面的mk-transfer-fn方法:
1
2
3
4
5
6
|
; worker.clj ( defn
mk-transfer- fn
[ transfer-queue ] ( fn
[ task ^Tuple tuple ] (.put ^LinkedBlockingQueue transfer-queue
[ task tuple ] ) )) |
這個方法其實只是往一個LinkedBlockingQueue裏面放入一條新記錄(task-id, tuple)
然後這個queue裏面的內容會被下面這段代碼處理
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
; worker.clj ; 這裏面的這個socket到底是什麼東西? (async- loop ( fn
[ ^ArrayList drainer ^KryoTupleSerializer serializer ] ; 從transfer-queue裏面取出一個任務來 ; 這個任務其實就是(task, tuple) ( let
[ felem (. take
transfer-queue) ] (.add drainer felem) (.drainTo transfer-queue drainer)) (read-locked endpoint-socket-lock ; 獲取從node+port到socket的映射 ( let
[ node+port->socket @node+port->socket ; 獲取從task-id到node+port的映射 task->node+port @task->node+port ] ( doseq
[ [ task ^Tuple tuple ]
drainer ] ; 獲取task對應的socket ( let
[ socket (node+port->socket (task->node+port task)) ; 序列化這個tuple ser-tuple (.serialize serializer tuple) ] ; 發送這個tuple (msg/ send
socket task ser-tuple) )) )) ) |
從上面代碼可見,tuple最終是被序列化之後由msg/send方法通過socket發送給指定的task的。注意上面代碼裏面的async-loop
表示會創建一個單獨的線程來執行這些代碼。可以storm會起一個獨立線程來專門發送待發送的消息的。
我們來看下這個socket到底是個怎麼樣的東西。這個socket是在worker.clj裏面被初始化的,看下面的代碼:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
; socket(worker.clj) (swap! node+port->socket merge (into {} (dofor [ [ node port
:as endpoint ]
new -connections ] [ endpoint (msg/connect mq-context (( :node- >host assignment) node) port) ] ))) |
從上面代碼可以看出socket其實是msg/connect創建出來的。那 msg/connect到底在做什麼呢? 這個方法是定義在protocol.clj裏面的:
1
2
3
4
5
6
|
(defprotocol Context (bind
[ context virtual-port ] ) (connect
[ context host port ] ) ( send -local-task-empty
[ context virtual-port ] ) (term
[ context ] ) ) |
這裏定義的只是一個接口而已,具體的實現是在zmq.clj裏面。zmq是ZeroMQ的縮寫, 可見storm的supervisor之間就是利用zeromq來傳遞tuple的。
zmq.clj裏面的ZMQCOntext實現了Context接口:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
(deftype ZMQContext
[ context linger-ms ipc? ] ; 實現Context接口 Context ; 從給定的virtual-port拉消息 (bind
[ this virtual-port ] (-> context (mq/socket mq/pull) (mqvp/virtual-bind virtual-port) (ZMQConnection.) )) ; 給給定的host,port推送消息(push) (connect
[ this host port ] ( let
[ url ( if
ipc? (-> context (mq/socket mq/push) (mq/ set -linger linger-ms) (mq/connect url) (ZMQConnection.)))) ; 給本地的virtual-port發送一條空消息 ( send -local-task-empty
[ this virtual-port ] ( let
[ pusher (-> context (mq/socket mq/push) (mqvp/virtual-connect virtual-port)) ] (mq/ send
pusher (mq/barr)) (.close pusher))) (term
[ this ] (.term context)) ; 實現ZMQContextQuery接口 ZMQContextQuery (zmq-context
[ this ] context)) |
總結一些Twitter Storm對於tuple的處理/創建過程:
- Bolt創建一個tuple。
- Worker把tuple, 以及這個tuple要發送的地址(task-id)組成一個對象(task-id, tuple)放進待發送隊列(LinkedBlockingQueue).
- 一個單獨的線程(async-loop所創建的線程)會取出發送隊列裏面的每個tuple來處理
- Worker創建從當前task到目的task的zeromq連接。
- 序列化這個tuple並且通過這個zeromq的連接來發送這個tuple。