如果在 Docker 中运行,ZeroMQ REQ .recv() 会挂起大于 ~1kB 的消息



我正在研究一个相对简单的基于Python/ZeroMQ的工作分配系统,使用REQ/ROUTER套接字。系统是分布式的,工作节点在地理上分布在不同的大陆上。

负责分配工作的ROUTER.bind()ROUTER套接字。工作人员使用REQ套接字通过 TCP.connect()它。

在设置新工作节点的过程中,我注意到虽然较小的消息(最多 1kB)可以毫无问题地完成行程,但由ROUTER-end 发送的 ~2kB 及以上的回复永远不会被工作线程接收到他们的REQ-socket 中 - 当我调用recv()时,套接字只是挂起。

worker代码在Docker容器内运行,当使用--net=host运行相同的映像时,我能够解决此问题 - 如果Docker使用主机网络,这似乎不会发生。

我想知道这是主机上或 Docker 中的网络堆栈配置中的内容,还是在我的代码中可以阻止的内容?

这是我的代码的简化版本,重现了此问题:

工人

import sys
import zmq
import logging
import time
READY = 'R'
def worker(connect_to):
ctx = zmq.Context()
socket = ctx.socket(zmq.REQ)
socket.connect(connect_to)
log = logging.getLogger(__name__)
while True:
socket.send_string(READY)
log.debug("Send READY message, waiting for reply")
message = socket.recv()
log.debug("Got reply of %d bytes", len(message))
time.sleep(5)

if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
worker(sys.argv[1])

路由器

import sys
import zmq
import logging
REPLY_SIZE = 1024 * 8
def router(bind_to):
ctx = zmq.Context()
socket = ctx.socket(zmq.ROUTER)
socket.bind(bind_to)
poller = zmq.Poller()
poller.register(socket, zmq.POLLIN)
log = logging.getLogger(__name__)
while True:
socks = dict(poller.poll(5000))
if socks.get(socket) == zmq.POLLIN:
message = socket.recv_multipart()
log.debug("Received message of %d parts", len(message))
identity, _ = message[:2]
res = handle_message(message[2:])
log.debug("Sending %d bytes back in response on socket", len(res))
socket.send_multipart([identity, '', res])

def handle_message(parts):
log = logging.getLogger(__name__)
log.debug("Got message: %s", parts)
return 'A' * REPLY_SIZE

if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
router(sys.argv[1])

FWIW 我能够在 Ubuntu 16.04(路由器和工人)上使用 Docker17.09.0-ce、libzmq4.1.5和 PyZMQ15.4.0重现这一点。

不,先生,插座根本不挂:

为什么?

问题是,您已经指示Socket()实例进入无限阻塞状态,一旦调用.recv()方法,而没有指定zmq.NOBLOCK标志(ZeroMQ 原始 API 中的ZMQ_DONTWAIT标志)。

这就是原因,根据昨天报告的其他情况,将代码移动到无限阻塞状态,因为似乎还有其他问题阻止 Docker-container 将任何第一条消息正确传递到工人的 Docker 嵌入式 ZeroMQ-Context()I/O 引擎和REQ接入点的手中。由于REQ原型使用严格的两步有限状态自动机 - 严格跨步(.send()->.recv()->.send()->...ad infimum)

这种因>果逆转是错误的和误导性的 -
">套接字只是挂起">
的问题无法从Docker 不传递一条消息(允许.recv()返回)的问题中决定


后续步骤:

可以在REQ端使用.poll()来嗅探,而不会阻止 worker 中任何已到达的消息。

一旦没有这些,首先关注 Docker+ 接下来可能会受益于 ZeroMQContext()-I/O 引擎性能和链路级调整配置选项。

最新更新