如何在Erlang或Elixir中使用ZeroMQ实现非阻塞套接字接收?

时间:2017-10-06 07:15:55

标签: erlang elixir zeromq chumak

在Python中,我可以选择使用“poller”对象来轮询阻塞套接字,以便在指定的毫秒数之后等待和解除阻塞消息(在下面的情况下,1000,在while块中):

import zmq

# now open up all the sockets
context = zmq.Context()
outsub = context.socket(zmq.SUB)
outsub.bind("tcp://" + myip + ":" + str(args.outsubport))
outsub.setsockopt(zmq.SUBSCRIBE, b"")
inreq = context.socket(zmq.ROUTER)  
inreq.bind("tcp://" + myip + ":" + str(args.inreqport))
outref = context.socket(zmq.ROUTER)  
outref.bind("tcp://" + myip + ":" + str(args.outrefport))
req = context.socket(zmq.ROUTER)  
req.bind("tcp://" + myip + ":" + str(args.reqport))
repub = context.socket(zmq.PUB)  
repub.bind("tcp://" + myip + ":" + str(args.repubport))

# sort out the poller
poller = zmq.Poller() 
poller.register(inreq, zmq.POLLIN)
poller.register(outsub, zmq.POLLIN)
poller.register(outref, zmq.POLLIN)
poller.register(req, zmq.POLLIN)

# UDP socket setup for broadcasting this server's address 
cs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
cs.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
cs.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

# housekeeping variables
pulsecheck = datetime.utcnow() + timedelta(seconds = 1)
alivelist = dict()
pulsetimeout = 5

while True: 
    polls = dict(poller.poll(1000))
    if inreq in polls:
        msg = inreq.recv_multipart()
        if msg[1] == b"pulse":           # handle pluse
            ansi("cyan", False, textout = " pulse" + "-" + msg[0].decode())
            if not msg[0] in alivelist.keys():
                handlechange(msg[0])
            alivelist[msg[0]] = datetime.utcnow() + timedelta(seconds = pulsetimeout)
    if outsub in polls:
        msgin = outsub.recv_multipart()[0]
        repub.send(msgin) # republish
        msg = unpacker(msgin)
        if isinstance(msg, dict):
            valu = msg.get("value")
            print(".", end = "", flush = True)
        else:
            ansi("green", False, textout = msg)

    if req in polls:
        msg = req.recv_multipart()
        valmsg = validate_request(msg)
        if not valmsg[0]:
            ansi("red", True); print(valmsg[1]); ansi()
        elif len(alivelist) > 0:
            targetnode = random.choice(list(alivelist.keys()))
            inreq.send_multipart([targetnode, packer(valmsg[1])])
            ansi("blue", True, textout = "sent to " + targetnode.decode())
        else:
            ansi("red", True, textout = "NO CONNECTED NODES TO SEND REQUEST TO")
    if outref in polls:
        msg = outref.recv_multipart()
        destinataire, correlid = msg[1].split(b"/")
        req.send_multipart([destinataire, correlid, msg[2]])

我想在Elixir(或Erlang)中实现类似的东西,但我首选的本机库chumak似乎没有实现轮询。我如何在Erlang / Elixir中实现非阻塞接收,最好是使用Chumak,但如果需要,我会转移到另一个Erlang zeroMQ库?我的套接字模式首选项是路由器发送,经销商收到

修改

我的用例如下。我有第三方金融服务,它根据请求提供数据,提供异步的答案。所以你可以发送多个请求,你会在一段不确定的时间后得到回复,而不一定按照你发送的顺序。

所以我需要将这项服务连接到Erlang(实际上是Elixir),ZeroMQ似乎很合适。连接(通过Phoenix)到Erlang / Elixir的多个用户将发送请求,我需要将这些用户传递给此服务。

如果其中一个请求出错,或者第三方服务出现某种问题,则会出现问题。我将阻止等待响应,然后无法处理来自Phoenix的新请求。

基本上我想不断听取新的请求,发送它们,但是如果一个请求没有产生响应,我的响应比请求少一个,这将导致永久等待。

据我所知,如果我单独发送请求,那么好的请求会产生响应,因此即使随着时间的推移,我发送的请求和收到的响应之间会有很大的数字差异,我也不需要担心阻塞。也许设计理念是我不应该担心这个?或者我应该尝试跟踪对请求的一对一响应并以某种方式超时无响应?这是一种有效的设计模式吗?

1 个答案:

答案 0 :(得分:4)

您的系统是否经常连接到异步查询资源,或者您是否与每个查询建立新连接?

每种情况在Erlang中都有自己的自然模型。

案例:单个(或池)长期联系

维护与资源的会话的长期连接(与数据库的连接的工作方式)最自然地建模为系统中具有代表该外部资源的唯一任务的进程。

该过程的要求是:

  • 将外部资源的消息转换为内部有意义的消息(不仅仅是通过垃圾邮件 - 不要让原始的外部数据侵入您的系统,除非它对您完全不透明)。
  • 跟踪超时请求(这可能需要某种类似的轮询,但可以使用erlang:send_after/3更精确地完成

当然,这意味着实现此过程的模块需要说出该资源的协议。但如果实现了这一点,那么就不需要像MQ应用程序这样的消息传递代理。

这允许你让 进程被激活并阻止接收,而你的程序的其余部分将继续执行任何操作。如果没有一些任意的轮询,肯定会让你进入计划问题的邪恶黑色沼泽。

案例:每个查询的新连接

如果对资源的每个查询都需要新连接,则模型类似,但在此处您为每个查询生成一个新进程,它代表系统中的查询。它会阻止等待响应(超时),其他任何事情都不重要。

实际上,这是更简单的模型,因为那时你不必擦除过去的,可能超时的请求列表,这些请求永远不会返回,不必与一组暂停的超时交互通过erlang:send_after/3发送的消息,您将抽象更接近实际的问题模型

您不知道这些查询何时会返回,这会导致一些潜在的混淆 - 因此将每个实际查询建模为生物是切断逻辑混乱的最佳方式。

无论哪种方式,自然地模拟问题:作为并发,异步系统

但是,在任何情况下,您是否希望以Python或C等方式进行轮询。这是一个并发问题,因此对其进行建模将为您提供更多的逻辑自由,并且更有可能产生一个缺少产生奇怪情况的角落的正确解决方案。