【转载翻译】谈谈Nanomsg与可扩展性协议(为什么ZeroMQ不应该是你的首选)

本文转载自本人博客:https://www.jjy2023.cn/2024/06/10/%e3%80%90%e8%bd%ac%e8%bd%bd%e3%80%91%e8%b0%88%e8%b0%88nanomsg%e4%b8%8e%e5%8f%af%e6%89%a9%e5%b1%95%e6%80%a7%e5%8d%8f%e8%ae%ae/

原文:A Look at Nanomsg and Scalability Protocols (Why ZeroMQ Shouldn’t Be Your First Choice)

本文由Tyler Treat发布于2014年6月29日,很早了,很多内容已经过时,注意辨别

本月早些时候,我探讨了ZeroMQ以及它如何证明是构建快速,高吞吐量和可扩展的分布式系统的有前途的解决方案。尽管ZeroMQ很好地解决了这类问题,但它也不是没有缺点。它的创造者试图通过精神继承者Crossroads I/Onanomsg来纠正这些缺点。

现在已经不存在的Crossroads I/O是ZeroMQ的一个合适的分支,其真正的目的是围绕它建立一个可行的商业生态系统。然而,Nanomsg是ZeroMQ的“重新构想”——完全用C重写((作者解释了为什么他最初应该用C而不是c++编写ZeroMQ .))。它建立在ZeroMQ坚如磐石的性能特点,同时提供了几个重要的改进,内部和外部。它还试图解决ZeroMQ经常出现的许多奇怪行为。今天,我将介绍nanomsg与其前身的区别,并以服务发现的形式为其实现一个用例。

Nanomsg vs. ZeroMQ

人们对ZeroMQ的一个常见抱怨是,它没有为新的传输协议提供API,这实际上限制了您只能使用TCP、PGM、IPC和ITC。Nanomsg通过为传输和消息传递协议提供可插拔接口来解决这个问题。这意味着支持新的传输(例如WebSockets)和新的消息传递模式,超出了PUB/SUB、REQ/REP等标准集。

Nanomsg是完全posix兼容的,给它一个更干净的API和更好的兼容性。套接字不再表示为void指针并绑定到上下文—只需初始化一个新的套接字并在一步中开始使用它。使用ZeroMQ,上下文在内部充当全局状态的存储机制,对用户来说,充当I/O线程池。这个概念已经完全从nanomsg中删除了。

除了POSIX兼容性之外,nanomsg还希望在API和协议级别上实现互操作,这将允许它成为ZeroMQ和其他实现ZMTP/1.0和ZMTP/2.0的库的临时替代品,或者与之互操作。然而,它尚未达到完全平等。

ZeroMQ在架构上有一个根本性的缺陷。其套接字不是线程安全的。就其本身而言,这是没有问题的,事实上,在某些情况下是有益的。通过在自己的线程中隔离每个对象,消除了对信号量和互斥锁的需求。线程不会相互接触,而是通过消息传递实现并发性。这种模式对于工作线程管理的对象工作得很好,但是当对象在用户线程中管理时就失效了。如果线程正在执行另一个任务,则该对象被阻塞。Nanomsg取消了对象和线程之间的一对一关系。交互不是依赖于消息传递,而是建模为一组状态机。因此,nanomsg socket是线程安全的。

Nanomsg有许多其他的内部优化,旨在提高内存和CPU效率。ZeroMQ使用一个简单的trie结构来存储和匹配PUB/SUB订阅,它对于低于10,000的订阅表现良好,但对于超过10,000的订阅很快就变得不合理了。Nanomsg使用一种称为基数树的空间优化树来存储订阅。与它的前身不同,该库还提供了一个真正的零拷贝 API,通过允许内存在完全绕过CPU的情况下从一台机器复制到另一台机器,大大提高了性能。

ZeroMQ使用轮询算法实现负载均衡。虽然它提供了平等的工作分配,但它有其局限性。假设您有两个数据中心,一个在纽约,一个在伦敦,每个站点都托管“foo”服务的实例。理想情况下,从纽约发出的请求不应该被路由到伦敦数据中心,反之亦然。不幸的是,使用ZeroMQ的循环平衡,这是完全可能的。nanomsg提供的面向用户的新特性之一是出站流量的优先路由。我们通过为同样托管在纽约的应用程序分配优先级为1来避免这种延迟问题。然后将优先级2分配给托管在伦敦的foo服务,以便在纽约的foo不可用的情况下进行故障转移。

此外,nanomsg还提供了一个命令行工具,用于与称为[nanocat]的系统进行交互(http://nanomsg.org/v0.2/nanocat.1.html)。该工具允许您通过nanomsg套接字发送和接收数据,这对于调试和健康检查非常有用。

扩展的多种协议

也许最有趣的是nanomsg对ZeroMQ的哲学背离。nanomsg没有充当一个通用的网络库,而是打算通过实现所谓的“可扩展协议”,为构建扩展性和高性能的分布式系统提供“乐高积木”。这些可扩展协议是通信模式,是网络堆栈传输层之上的抽象。这些协议彼此完全分离,这样每个协议都可以包含一个定义良好的分布式算法。正如nanomsg的作者Martin Sustrik所述,其目的是通过IETF对协议规范进行标准化。

Nanomsg目前定义了六种不同的可扩展性协议:PAIR、REQREP、PIPELINE、BUS、PUBSUB和SURVEY。

PAIR(点对点双向通信)

PAIR在两个端点之间实现简单的一对一、双向通信。两个节点之间可以相互发送消息。

REQREP(客户端请求,服务器响应)

REQREP协议定义了一种模式,用于构建无状态服务来处理用户请求。客户端发送请求,服务器接收请求,进行一些处理,然后返回响应。

PIPELINE (单向管道通信)

PIPELINE提供单向数据流,这对于创建负载平衡的处理管道非常有用。生产者节点提交分布在消费者节点之间的工作。

BUS(多对多的公共通信)

总线允许从每个对等点发送的消息传递到组中的每个其他对等点。

PUBSUB (话题广播通信)

PUBSUB允许发布者向零个或多个订阅者多播消息。可以连接到多个发布者的订阅者可以订阅特定的主题,从而允许他们只接收与他们相关的消息。

SURVEY (小组提问通信)

最后一种协议是SURVEY,我将通过实现一个用例来进一步研究它。SURVEY模式类似于PUBSUB,因为来自一个节点的消息被广播到整个组,但不同之处在于组中的每个节点都对消息进行“响应”。这打开了各种各样的应用程序,因为它允许您一次快速轻松地查询大量系统的状态。被调查者必须在调查者设定的时间窗口内作出回应。

实现服务发现

正如我所指出的,SURVEY协议有很多有趣的应用程序。例如:

-这条记录有什么资料?
这种产品你们的报价是多少?
-谁能处理这个请求?

为了继续探索它,我将实现一个基本的服务发现模式。服务发现是一个非常适合SURVEY的简单问题:那里有什么服务?我们的解决办法是定期提交问题。当服务启动时,它们将连接到我们的服务发现系统,以便识别自己。我们可以调整参数,比如我们调查小组的频率,以确保我们有一个准确的服务列表,以及服务需要多长时间做出响应。

这很好,因为1)发现系统不需要知道有哪些服务——它只是盲目地提交调查——2)当服务启动时,它将被发现,如果它死亡,它将“未被发现”。

下面是ServiceDiscovery类:

nanomsg_service_discovery.py hosted with ❤ by GitHub

from collections import defaultdict
import random

from nanomsg import NanoMsgAPIError
from nanomsg import Socket
from nanomsg import SURVEYOR
from nanomsg import SURVEYOR_DEADLINE

class ServiceDiscovery(object):

    def __init__(self, port, deadline=5000):
        self.socket = Socket(SURVEYOR)
        self.port = port
        self.deadline = deadline
        self.services = defaultdict(set)

    def bind(self):
        self.socket.bind('tcp://*:%s' % self.port)
        self.socket.set_int_option(SURVEYOR, SURVEYOR_DEADLINE, self.deadline)

    def discover(self):
        if not self.socket.is_open():
            return self.services

        self.services = defaultdict(set)
        self.socket.send('service query')

        while True:
            try:
                response = self.socket.recv()
            except NanoMsgAPIError:
                break

            service, address = response.split('|')
            self.services[service].add(address)

        return self.services

    def resolve(self, service):
        providers = self.services[service]

        if not providers:
            return None

        return random.choice(tuple(providers))

    def close(self):
        self.socket.close()

发现方法提交调查,然后收集响应。注意,我们构造了一个SURVEYOR套接字并在其上设置了SURVEYOR_DEADLINE选项。这个截止日期是从提交调查到必须接收响应的毫秒数——根据您的网络拓扑相应地调整它。一旦达到调查截止日期,就会引发NanoMsgAPIError并中断循环。resolve方法将获取一个服务的名称,并从我们发现的服务中随机选择一个可用的提供者。

然后,我们可以用一个定期运行discover的守护进程包装ServiceDiscovery。

nanomsg_discovery_daemon.py hosted with ❤ by GitHub

import os
import time


from service_discovery import ServiceDiscovery


DEFAULT_PORT = 5555
DEFAULT_DEADLINE = 5000
DEFAULT_INTERVAL = 2000


def start_discovery(port, deadline, interval):
    discovery = ServiceDiscovery(port, deadline=deadline)
    discovery.bind()


    print 'Starting service discovery [port: %s, deadline: %s, interval: %s]' \
        % (port, deadline, interval)


    while True:
        print discovery.discover()
        time.sleep(interval / 1000)


if __name__ == '__main__':
    port = int(os.environ.get('PORT', DEFAULT_PORT))
    deadline = int(os.environ.get('DEADLINE', DEFAULT_DEADLINE))
    interval = int(os.environ.get('INTERVAL', DEFAULT_INTERVAL))


    start_discovery(port, deadline, interval)

发现参数是通过注入Docker容器的环境变量来配置的。

服务在启动时必须连接到发现系统。当他们收到调查时,他们应该通过确定他们提供的服务和服务所在的位置来响应。一个这样的服务可能如下所示:

nanomsg_service.py hosted with ❤ by GitHub

import os
from threading import Thread


from nanomsg import REP
from nanomsg import RESPONDENT
from nanomsg import Socket


DEFAULT_DISCOVERY_HOST = 'localhost'
DEFAULT_DISCOVERY_PORT = 5555
DEFAULT_SERVICE_NAME = 'foo'
DEFAULT_SERVICE_PROTOCOL = 'tcp'
DEFAULT_SERVICE_HOST = 'localhost'
DEFAULT_SERVICE_PORT = 9000


def register_service(service_name, service_address, discovery_host,
                     discovery_port):
    socket = Socket(RESPONDENT)
    socket.connect('tcp://%s:%s' % (discovery_host, discovery_port))


    print 'Starting service registration [service: %s %s, discovery: %s:%s]' \
        % (service_name, service_address, discovery_host, discovery_port)


    while True:
        message = socket.recv()
        if message == 'service query':
            socket.send('%s|%s' % (service_name, service_address))


def start_service(service_name, service_protocol, service_port):
    socket = Socket(REP)
    socket.bind('%s://*:%s' % (service_protocol, service_port))


    print 'Starting service %s' % service_name


    while True:
        request = socket.recv()
        print 'Request: %s' % request
        socket.send('The answer is 42')


if __name__ == '__main__':
    discovery_host = os.environ.get('DISCOVERY_HOST', DEFAULT_DISCOVERY_HOST)
    discovery_port = os.environ.get('DISCOVERY_PORT', DEFAULT_DISCOVERY_PORT)
    service_name = os.environ.get('SERVICE_NAME', DEFAULT_SERVICE_NAME)
    service_host = os.environ.get('SERVICE_HOST', DEFAULT_SERVICE_HOST)
    service_port = os.environ.get('SERVICE_PORT', DEFAULT_SERVICE_PORT)
    service_protocol = os.environ.get('SERVICE_PROTOCOL',
                                      DEFAULT_SERVICE_PROTOCOL)


    service_address = '%s://%s:%s' % (service_protocol, service_host,
                                      service_port)


    Thread(target=register_service, args=(service_name, service_address,
                                          discovery_host,
                                          discovery_port)).start()


    start_service(service_name, service_protocol, service_port)

同样,我们通过在容器上设置的环境变量来配置参数。请注意,我们使用应答套接字连接到发现系统,然后该套接字使用服务名称和地址响应服务查询。服务本身使用一个REP套接字,该套接字简单地用“答案是42”来响应任何请求,但它可以采用任意数量的形式,如HTTP、原始套接字等。

这个例子的完整代码,包括Dockerfiles,可以在GitHub上找到。

选择Nanomsg还是ZeroMQ?

基于nanomsg在ZeroMQ之上所做的所有改进,您可能想知道为什么要使用后者。Nanomsg仍然相对年轻。虽然它有许多语言绑定,但它还没有达到ZeroMQ的成熟程度,ZeroMQ有一个蓬勃发展的开发社区。ZeroMQ有大量的文档和其他资源来帮助开发人员使用这个库,而nanomsg只有很少的资源。在Google上快速搜索一下,你就会知道它们之间的区别(ZeroMQ有大约50万个结果,而nanomsg有13500个结果)。

也就是说,nanomsg的改进,特别是其可伸缩性协议使其非常吸引人。ZeroMQ暴露的许多奇怪行为已经完全解决,或者至少减轻了。它正在积极开发,并迅速获得越来越多的关注。从技术上讲,nanomsg自3月份以来一直处于测试阶段,但如果它还没有投入生产,那么它已经开始看起来可以投入生产了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空名_Noname

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值