級聯模式
通常,一個節點,即可以作爲 Server,同時也能作爲 Client,通過 PipeLine 模型中的 Worker,他向上連接着任務分發,向下連接着結果蒐集的 Sink 機器。因此,我們可以藉助這種特性,豐富的擴展原有的三種模式。例如,一個代理 Publisher,作爲一個內網的 Subscriber 接受信息,同時將信息,轉發到外網,其結構圖
多個服務器
ZMQ 和 Socket 的區別在於,前者支持N:M的連接,而後者則只是1:1的連接,那麼一個 Client 連接多個 Server 的情況是怎樣的呢
ZMQ 的N:1的連接情況:(一對多)
我們假設 Client 有 R1,R2,R3,R4四個任務,我們只需要一個 ZMQ 的 Socket,就可以連接四個服務,他能夠自動均衡的分配任務。如圖所示,R1,R4自動分配到了節點A,R2到了B,R3到了C。
N:M的連接(多對多)
如圖:
我們通過一箇中間結點(Broker)來進行負載均衡的功能。我們通過代碼瞭解,其中的 Client 實際基礎的zmq.REQ模式,而 Server 端的不同是,他不需要監聽端口,而是需要連接 Broker 的端口,接受需要處理的信息。所以,我們重點閱讀 Broker 的代碼:
client.py
import zmq,time
# Prepare our context and sockets
context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect("tcp://localhost:5559")
# Do 10 requests, waiting each time for a response
for request in range(1,11):
time.sleep(2)
socket.send(b"Hello")
message = socket.recv()
print("Received reply %s [%s]" % (request, message))
Broker.py
import zmq,time
# Prepare our context and sockets
context = zmq.Context()
frontend = context.socket(zmq.ROUTER)
backend = context.socket(zmq.DEALER)
frontend.bind("tcp://*:5559")
backend.bind("tcp://*:5560")
# Initialize poll set
poller = zmq.Poller()
poller.register(frontend, zmq.POLLIN)
poller.register(backend, zmq.POLLIN)
# Switch messages between sockets
while True:
socks = dict(poller.poll()) #輪詢器 循環接收
if socks.get(frontend) == zmq.POLLIN:
message = frontend.recv_multipart()
backend.send_multipart(message)
if socks.get(backend) == zmq.POLLIN:
message = backend.recv_multipart()
frontend.send_multipart(message)
server.py
import zmq
context = zmq.Context()
socket = context.socket(zmq.REP)
socket.connect("tcp://localhost:5560")
while True:
message = socket.recv()
print("Received request: %s" % message)
socket.send(b"World")
同時我們可以開多個客戶端可多個服務端來同時進行,Broker.py可以根據收到消息的不同來進行相應的處理返回給對方
Broker 監聽了兩個端口,接受從多個 Client 端發送過來的數據,並將數據,轉發給 Server。在 Broker 中,我們監聽了兩個端口,使用了兩個 Socket,那麼對於多個 Socket 的情況,我們是不需要通過輪詢的方式去處理數據的,在之前,我們可以使用 libevent 實現,異步的信息處理和傳輸。而現在,我們只需要使用 ZMQ 的$poll->poll 以實現多個 Socket 的異步處理。
擴展 ZMQ 的訂閱者模式
當我們將 WEB 代碼部署到集羣上的時候,如果需要實時的將最新的配置信息,主動的推送到各個機器節點。在此過程中,我們一定要保證,各個節點收到的信息的一致性和正確性,如果使用 HTTP,由於他的無狀態性,我們無法保證信息的一致性,當然,你可以使用 HTTP 來實現,只是更復雜,ZMQ是更好的選擇
我們使用 ZMQ 的信息訂閱模式。在普通模式中,我們注意到,對於後來的加入節點,始終會丟失在他加入之前,已經發送的信息(Slow joiner)。我們可以開啓另外一個 ZMQ 的通信通道,用於報告當前節點的情況(節點的身份、準備狀態等),保證所有的節點都連接完成之後再開始推送消息,其結構如圖所示:
使用 Request-Reply 連接來確認所有的節點連接完畢
Sub代碼:
import time
import zmq
def main():
context = zmq.Context()
#創建接收訂閱消息的連接
subscriber = context.socket(zmq.SUB)
subscriber.connect('tcp://localhost:5561')
#設置接收的連接頭
subscriber.setsockopt(zmq.SUBSCRIBE, b'')
time.sleep(1)
#創建用來確認用戶連接是否足夠的連接
syncclient = context.socket(zmq.REQ)
syncclient.connect('tcp://localhost:5562')
#發送確認連接的消息 並接受`在這裏插入代碼片`
syncclient.send(b'')
syncclient.recv()
nbr = 0
while True:
msg = subscriber.recv()
if msg == b'END':
break
nbr += 1
print ('Received %d updates' % nbr)
if __name__ == '__main__':
main()
pub代碼:
import zmq
# 確認節點數目達到要求
SUBSCRIBERS_EXPECTED = 10
def main():
context = zmq.Context()
# 創建用來發送需要的消息的連接
publisher = context.socket(zmq.PUB)
publisher.sndhwm = 1100000 # 設置高水位標記
publisher.bind('tcp://*:5561')
# 創建用來確認用戶數量是否足夠的連接
syncservice = context.socket(zmq.REP)
syncservice.bind('tcp://*:5562')
subscribers = 0
# 判斷此時的連接數 不夠的話繼續接收並連接
while subscribers < SUBSCRIBERS_EXPECTED:
msg = syncservice.recv()
syncservice.send(b'')
subscribers += 1
print("+1 subscriber (%i/%i)" % (subscribers, SUBSCRIBERS_EXPECTED))
#當連接數量足夠了開始發佈消息
for i in range(1000000):
publisher.send(b'Rhubarb')
publisher.send(b'END')
if __name__ == '__main__':
main()
如果使用TCP連接並且訂閱者是慢速的,那麼消息將在發佈方排隊;可以使用高水位標記(High-Water Marks,HWM)來定義緩衝區的大小,在ZeroMQ v2.x版本中HWM默認是無限制的,而在v3.x中默認情況下它是1000。對於PUB套接字,當到達HWM時,將丟棄數據。設置HWM參數: