【流式計算】Twitter Storm源代碼分析之Tuple是如何發送的

作者: xumingming | 可以轉載, 但必須以超鏈接形式標明文章原始出處和作者信息及版權聲明
網址: 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?
                (str "ipc://" port "ipc")
                (str "tcp://" host ":" port))]
      (-> 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的處理/創建過程:

  1. Bolt創建一個tuple。
  2. Worker把tuple, 以及這個tuple要發送的地址(task-id)組成一個對象(task-id, tuple)放進待發送隊列(LinkedBlockingQueue).
  3. 一個單獨的線程(async-loop所創建的線程)會取出發送隊列裏面的每個tuple來處理
    • Worker創建從當前task到目的task的zeromq連接。
    • 序列化這個tuple並且通過這個zeromq的連接來發送這個tuple。

發佈了34 篇原創文章 · 獲贊 15 · 訪問量 51萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章