ZeroMQ - goddes4/python-study-wiki GitHub Wiki
νΉμ§
- μλ² λλ λ€νΈμνΉ λΌμ΄λΈλ¬λ¦¬ (ActiveMQ or RabbitMQ κ°μ λ©μμ§ λΈλ‘μ»€κ° μ‘΄μ¬νμ§ μμ)
- μ¬μ©μμ μ΅μν Socket Style APIλ‘ λμμΈλ λΌμ΄λΈλ¬λ¦¬
- λ©μμ§ μμ μ΄ κ°λ₯νμ§ μμ λλ Queuing λ μ μλ€ (over-full νμ λν μ μ± μ λ©μμ§ ν¨ν΄μ λ°λΌ κ²°μ )
- λ§μ λ Έλ ₯ μμ΄λ 볡μ‘ν ν΅μ μμ€ν μ μ€κ³ν μ μλλ‘ ν΄μ£Όλ λ©μμ§ λΌμ΄λΈλ¬λ¦¬
- λΆμ° λλ λμμ± μ ν리μΌμ΄μ μμ μ μ©νλ€
- λΌμ°ν μ΄ κ°λ₯ν λ©μΌλ°μ€λ€
- lock-free (lock, semaphores λκΈ°κ° νμνμ§ μμ)
- μλμΌλ‘ μ¬ μ°κ²° μλ (μ΄λ€ μμλ‘λ ꡬμ±μμλ₯Ό μμ ν μ μμ) μΈμ λ μ§ λ€νΈμν¬μ μ°Έμ¬νκ³ λ λ μ μλ μλΉμ€ μ§ν₯ μν€ν μ²(SOA)λ₯Ό λ§λ€μ μλ€.
λΆμ° μ ν리μΌμ΄μ (Distributed Application)μ κ±°λν νλμ μ ν리μΌμ΄μ (Monolithic Application) λ³΄λ€ λ³νμ λμνκΈ° μ½λ€.
server κ° bind() νμ§ μμ μνμμ client κ° connect() νκ³ λ©μμ§λ₯Ό μ‘μ νλλΌλ queuing νμ μ²λ¦¬λλ€.
(λ©μμ§ μ μ‘ μ΄νμ close() νλλΌλ λμ€μ μλ²κ° bind() μ΄νμ λ°μ΄ν°κ° μ λ¬λ¨,
close() μ΄νμ λ°μ΄ν° μ μ‘μ μνμ§ μμΌλ©΄ `socket.setsockopt(zmq.LINGER, 0)` μ€μ )
(connect)(3)PUSH-PULL(1)(bind)
PUSH-1 μμ 3λ² μ μ‘, PUSH-2 μμ 3λ² μ μ‘, μλ² bind() μμΌλ‘ μ§ν ν λ λ©μμ§λ PUSH-1, PUSH-2 νλ²μ© λ²κ°μ μ²λ¦¬νλ€.
(connect)(1)PUSH-PULL(3)(bind)
3κ°μ PULL μμ λμμ recv() λκΈ°μ€μΈ μνμμ PUSH μμ μ μ‘νκ² λλ©΄ 3μ€ νκ°μ PULLλ§ λ©μμ§λ₯Ό μμ νλ€.
λ Έλκ° μ°κ²° λ°©μ
- ν νλ‘μΈμ€μ λ μ°λ λ
- ν μμ€ν μ λ νλ‘μΈμ€
- λ€νΈμν¬μμ λ μμ€ν
Messaging Pattern
- Request-Reply Pattern
- Publish/Subscribe pattern
- Pipeline Pattern
Device
Queue
Forwarder
Streamer
Installation in python
# pip install pyzmq
λ²μ νμΈ
import zmq
print(zmq.pyzmq_version())
ZeroMQ Context
zmq λΌμ΄λΈλ¬λ¦¬ κΈ°λ₯μ μ¬μ©νκΈ° μ μ λ¨Όμ μμ± λμΌ νλ€.
import zmq
context = zmq.Context()
ZeroMQ Sockets
zmq μμΌμ context λ₯Ό ν΅ν΄ μμ±ν μ μλ€.
socket = context.socket(zmq.REP)
Example
REQ/REP
νΉμ§
- REQ socket μ λ§μ μλ²μ μ°κ²°(connect) ν μ μλ€.
- REQ μ send() λ μλ΅μ΄ μ¬ λ κΉμ§ block λλ€.
- REP μ recv() λ μμ²μ΄ μμ λ λ κΉμ§ block λλ€.
Replay
λ©μμ§ μλ΅
import zmq
context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind('tcp://127.0.0.1:10101')
while True:
print('recv : ' + socket.recv_string())
socket.send_string('world')
Request
import zmq
import time
context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect('tcp://127.0.0.1:10101')
while True:
socket.send_string('hello')
print('recv : ' + socket.recv_string())
time.sleep(3)
===
Pub/Sub
νΉμ§
- PUB socketμ κ²½μ° μ°κ²°λ SUBκ° μλ κ²½μ° λ©μμ§λ λ²λ €μ§λ€.
SUB μ κ²½μ° λ°λμ setsockopt() λ₯Ό μ¬μ©νμ¬ subscription μ μ€μ ν΄μΌ νλ€. SUB/PUB μ΄λκ³³μμ μ°κ²°(connect)νλ , λ°μΈλ(bind) νλ λ¬Έμ λ λμ§ μμΌλ, λ§μ½ SUB μμΌμ λ¨Όμ λ°μΈλ(bind) νκ³ λμ€μ PUB μμΌμ μ°κ²°(connect)νλ©΄ SUB μμΌμ μ€λλ λ©μμ§λ₯Ό λ°μ μ μκ² λλ€. κ·Έλ¬λ―λ‘ κ°λ₯νλ©΄ PUBμ λ°μΈλ(bind), SUB μ μ°κ²°(connect) νλκ²μ΄ κ°μ₯ μ’λ€.
Publisher κ° λ°μΈλ© ν μ¦μ λ©μμ§λ₯Ό μ μ‘νλ©΄, Subscriber λ λ°μ΄ν°λ₯Ό μμ λͺ»ν κ°λ₯μ±μ΄ μμ΅λλ€. μ΄λ₯Ό μν΄μ Subscriber κ° μ°κ²°νκ³ μ€λΉλκΈ°κΉμ§ λ°μ΄ν°λ₯Ό λ°μ‘νμ§ μλλ‘ λκΈ°ν νλ λ°©λ²μ μ 곡νλ€.
ΓMQμ PUB-SUB Pattern νΉμ§
- νλμ Subscriber λ ν κ° μ΄μμ Publisher μ μ°κ²°ν μ μλ€.
- Subscriber κ° μλ€λ©΄ λͺ¨λ Publisher μ λ©μμ§λ μ μ€ λλ€.
- Subscriber μμλ§ λ©μμ§ νν°λ§μ΄ κ°λ₯νλ€.
Publisher
3 μ΄μ νλ² λ©μμ§ μ μ‘
import zmq
import time
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind('tcp://127.0.0.1:10100')
while True:
socket.send_string('Hello')
time.sleep(3)
Subscriber
import zmq
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect('tcp://127.0.0.1:10100')
socket.setsockopt_string(zmq.SUBSCRIBE, '') # it will capture all messages
while True:
print(socket.recv_string())
===
PULL/PUSH (Pipeline ν¨ν΄)
νΉμ§
- PUSH socket μ μ°κ²°μ΄ μ‘΄μ¬νμ§ μμ λ send() νκΈ° λλ©΄ μ°κ²° λ λ κΉμ§ λΈλ‘ λλ€.
- μ λ¬λ λ©μμ§λ μ°κ²°λ socket μ round robin λλ€.
- Workerλ ventilatorμ PULLλ‘ μ°κ²°(connect)λμ΄ μκ³ , sinkμλ PUSHλ‘ μ°κ²°(Connect) λμ΄ μλ€. μ΄κ²μ Worker λ μμλ‘ μΆκ° ν μ μλ€λ κ²μ μλ―Ένλ€. λ§μ½ worker κ° λ°μΈλ©(bind) λμ΄ μλ€λ©΄ worker κ° μΆκ° λ λλ§λ€ λ§€λ² ventilatorμ sinkμ λ λ§μ μμΌμ΄ νμνλ€. μ΄ κ΅¬μ‘°μμ ventilatorμ sink λ stable part, workerλ dynamic part λΌ λΆλ₯Έλ€.
- ventilatorμ PUSH μμΌμ κ· λ±νκ² Workerμ μμ μ λΆλ°°νλ€. (load-balancing)
- Sinkμ PULL μμΌμ κ· λ±νκ² Workerλ‘ λΆν° κ²°κ³Όλ₯Ό μμ§νλ€. (fair-queuing)
PUSH, PULLμ bind, connect λ μν©μ λ°λΌ μ μ©ν ν¨ν΄μ΄ μλ€.
- PUSH - bind, PULL - connect μ κ²½μ°λ λμ μ²λ¦¬λ₯Ό μν Producer-Consumer ν¨ν΄μ μ ν©
- PUSH - connect, PULL - bind μ κ²½μ° μ²λ¦¬ λ°μ΄ν°λ₯Ό νκ³³μΌλ‘ μ§μ€ μμΌ λͺ¨μ λ μ’μ
Parallel Pipeline with Kill Signaling
Parallel task ventilator
3 μ΄μ νλ² λ©μμ§ μ μ‘
import zmq
import random
import time
context = zmq.Context()
sender = context.socket(zmq.PUSH)
sender.bind('tcp://127.0.0.1:10102')
sink = context.socket(zmq.PUSH)
sink.connect('tcp://127.0.0.1:10103')
print("Press Enter when the workers are ready: ")
_ = input()
print("Sending tasks to workers")
# The first message is "0" and signals start of batch
sink.send(b'0')
# Initialize random number generator
random.seed()
# Send 100 tasks
total_msec = 0
for task_nbr in range(100):
# Random workload from 1 to 100 msecs
workload = random.randint(1, 100)
total_msec += workload
sender.send_string(u'%i' % workload)
print(i)
print("Total expected cost: %s msec" % total_msec)
# Give 0MQ time to deliver
time.sleep(1)
Parallel task worker
import sys
import time
import zmq
context = zmq.Context()
# Socket to receive messages on
receiver = context.socket(zmq.PULL)
receiver.connect("tcp://localhost:10102")
# Socket to send messages to
sender = context.socket(zmq.PUSH)
sender.connect("tcp://localhost:10103")
# Process tasks forever
while True:
s = receiver.recv()
# Simple progress indicator for the viewer
sys.stdout.write('.')
sys.stdout.flush()
# Do the work
time.sleep(int(s)*0.001)
# Send results to sink
sender.send(b'')
Parallel task sink
import sys
import time
import zmq
context = zmq.Context()
# Socket to receive messages on
receiver = context.socket(zmq.PULL)
receiver.bind("tcp://127.0.0.1:10103")
# Wait for start of batch
s = receiver.recv()
# Start our clock now
tstart = time.time()
# Process 100 confirmations
total_msec = 0
for task_nbr in range(100):
s = receiver.recv()
if task_nbr % 10 == 0:
sys.stdout.write(':')
else:
sys.stdout.write('.')
sys.stdout.flush()
# Calculate and report duration of batch
tend = time.time()
print("Total elapsed time: %d msec" % ((tend-tstart)*1000))
context.term() μ νΈμΆ νκΈ° μ μ μ²΄ν¬ μ¬ν
- μ΄λ €μλ μμΌμ΄ μλ€λ©΄ Blocking (LINGER : 0 μμλ Blocking)
- μμΌμ΄ Close() μνλλΌλ send() κ° μ²λ¦¬ λκΈ° μ κΉμ§ Blocking (LINGER : 0 μ μ©μ μμΈ)
- μ¦ λ°μ΄ν° μ μ‘μ΄ μλ£λ μ΄νμ μμΌ μ’ λ£, Context μ’ λ£ μμΌλ‘ μ§νλμ΄μΌ ν¨. λ°μ΄ν° μ μ‘κ³Ό μκ΄μμ΄ μ’ λ£ νκ³ μΆμΌλ©΄ LINGER zero μ΅μ μ μ©
Handling Multiple Sockets
This version uses a simple recv loop
import zmq
import time
# Prepare our context and sockets
context = zmq.Context()
# Connect to task ventilator
receiver = context.socket(zmq.PULL)
receiver.connect("tcp://localhost:5557")
# Connect to weather server
subscriber = context.socket(zmq.SUB)
subscriber.connect("tcp://localhost:5556")
subscriber.setsockopt(zmq.SUBSCRIBE, b"10001")
# Process messages from both sockets
# We prioritize traffic from the task ventilator
while True:
# Process any waiting tasks
while True:
try:
msg = receiver.recv(zmq.DONTWAIT)
except zmq.Again:
break
# process task
# Process any waiting weather updates
while True:
try:
msg = subscriber.recv(zmq.DONTWAIT)
except zmq.Again:
break
# process weather update
# No activity, so sleep for 1 msec
time.sleep(0.001)
This version uses zmq.Poller()
import zmq
# Prepare our context and sockets
context = zmq.Context()
# Connect to task ventilator
receiver = context.socket(zmq.PULL)
receiver.connect("tcp://localhost:5557")
# Connect to weather server
subscriber = context.socket(zmq.SUB)
subscriber.connect("tcp://localhost:5556")
subscriber.setsockopt(zmq.SUBSCRIBE, b"10001")
# Initialize poll set
poller = zmq.Poller()
poller.register(receiver, zmq.POLLIN)
poller.register(subscriber, zmq.POLLIN)
# Process messages from both sockets
while True:
try:
socks = dict(poller.poll())
except KeyboardInterrupt:
break
if receiver in socks:
message = receiver.recv()
# process task
if subscriber in socks:
message = subscriber.recv()
# process weather update
κ·Έλ°μ λ©μμ§ ν¨ν΄
Small-Scale Pub-Sub Network
Extended Pub-Sub
Request Distribution
Extended Request-Reply
Pub-Sub Forwarder Proxy
Queue Broker Exameple
Simple request-reply broker
import zmq
# 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)
Same as request-reply broker but using zmq.proxy
import zmq
def main():
""" main method """
context = zmq.Context()
# Socket facing clients
frontend = context.socket(zmq.ROUTER)
frontend.bind("tcp://*:5559")
# Socket facing services
backend = context.socket(zmq.DEALER)
backend.bind("tcp://*:5560")
zmq.proxy(frontend, backend)
# We never get hereβ¦
frontend.close()
backend.close()
context.term()
if __name__ == "__main__":
main()