简体   繁体   English

Python ZeroMQ中的异步客户端/服务器模式

[英]Asynchronous Client/Server pattern in Python ZeroMQ

I have 3 programs written in Python, which need to be connected. 我有3个用Python编写的程序,需要连接。 2 programs X and Y gather some information, which are sent by them to program Z. Program Z analyzes the data and send to program X and Y some decisions. 2个程序X和Y收集一些信息,这些信息由它们发送到程序Z。程序Z分析数据并将一些决定发送给程序X和Y。 Number of programs similar to X and Y will be expanded in the future. 将来将扩展与X和Y类似的程序数量。 Initially I used named pipe to allow communication from X, Y to Z. But as you can see, I need bidirectional relation. 最初,我使用命名管道来允许从X,Y到Z的通信。但是如您所见,我需要双向关系。 My boss told me to use ZeroMQ. 我的老板告诉我使用ZeroMQ。 I have just found pattern for my use case, which is called Asynchronous Client/Server. 我刚刚为我的用例找到了模式,称为异步客户端/服务器。 Please see code from ZMQ book ( http://zguide.zeromq.org/py:all ) below. 请参阅下面的ZMQ书( http://zguide.zeromq.org/py:all )中的代码。

The problem is my boss does not want to use any threads, forks etc. I moved client and server tasks to separate programs, but I am not sure what to do with ServerWorker class. 问题是我的老板不想使用任何线程,分支等。我将客户端和服务器任务移到了单独的程序中,但是我不确定该如何处理ServerWorker类。 Can this be somehow used without threads? 可以在没有线程的情况下使用它吗? Also, I am wondering, how to establish optimal workers amount. 另外,我想知道如何确定最佳工人数量。

import zmq
import sys
import threading
import time
from random import randint, random

__author__ = "Felipe Cruz <felipecruz@loogica.net>"
__license__ = "MIT/X11"

def tprint(msg):
    """like print, but won't get newlines confused with multiple threads"""
    sys.stdout.write(msg + '\n')
    sys.stdout.flush()

class ClientTask(threading.Thread):
    """ClientTask"""
    def __init__(self, id):
        self.id = id
        threading.Thread.__init__ (self)

    def run(self):
        context = zmq.Context()
        socket = context.socket(zmq.DEALER)
        identity = u'worker-%d' % self.id
        socket.identity = identity.encode('ascii')
        socket.connect('tcp://localhost:5570')
        print('Client %s started' % (identity))
        poll = zmq.Poller()
        poll.register(socket, zmq.POLLIN)
        reqs = 0
        while True:
            reqs = reqs + 1
            print('Req #%d sent..' % (reqs))
            socket.send_string(u'request #%d' % (reqs))
            for i in range(5):
                sockets = dict(poll.poll(1000))
                if socket in sockets:
                    msg = socket.recv()
                    tprint('Client %s received: %s' % (identity, msg))

        socket.close()
        context.term()

class ServerTask(threading.Thread):
    """ServerTask"""
    def __init__(self):
        threading.Thread.__init__ (self)

    def run(self):
        context = zmq.Context()
        frontend = context.socket(zmq.ROUTER)
        frontend.bind('tcp://*:5570')

        backend = context.socket(zmq.DEALER)
        backend.bind('inproc://backend')

        workers = []
        for i in range(5):
            worker = ServerWorker(context)
            worker.start()
            workers.append(worker)

        poll = zmq.Poller()
        poll.register(frontend, zmq.POLLIN)
        poll.register(backend,  zmq.POLLIN)

        while True:
            sockets = dict(poll.poll())
            if frontend in sockets:
                ident, msg = frontend.recv_multipart()
                tprint('Server received %s id %s' % (msg, ident))
                backend.send_multipart([ident, msg])
            if backend in sockets:
                ident, msg = backend.recv_multipart()
                tprint('Sending to frontend %s id %s' % (msg, ident))
                frontend.send_multipart([ident, msg])

        frontend.close()
        backend.close()
        context.term()

class ServerWorker(threading.Thread):
    """ServerWorker"""
    def __init__(self, context):
        threading.Thread.__init__ (self)
        self.context = context

    def run(self):
        worker = self.context.socket(zmq.DEALER)
        worker.connect('inproc://backend')
        tprint('Worker started')
        while True:
            ident, msg = worker.recv_multipart()
            tprint('Worker received %s from %s' % (msg, ident))
            replies = randint(0,4)
            for i in range(replies):
                time.sleep(1. / (randint(1,10)))
                worker.send_multipart([ident, msg])

        worker.close()

def main():
    """main function"""
    server = ServerTask()
    server.start()
    for i in range(3):
        client = ClientTask(i)
        client.start()

    server.join()

if __name__ == "__main__":
    main()

So, you grabbed the code from here: Asynchronous Client/Server Pattern 因此,您从这里获取了代码: 异步客户端/服务器模式

Pay close attention to the images that show you the model this code is targeted to. 请密切注意显示该代码所针对的模型的图像。 In particular, look at "Figure 38 - Detail of Asynchronous Server". 特别是,请查看“图38-异步服务器的详细信息”。 The ServerWorker class is spinning up 5 "Worker" nodes. ServerWorker类正在ServerWorker 5个“ Worker”节点。 In the code, those nodes are threads, but you could make them completely separate programs. 在代码中,那些节点是线程,但是您可以使它们完全独立于程序。 In that case, your server program (probably) wouldn't be responsible for spinning them up, they'd spin up separately and just communicate to your server that they are ready to receive work. 在这种情况下,您的服务器程序(可能)将不会对其进行旋转,它们将单独旋转,并仅与您的服务器进行通信以表明它们已准备好接受工作。

You'll see this often in ZMQ examples, a multi-node topology mimicked in threads in a single executable. 您会在ZMQ示例中经常看到这种情况,ZMQ示例是在单个可执行文件中的线程中模拟的多节点拓扑。 It's just to make reading the whole thing easy, it's not always intended to be used that way. 仅仅是为了使阅读整篇文章容易,并不总是打算那样使用。

For your particular case, it could make sense to have the workers be threads or to break them out into separate programs... but if it's a business requirement from your boss, then just break them out into separate programs. 对于您的特殊情况,让工作人员成为线程或将其分解为单独的程序是很有意义的……但是,如果老板提出了业务要求,则只需将其分解为单独的程序即可。

Of course, to answer your second question, there's no way to know how many workers would be optimal without understanding the work load they'll be performing and how quickly they'll need to respond... your goal is to have the worker complete the work faster than new work is received. 当然,要回答第二个问题,在不了解他们将要执行的工作量以及他们将需要多快响应的情况下,无法知道有多少工人是最佳的……您的目标是让工人完成工作比收到新工作快。 There's a fair chance, in many cases, that that can be accomplished with a single worker. 在很多情况下,只有一个工人即可实现这一目标。 If so, you can have your server itself be the worker, and just skip the entire "worker tier" of the architecture. 如果是这样,您可以让服务器本身成为工作服务器,而只需跳过架构的整个“工作人员层”。 You should start there, for the sake of simplicity, and just do some load testing to see if it will actually cope with your workload effectively. 为了简单起见,您应该从此处开始,然后进行一些负载测试以查看它是否实际上可以有效地处理您的工作负载。 If not, get a sense of how long it takes to complete a task, and how quickly tasks are coming in. Let's say a worker can complete a task in 15 seconds. 如果不是,则了解完成一项任务需要多长时间,以及完成任务有多快。假设某个工人可以在15秒内完成一项任务。 That's 4 tasks a minute. 每分钟4个任务。 If tasks are coming in 5 tasks a minute, you need 2 workers, and you'll have a little headroom to grow. 如果每分钟要执行5个任务,那么您需要2个工作人员,并且还有一点增长空间。 If things are wildly variable, then you'll have to make a decision about resources vs. reliability. 如果情况千差万别,那么您就必须决定资源与可靠性。

Before you get too much farther down the trail, make sure you read Chapter 4, Reliable Request/Reply Patterns, it will provide some insight for handling exceptions, and might give you a better pattern to follow. 在您走得更远之前,请确保您已阅读了第4章,可靠的请求/答复模式,它将为处理异常提供一些见识,并可能为您提供更好的模式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM