python_ZMQ学习系列(一)

系列简介

该系列是根据电子工业出版社出版的《ZeroMQ云时代极速消息通信库》的章节进行系列章节分割(即一为书中第一章)
由于作者在书里的项目都是开源的,应该不会侵权,如有侵权可告知删除

什么是ZMQ

书中其实是有zmq的简介的,大概概括以下,就是一个可嵌入的网络库,作用就像是一个并发框架。

为什么叫ZMQ

如果你和我一样只是在网上找资料可能会困惑,为什么有的叫ZeroMq;有的叫0MQ;有的叫zmq,还有人叫ØMQ。
是因为这个框架想体现自己的“零代理”“零延时(尽可能零延时)”

ZMQ的模式

  1. 扇出
  2. 发布 - 订阅
  3. 任务分配
  4. 请求 - 应答

代码样例

3.7.8的python上成功运行的,此处不附上结果了,因为在网上看很多博主写的都有点笼统,所以建议如果有时间可以找一下那本书,去成体系的看一下。

请求-应答

应答服务端:有的博主把这一端比喻成老师,负责回应

"""
简易版的服务器
请求-应答模式,它对应适用于RPC(远程过程调用)和传统的客户端/服务器模型
在这个情况下,如果终止服务器并且重新启动它,客户端是无法正常恢复的
"""
import zmq
import time
context=zmq.Context()
# REP是应答方
socket=context.socket(zmq.REP)
socket.bind('tcp://127.0.0.1:5559')
while True:
    message=socket.recv()
    print("Received request: ", message)
    time.sleep(1)
    socket.send("World".encode())


请求-客户端:对应教师,这一端被比喻为学生,负责提问

import zmq
import time
context=zmq.Context()

print("Connecting to hello world server..."  )
# REQ 是提问方
socket=context.socket(zmq.REQ)
socket.connect("tcp://127.0.0.1:5559")
for request in range(10):
    print("Sending request ", request, "...")
    socket.send('Hello'.encode())

    message=socket.recv()
    print("Received reply ", request, "[", message, "]")

书中对于请求-应答的截图

我的理解是,这是一问一答的工作,所以即使你先打开客户端,再打开服务器,一问一答后仍然能够正常的工作。
需要注意的一点,如果终止服务器并重新启动它,客户端将无法恢复,因为从崩溃状态恢复是很复杂的(我还没看到那一章,后续应该会有)

发布 - 订阅

书上的例子是一个查询天气的服务端

发布者

from random import randrange
import zmq

context = zmq.Context()
# 发布人
publisher = context.socket(zmq.PUB)
publisher.bind("tcp://*:5556")

while True:
    # 订阅号
    zipcode = randrange(1, 100000)
    # 温度
    temperature = randrange(-80, 135)
    # 相对湿度
    relhumidity = randrange(10, 60)

    publisher.send_string(f"{zipcode} {temperature} {relhumidity}")

订阅者

"""
单向数据发布模式-客户端
发布-订阅模式,没有终点也没有起点,就像是一个永无休止的广播
"""
import random
import sys
import time

import zmq

context = zmq.Context()
# 订阅人
subscriber = context.socket(zmq.SUB)
print("尝试连接天气服务器:Collecting updates from weather server...")
subscriber.connect("tcp://localhost:5556")

# 订阅邮政编码
"""sys.argv表示sys模块中的argv变量,
sys.argv是一个字符串的列表,其包含了命令行参数的列表,即使用命令行传递给你的程序的参数。
特别注意:脚本的名称总是sys.argv列表的第一个参数"""
zip_filter = sys.argv[1] if len(sys.argv) > 1 else "10001"

# 使用sub套接字时必须使用zmq_setsockopt设置一个订阅,如果没有设置订阅就不会受到消息
# zip_filter设置一个订阅码,有订阅号是特定值时客户端才可以接收,如果不设置订阅号则设置“”即可
subscriber.setsockopt_string(zmq.SUBSCRIBE, zip_filter)
total_temp = 0
print('连接成功开始接收信息')
for update_nbr in range(5):
    now_string = subscriber.recv_string()
    zipcode, temperature, relhumidity = now_string.split()
    total_temp += int(temperature)
    print(f'接收到{zipcode}')
    print((f"Average temperature for zipcode "
           f"'{zip_filter}' was {total_temp / (update_nbr + 1)} F"))

书中发布-订阅样例图
需要注意的是

  1. 发布者不知道订阅者开始得到信息的精确时间,即使你先启动了一个订阅者,稍等片刻后再启动发布者,订阅者也会错过发布者发送的第一个消息(书上是说一个,我测试了一下应该不止一条消息)。这是因为订阅者连接到发布者的时候,需要的时间很短,在这个时候发布者可能已经把消息发出去了。
    我在测试的时候直接在发布端加了个延时进行测试,延时后客户端可以正常接收,但是作者在书中说:“这是一个很愚蠢的方式,因为这脆弱且不雅又缓慢”之后会有介绍如何优雅实现。
  2. 一个订阅者可以连接到多个发布者,每次使用一个connect调用,那么数据将交错到达(‘公平排队’)因此,没有任何一个发布者能够淹没其他发布者(前面是原文,我的理解是:你可以看B站,可以看微博,可以订阅多个平台)
  3. 如果一个发布者没有连接的订阅者,那么他会简单的丢弃所有消息。
  4. 如果你是用的是TCP并且订阅者是慢速的,那么消息将在发布方排队。我们将在下一章了解如何通过“高水位线”来针对这种情况保护发布者。
  5. 从 ØMQ v3.x 开始,在使用连接的协议(tcp 或 ipc)时,过滤发生在发布方。使用epgm 协议,过滤发生在订阅方。但在 ØMQ v2.x 版本中,所有过滤都发生在订阅方。

Push-Pull

这个例子是,在发生器中生成多个并行任务,分发给多个工人,然后让工人汇总结果发送给接收器

# 生成并行任务端
import random
import time

import zmq

context = zmq.Context()
# 用于发送信息的套接字
sender = context.socket(zmq.PUSH)
sender.bind("tcp://*:5557")

# 用于发送批次开始消息的套接字
sink = context.socket(zmq.PUSH)
sink.connect("tcp://localhost:5558")
print("当工人准备好后点击回程Press Enter when the workers are ready: ")
_ = input()
print("开始发送任务Sending tasks to workers...")
# 第一个消息是“0”,他表示批次的开始
sink.send(b'0')
# 初始化随机数发生器
random.seed()
total_msec = 0
print('准备进入循环')
# 发送一百个任务
for task_nbr in range(100):
    print('-----------------------------')
    print(f'开始发送{task_nbr}')
    # 从1到100毫秒的随机工作负载
    workload = random.randint(1, 100)
    total_msec += workload
    sender.send_string(f"{workload}")

print(f"总体耗费时间为Total expected cost: {total_msec} msec")
# 给ZMQ一点时间来传递
time.sleep(1)
""""
并行任务工人
将PULL套接字连接到tcp://localhost:5557
通过上面的套接字来手机自来发生器的工作负载
将PUSH套接字连接到tcp://localhost:5558
通过5558的套接字发送结果给接收器
"""
import sys
import time

import zmq

context = zmq.Context()

# 用于接收消息的套接字
receiver = context.socket(zmq.PULL)
receiver.connect('tcp://localhost:5557')

# 用于发送消息的套接字
sender = context.socket(zmq.PUSH)
sender.connect('tcp://localhost:5558')

# 永远地处理任务
while True:
    print('-----------------------------')
    print('接收信息')
    s = receiver.recv()
    # 用于查看器的简易过程指示器
    sys.stdout.write('.')
    sys.stdout.flush()
    # 不做工作
    time.sleep(int(s) * 0.001)
    # 将结果发送给接收器
    sender.send_string(f'{s}')
    print('发送消息')

"""
并行任务接收器
将PULL套接字绑定到 tcp://localhost:5558
通过上述套接字收集来自各个工人的结果
"""
import sys
import time

import zmq

# 准备上下文和套接字
context = zmq.Context()
receiver = context.socket(zmq.PULL)
# 接收各个工人结果的服务器
receiver.bind('tcp://*:5558')
# 等待批次的开始
s = receiver.recv()

# 启动时钟
tstart_time = time.time()

# 处理100个确认
for task_nbr in range(100):
    print('-----------------------------')
    print('开始接收任务')
    s = receiver.recv()
    print(s)
    if task_nbr % 10 == 0:
        sys.stdout.write(':')
    else:
        sys.stdout.write('.')
    sys.stdout.flush()
    print('准备接收下一个任务')

# 计算并报告批次的用时
tend=time.time()
print(f"Total elapsed time: {(tend-tstart_time)*1000} msec")

在我的理解里:Push和Pull都能成为服务器,绑定一个端口
书中:这段代码有一个问题,必须同步开始同批次所有工人的启动和运行,这是一个疑难杂症,并没有简单的解决方案。connect需要一定的时间,所以当一组工人连接到发生器的时候,第一个连接成功的工人会在很短的时间得到消息的整个负载,而其他的工人还在进行连接。如果不知道为什么批次开始的不同步,那么系统就将无法并行运行,可以尝试在发生器中移走等待,看看会发生什么?
负载均衡:发生器的PUSH套接字将任务均匀的分配给工人(假设批处理开始发出之前,都已连接)。这就是所谓的负载均衡
公平排队:接收器的PULL套接字均匀的接受来自工人的结果。这就是所谓的公平排队
在这里插入图片描述

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值