队列ZeroMQ的使用大纲

举报
码乐 发表于 2025/06/18 07:02:09 2025/06/18
【摘要】 1 简介本文主要介绍了ZeroMQ的核心概念、使用方法和编程实践。ZeroMQ是一个高性能异步消息库,支持多种通信模式,如请求-响应、发布-订阅、流水线等。文章详细阐述了ZeroMQ套接字的生命周期、核心模式、消息处理机制以及在实际编程中的注意事项,包括上下文管理、套接字选项设置、消息发送与接收等。此外,还探讨了ZeroMQ的可靠性问题及多线程环境下的最佳实践。 2 内容大纲ZeroMQ队...

1 简介

本文主要介绍了ZeroMQ的核心概念、使用方法和编程实践。ZeroMQ是一个高性能异步消息库,支持多种通信模式,如请求-响应、发布-订阅、流水线等。

文章详细阐述了ZeroMQ套接字的生命周期、核心模式、消息处理机制以及在实际编程中的注意事项,包括上下文管理、套接字选项设置、消息发送与接收等。此外,还探讨了ZeroMQ的可靠性问题及多线程环境下的最佳实践。

2 内容大纲

  1. ZeroMQ队列的生命周期

创建和销毁套接字(zmq_socket(), zmq_close())

设置套接字选项(zmq_setsockopt(), zmq_getsockopt())

建立网络连接(zmq_bind(), zmq_connect())

消息传递(zmq_msg_send(), zmq_msg_recv())

服务器与客户端的角色

服务器通过zmq_bind()绑定到固定端点,客户端通过zmq_connect()连接

ZeroMQ套接字与TCP套接字的区别

基于消息(而非字节流)、后台I/O线程、内置路由机制

  1. ZeroMQ的核心模式
    内置通信模式

请求-响应(远程调用与任务分发)

发布-订阅(数据分发)

流水线(并行任务分发与收集)

排他对(进程内线程通信)

支持的套接字组合

如PUB-SUB、REQ-REP、PUSH-PULL等

消息处理API

初始化、发送、接收、释放消息的函数(如zmq_msg_init(), zmq_msg_send())

代理功能

zmq_proxy()实现消息转发与中介

  1. 发布-订阅模式
    订阅机制

通过ZMQ_SUBSCRIBE设置订阅前缀

潜在问题与局限性

消息丢失(如订阅者启动延迟、网络故障)

无速率控制、无连接状态反馈

可靠性改进建议

确保订阅者先启动、绑定方向优化

PUSH-PULL模式

单向任务分发,轮询选择接收者

  1. ZeroMQ编程实践
    开发建议

逐步学习、代码整洁、模块化设计、即时测试

上下文管理

单进程单上下文(zmq_ctx_new()与zmq_ctx_destroy())

多进程场景下的上下文处理

资源清理

关闭套接字、设置LINGER值、避免内存泄漏

多线程注意事项

避免共享套接字、优雅退出(关闭套接字后销毁上下文)

3 一个典型ZeroMQ生命周期

  • 1 创建和销毁套接字

它们共同形成插座生命的业力循环(见 zmq_socket(), zmq_close())。

  • 2 设置套接字的选项

并在必要时检查套接字来配置套接字(参见 zmq_setsockopt(), zmq_getsockopt())。

  • 3 通过创建与套接字之间的 ZeroMQ 连接

将套接字插入网络拓扑中(参见 zmq_bind(), zmq_connect())。

  • 4 使用套接字通过在套接字上写入和接收消息

承载数据参见 zmq_msg_send(), zmq_msg_recv()。

要在两个节点之间创建连接,请在一个节点中使用 zmq_bind(),在另一个节点中使用 zmq_connect()。
作为一般经验法则,执行zmq_bind()的节点是“服务器”,位于已知的网络地址上,
而执行zmq_connect()的节点是“客户端”,具有未知或任意的网络地址。

因此,我们说我们“将套接字绑定到端点”和“将套接字连接到端点”,端点是众所周知的网络服务地址。

服务器节点可以绑定到许多端点(即协议和地址的组合),并且可以使用单个套接字来执行此操作。这意味着它将接受不同传输的连接:

	zmq_bind (socket, "tcp://*:5555");
	zmq_bind (socket, "tcp://*:9999");
	zmq_bind (socket, "inproc://somename");

对于大多数链接,不允许重复绑定,虽然ipc 传输确实允许一个进程绑定到前一个进程已使用的端点。
它旨在允许进程在崩溃后恢复。
通常应该将“服务器”视为拓扑中绑定到或多或少固定端点的静态部分,而将“客户端”视为来来去去并连接到这些端点的动态部分。然后,围绕此模型设计应用程序。这样,它“工作”的机会要好得多。

  • 发送和接收消息

若要发送和接收消息,请使用 zmq_msg_send() 和 zmq_msg_recv() 方法。

  • 区别 TCP套接字 VS ZeroMQ套接字

TCP 套接字和 ZeroMQ 套接字在处理数据时的主要区别:

ZeroMQ 套接字携带消息,如 UDP,而不是像 TCP 那样携带字节流。

ZeroMQ 消息是长度指定的二进制数据。我们很快就会来消息;他们的设计针对性能进行了优化,因此有点棘手。

ZeroMQ 套接字在后台线程中执行 I/O。

这意味着消息到达本地输入队列,并从本地输出队列发送,无论您的应用程序忙于做什么。

根据套接字类型,ZeroMQ 套接字内置了一到一到N的路由行为。

4 模式

  • ZeroMQ的核心模式

    libzmq。readthedocs。io /en /latest /zmq_socket.html

内置的核心 ZeroMQ 模式包括:

	请求-答复,将一组客户端连接到一组服务。这是一种远程过程调用和任务分发模式。

	Pub-sub,它将一组发布者连接到一组订阅者。这是一种数据分布模式。

	流水线,它以扇出/扇入模式连接节点,该模式可以有多个步骤和循环。这是一种并行任务分发和收集模式。

	专用对,专门连接两个插座。这是一种在进程中连接两个线程的模式,不要与“普通”套接字对混淆。
  • 排他性对模式

    连接绑定对有效的套接字组合(任何一方都可以绑定):

        PUB 和 SUB
        REQ 和 REP
        REQ 和 ROUTER(注意,REQ 会插入一个额外的空帧)
        DEALER 和 REP(注意,REP 假设帧为 null)
        代理和路由
        代理和代理
        路由和路由
        PUSH/PULL 推拉
        配对和配对
    
  • 初始化消息常用的函数

      使用的 zmq_send() 和 zmq_recv() 方法是简单的单行方法。我们将经常使用它们,但 zmq_recv() 不擅长处理任意消息大小:它会将消息截断为您提供的任何缓冲区大小。
      
      因此,还有第二个 API 可以处理zmq_msg_t结构,具有更丰富但更困难的 API:
    
      初始化消息: zmq_msg_init()、 zmq_msg_init_size()、 zmq_msg_init_data()。
      发送和接收消息: zmq_msg_send()、 zmq_msg_recv()。
      发布消息: zmq_msg_close()。
      访问消息内容: zmq_msg_data()、 zmq_msg_size()、 zmq_msg_more()”。
      使用消息属性: zmq_msg_get()、 zmq_msg_set()。
      消息操作: zmq_msg_copy()、 zmq_msg_move()。
    
  • ZeroMQ 内置代理功能

事实证明,上一节的 rrbroker 中的核心循环非常有用,并且可重用。它让我们可以毫不费力地构建 pub-sub 转发器、共享队列和其他小中介。ZeroMQ 将其包含在一个方法中, zmq_proxy():

			zmq_proxy (frontend, backend, capture);

zmq_proxy() 方法有三个参数:一个桥接在一起的前端和后端套接字,以及一个将所有消息发送到的捕获套接字

5 发布和订阅

在发布者处启用HWM,如果订阅者检测到间隙(编号不连续),订阅者就启动自杀蜗牛操作。

在 SUB 套接字上,使用 zmq_setsockopt() 和 ZMQ_SUBSCRIBE 设置订阅,否则不会收到消息。因为你按前缀订阅消息,所以如果你订阅“”(空订阅),你将获得一切

如果在 PUB 套接字开始发送数据后启动 SUB 套接字(即与 PUB 套接字建立连接),则将丢失它在建立连接之前发布的任何内容。

如果这是一个问题,请设置您的体系结构,以便 SUB 套接字首先启动,然后 PUB 套接字开始发布。

即使同步 SUB 和 PUB 套接字,仍可能丢失消息。这是因为在实际创建连接之前不会创建内部队列。

如果您可以切换绑定/连接方向,以便 SUB 套接字绑定,并且 PUB 套接字连接,您可能会发现它更符合您的预期。

PUB 将每条消息发送给“all of many”,而 PUSH 和 DEALER 将消息轮换到“one of many”。
不能简单地用 PUB 替换 PUSH,反之亦然,并希望事情会奏效.

pub-sub 的目标是可扩展性。这意味着大量数据需要快速发送给许多收件人。
如果您需要每秒向数千个点发送数百万条消息,那么您会比每秒需要向少数收件人发送几条消息更欣赏 pub-sub。

发布者无法判断订阅者何时成功连接,无论是在初始连接上,还是在网络故障后的重新连接上。

订阅者不能告诉发布者任何允许发布者控制他们发送消息的速率的信息。
发布者只有一个设置,即全速,订阅者必须跟上或丢失消息。

发布者无法判断订阅者何时因进程崩溃、网络中断等原因而消失。

不利的一面是,如果我们想做可靠的组播,我们实际上需要所有这些。当订阅者连接时,当网络故障发生时,或者当订阅者或网络无法跟上发布者时,ZeroMQ pub-sub 模式将任意丢失消息

发布-订阅模式的问题:

	订阅者加入较晚,因此他们错过了服务器已发送的消息。
	订阅者获取消息的速度可能太慢,因此队列会堆积起来,然后溢出。
	订阅者在离开时可能会丢失和丢失消息。
	订阅者可能会崩溃并重新启动,并丢失他们已经收到的任何数据。
	网络可能会过载并丢弃数据(特别是对于 PGM)。
	网络速度可能会变得太慢,因此发布者端队列会溢出,发布者会崩溃。
  • PUSH-PULL

跟PUB-SUB模式一样,PUSH-PULL也只能实现消息的单向传输,但是pusher会采用轮询法选择一个worker把消息发送给它,其他worker都收不到这条消息,所以PUSH-PULL模式常用来做任务的分发。

6 使用 ZeroMQ 编程

看过一些例子后,你一定迫不及待地想开始在某些应用中使用 ZeroMQ。在你开始之前,深吸一口气,冷静下来,并思考一些基本的建议,这些建议将为你节省很多压力和困惑。

逐步学习 ZeroMQ。它只是一个简单的 API,但它隐藏了一个充满可能性的世界。慢慢来,掌握每一个可能性。

写出漂亮的代码。丑陋的代码隐藏了问题,使其他人难以帮助你。你可能会习惯无意义的变量名称,但阅读你的代码的人不会。

使用实词名称,而不是“我太粗心了,无法告诉你这个变量的真正用途”。使用一致的缩进和干净的布局。写出漂亮的代码,你的世界会更舒适。

在你制作时测试你制作的东西。当你的程序不起作用时,你应该知道哪五行是罪魁祸首。当你做 ZeroMQ 魔术时尤其如此,这在你尝试的前几次是行不通的。

当你发现事情没有按预期工作时,把你的代码分解成几个部分,测试每一个,看看哪一个不起作用。ZeroMQ 允许您制作本质上模块化的代码;利用它来发挥你的优势。

根据需要进行抽象(类、方法等)。如果你复制/粘贴了大量的代码,你也会复制/粘贴错误。

  • 获取正确的上下文

ZeroMQ 应用程序总是从创建上下文开始,然后使用它来创建套接字。在 C 中,它是 zmq_ctx_new() 调用。您应该在流程中创建和使用一个上下文。

从技术上讲,上下文是单个进程中所有套接字的容器,并充当 inproc 套接字的传输,这是在一个进程中连接线程的最快方式。

如果在运行时,一个进程有两个上下文,则它们就像单独的 ZeroMQ 实例。如果这是你想要的,好吧,但除此之外,请记住:

在进程开始时调用 zmq_ctx_new() 一次,在进程结束时调用 zmq_ctx_destroy() 一次。

如果您使用的是 fork() 系统调用,请在 fork 之后和子进程代码的开头执行 zmq_ctx_new()。一般来说,你想在子级中做有趣的(ZeroMQ)事情,在父级中做无聊的进程管理。

  • 干净的退出

优雅的程序员与优雅的杀手有着相同的座右铭:完成工作时总是清理干净。当您在 Python 等语言中使用 ZeroMQ 时,内容会自动释放。

但是在使用 C 语言时,你必须在完成对象后小心地释放它们,否则你会得到内存泄漏、不稳定的应用程序和通常的恶业。

内存泄漏是一回事,但 ZeroMQ 对退出应用程序的方式非常挑剔。原因是技术上的和痛苦的,但结果是,如果你让任何套接字保持打开状态,zmq_ctx_destroy() 函数将永远挂起。

即使关闭了所有套接字,如果存在待处理的连接或发送, zmq_ctx_destroy() 将默认等待,除非关闭这些套接字之前将这些套接字的 LINGER 设置为零。

我们需要关注的 ZeroMQ 对象是消息、套接字和上下文。幸运的是,它非常简单,至少在简单的程序中是这样:

尽可能使用 zmq_send() 和 zmq_recv(),因为它避免了使用zmq_msg_t对象的需要。

如果确实使用了 zmq_msg_recv(),请务必在使用完后立即通过调用 zmq_msg_close() 释放收到的消息。

如果您要打开和关闭大量套接字,这可能表明您需要重新设计应用程序。在某些情况下,在销毁上下文之前,套接字句柄不会被释放。

退出程序时,关闭套接字,然后调用 zmq_ctx_destroy()。这会破坏上下文。

至少对于C语言开发来说,情况是这样。在具有自动对象销毁功能的语言中,当您离开作用域时,套接字和上下文将被销毁。如果您使用异常,则必须在类似“最终”块之类的东西中进行清理,就像对任何资源一样。

如果正在做多线程工作,它会变得比这更复杂。我们将在下一章中讨论多线程,但是由于你们中的一些人会不顾警告,尝试在安全行走之前运行,所以下面是在多线程 ZeroMQ 应用程序中进行干净退出的快速而肮脏的指南。

首先,不要尝试使用来自多个线程的同一套接字。请不要解释为什么你认为这会非常有趣,只是请不要这样做。接下来,您需要关闭每个具有持续请求的套接字。

正确的方法是设置一个较低的 LINGER 值(1 秒),然后关闭套接字。如果你的语言绑定在你破坏上下文时没有自动为你执行此操作,我建议发送一个补丁。

  • 销毁上下文

这将导致任何阻塞接收或轮询或附加线程(即共享相同上下文)返回错误。

捕获该错误,然后设置 linger on,关闭该线程中的套接字,然后退出。不要两次销毁相同的上下文。

主线程中的zmq_ctx_destroy将阻塞,直到它知道的所有套接字都安全关闭。

7 小结

本文介绍ZeroMQ的核心优势:高性能、灵活模式、异步I/O,以及编码时的常见陷阱:消息丢失、上下文生命周期管理。

最佳实践:合理选择模式、重视资源清理、多线程安全设计

【版权声明】本文为华为云社区用户翻译文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容, 举报邮箱:cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。