锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

zmq 中文文档

时间:2023-05-03 02:37:01 4x1电力变送器

?MQ - The Guide

[Table of Contents](javascript:??

By Pieter Hintjens, CEO of iMatix

Please use the issue tracker for all comments and errata. This version covers the latest stable release of ZeroMQ (3.2). If you are using older versions of ZeroMQ then some of the examples and explanations won’t be accurate.

The Guide is originally in C, but also in PHP, Java, Python, Lua, and Haxe. We’ve also translated most of the examples into C , C#, CL, Delphi, Erlang, F#, Felix, Haskell, Objective-C, Ruby, Ada, Basic, Clojure, Go, Haxe, Node.js, ooc, Perl, and Scala.

ZeroMQ 简介

ZeroMQ(也称为?MQ, 0mq或zmq)看起来像嵌入式网络库(an embeddable networking library),但它就像一个并发框架。它为您提供scoket,可跨过程内、进程间,TCP各种传输和传输原子消息,如多播。您可以N-to-N的连接scokets 诸如 fan-out, pub-sub, task distribution,和request-reply等模式。其速度足以成为集群产品的组织(fabric)。它的异步I/O该模型为您提供可伸缩的多核应用程序,构建异步信息处理任务。它有很多语言api,它可以在大多数操作系统上运行。ZeroMQ来自iMatix,是LGPLv3级开源。

它如何开始

We took a normal TCP socket, injected it with a mix of radioactive isotopes stolen from a secret Soviet atomic research project, bombarded it with 1950-era cosmic rays, and put it into the hands of a drug-addled comic book author with a badly-disguised fetish for bulging muscles clad in spandex. Yes, ZeroMQ sockets are the world-saving superheroes of the networking world.

Figure 1 - A terrible accident…

[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-Pcgql7mB-1611294599074)(https://github.com/imatix/zguide/raw/master/images/fig1.png)]

Zero的含义

ZeroMQ的?权衡。一方面,这个奇怪的名字减少了ZeroMQ在谷歌和Twitter人气。另一方面,它惹恼了我们丹麦人写一些东西,比如?MG r?tfl”,并且“?看作(looking)零不好笑!地中海R?dgr?d fl?de !这显然是一种侮辱,意思是愿你的邻居是格伦德尔的直系后裔!这似乎是一笔公平的交易。

最初ZeroMQ0表示零代理,零延迟(尽可能)。从那时起,它开始包含不同的目标:零管理、零成本、零浪费。更一般地说,零是指渗透到项目中的极简主义文化。我们通过消除复杂性而不是披露新功能来增加功能。

Audience

这本书是专业程序员写的,他们想学习如何制作大规模的分布式软件,将主导未来的计算。我们假设你可以阅读C代码,因为这里的大多数例子都是用C编写的,即使ZeroMQ它被用于许多语言。我们假设你关心规模,因为ZeroMQ首先解决了这个问题。假设你需要以尽可能少的成本得到尽可能好的结果,否则你就不会欣赏了ZeroMQ权衡。除了基本的背景知识,我们还将介绍使用ZeroMQ所需的网络和分布式计算的所有概念。

致谢

Thanks to Andy Oram for making the O’Reilly book happen, and editing this text.

Thanks to Bill Desmarais, Brian Dorsey, Daniel Lin, Eric Desgranges, Gonzalo Diethelm, Guido Goldstein, Hunter Ford, Kamil Shakirov, Martin Sustrik, Mike Castleman, Naveen Chawla, Nicola Peduzzi, Oliver Smith, Olivier Chamoux, Peter Alexander, Pierre Rouleau, Randy Dryburgh, John Unwin, Alex Thomas, Mihail Minkov, Jeremy Avnet, Michael Compton, Kamil Kisiel, Mark Kharitonov, Guillaume Aubert, Ian Barber, Mike Sheridan, Faruk Akgul, Oleg Sidorov, Lev Givon, Allister MacLeod, Alexander D’Archangel, Andreas Hoelzlwimmer, Han Holl, Robert G. Jakabosky, Felipe Cruz, Marcus McCurdy, Mikhail Kulemin, Dr. Gerg? érdi, Pavel Zhukov, Alexander Else, Giovanni Ruggiero, Rick “Technoweenie”, Daniel Lundin, Dave Hoover, Simon Jefford, Benjamin Peterson, Justin Case, Devon Weller, Richard Smith, Alexander Morland, Wadim Grasza, Michael Jakl, Uwe Dauernheim, Sebastian Nowicki, Simone Deponti, Aaron Raddon, Dan Colish, Markus Schirp, Benoit Larroque, Jonathan Palardy, Isaiah Peng, Arkadiusz Orzechowski, Umut Aydin, Matthew Horsfall, Jeremy W. Sherman, Eric Pugh, Tyler Sellon, John E. Vincent, Pavel Mitin, Min RK, Igor Wiedler, Olof ?kesson, Patrick Lucas, Heow Goodman, Senthil Palanisami, John Gallagher, Tomas Roos, Stephen McQuay, Erik Allik, Arnaud Cogoluègnes, Rob Gagnon, Dan Williams, Edward Smith, James Tucker, Kristian Kristensen, Vadim Shalts, Martin Trojer, Tom van Leeuwen, Hiten Pandya, Harm Aarts, Marc Harter, Iskren Ivov Chernev, Jay Han, Sonia Hamilton, Nathan Stocks, Naveen Palli, and Zed Shaw for their contributions to this work.

Chapter 1 - Basics

Fixing the World

如何解释ZeroMQ?我们中的一些人从它所做的所有美好的事情开始。它的sockets在steroids上。就像有路由的邮箱。它很快! 当一切都变得明显时,别人试图分享他们的顿悟时刻,ap-pow-kaboom satori paradigm-shift moment。事情变得简单了。复杂性消失。它可以拓宽思维。*其他人试图通过比较来解释。它更小,更简单,但看起来仍然很熟悉。就我个人而言,我想记住为什么我们要制作它ZeroMQ,因为这很可能是你读者今天还在做的事情。

编程是一门伪装成艺术的科学,因为我们大多数人不懂软件的物理原理,很少有人教编程。
软件的物理不是算法、数据结构、语言和抽象。这些只是我们制造、使用和丢弃的工具。软件的真正物理特性是人的物理特性——具体来说,它是我们在复杂性方面的局限性,也是我们解决大问题的愿望。这是编程的科学:制作人们能理解和使用的积木,然后一起工作来解决最大的问题。

我们生活在一个互联的世界里,现代软件必须在这个世界上导航。因此,未来最大的解决方案构建模块是相互连接和大规模平行的。仅仅让代码强大而安静是不够的。代码必须与代码对话。代码必须健谈,善于沟通,关系良好。代码必须像人脑一样运行,数万亿个神经元相互发送信息,这是一个没有中央控制和单点故障的大型并行网络,但可以解决极其困难的问题。代码的来看起来像人脑,这并非偶然,因为每个网络的端点,在某种程度上,都是人脑。

如果您使用线程、协议或网络做过任何工作,您就会发现这几乎是不可能的。这是一个梦。当您开始处理实际的情况时,即使跨几个scoket连接几个程序也是非常麻烦的。数万亿吗?其代价将是难以想象的。连接计算机是如此困难,以至于软件和服务要做这是一项数十亿美元的业务。

所以我们生活在一个线路比我们使用它的能力超前数年的世界里。上世纪80年代,我们经历了一场软件危机。当时,弗雷德•布鲁克斯(Fred Brooks)等顶尖软件工程师相信,没有什么“灵丹妙药”能“保证生产率、可靠性或简单性哪怕提高一个数量级”。

布鲁克斯错过了免费和开源软件,正是这些软件解决了这场危机,使我们能够有效地共享知识。今天,我们面临着另一场软件危机,但我们很少谈论它。只有最大、最富有的公司才有能力创建连接的应用程序。有云,但它是私有的。我们的数据和知识正在从个人电脑上消失,变成我们无法访问、无法与之竞争的云。谁拥有我们的社交网络?这就像是反过来的大型机- pc革命。

我们可以把政治哲学留给另一本书。关键是,互联网提供了大量的潜在连接代码,现实情况是,对于大多数人来说,这是难以企及的,所以巨大而有趣的问题(在健康、教育、经济、交通、等等)仍然没有解决,因为没有办法连接代码,因此没有办法去连接可以一起工作的大脑来解决这些问题。

已经有很多尝试来解决连接代码的挑战。有数以千计的IETF规范,每个规范都解决了这个难题的一部分。对于应用程序开发人员来说,HTTP可能是一个简单到足以工作的解决方案,但是它鼓励开发人员和架构师从大服务器和thin,stupid的客户机的角度考虑问题,从而使问题变得更糟。

因此,今天人们仍然使用原始UDP和TCP、专有协议、HTTP和Websockets连接应用程序。它仍然痛苦、缓慢、难以扩展,而且本质上是集中的。分布式P2P架构主要是为了玩,而不是工作。有多少应用程序使用Skype或Bittorrent来交换数据?

这让我们回到编程科学。要改变世界,我们需要做两件事。第一,解决“如何在任何地方将任何代码连接到任何代码”的一般问题。第二,用最简单的模块来概括,让人们能够理解和使用。

这听起来简单得可笑。也许确实如此。这就是重点。

开始的前提

我们假设您至少使用了ZeroMQ的3.2版。我们假设您正在使用Linux机器或类似的东西。我们假设您可以或多或少地阅读C代码,因为这是示例的默认语言。我们假设,当我们编写像PUSH或SUBSCRIBE这样的常量时,您可以想象它们实际上被称为’ ZMQ_PUSH ‘或’ ZMQ_SUBSCRIBE '(如果编程语言需要的话)。

获取例子

The examples live in a public GitHub repository. The simplest way to get all the examples is to clone this repository:

git clone --depth=1 https://github.com/imatix/zguide.git

接下来,浏览examples子目录。你会通过语言找到例子。如果您使用的语言中缺少示例,建议您提交翻译。正是由于许多人的努力,这篇文章才变得如此有用。所有示例都是根据MIT/X11授权的。

有求必应

让我们从一些代码开始。当然,我们从Hello World的例子开始。我们将创建一个客户机和一个服务器。客户端向服务器发送“Hello”,服务器以“World”作为响应。这是C语言的服务器,它在端口5555上打开一个ZeroMQ scoket,读取请求,然后用“World”对每个请求进行响应:

[hwserver: Hello World server in C](javascript:😉

C++ | C# | Clojure | CL | Delphi | Erlang | F# | Felix | Go | Haskell | Haxe | Java | Lua | Node.js | Objective-C| Perl | PHP | Python | Q | Racket | Ruby | Scala | Tcl | Ada | Basic | ooc

图2 - Request-Reply

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jzXKt26r-1611294599076)(https://github.com/imatix/zguide/raw/master/images/fig2.png)]

REQ-REP套接字对是同步的。客户机在循环中发出zmq_send()然后zmq_recv(),在循环中(或者只需要执行一次)。执行任何其他序列(例如,在一行中发送两条消息)都会导致send或recv调用返回的代码为-1。类似地,服务按这个顺序发出zmq_recv()和zmq_send(),只要它需要。

ZeroMQ使用C作为参考语言,这是我们在示例中使用的主要语言。如果您正在在线阅读本文,下面的示例链接将带您到其他编程语言的翻译。让我们在c++中比较相同的服务器:

//Hello World server in C++
//Binds REP socket to tcp://\*:5555
//Expects "Hello" from client, replies with "World"
//
#include 
#include 
#include 
#ifndef _WIN32
#include 
#else
#include 

#define sleep(n)    Sleep(n)
#endif

int main () {
`    `*//  Prepare our context and socket*
`    `zmq::context_t context (1);
`    `zmq::socket_t socket (context, ZMQ_REP);
`    `socket.bind ("tcp://*:5555");

`    `**while** (true) {
`        `zmq::message_t request;

`        `*//  Wait for next request from client*
`        `socket.recv (&request);
`        `std::cout << "Received Hello" << std::endl;

`        `*//  Do some 'work'*
`        `sleep(1);

`        `*//  Send reply back to client*
`        `zmq::message_t reply (5);
`        `memcpy (reply.data (), "World", 5);
`        `socket.send (reply);
`    }`
`    `**return** 0;
}

hwserver.cpp: Hello World server

You can see that the ZeroMQ API is similar in C and C++. In a language like PHP or Java, we can hide even more and the code becomes even easier to read:

\*/*

$context = **new** ZMQContext(1);

*//  Socket to talk to clients*
$responder = **new** ZMQSocket($context, ZMQ::SOCKET_REP);
$responder->bind("tcp://*:5555");

**while** (**true**) {
`    `*//  Wait for next request from client*
`    `$request = $responder->recv();
`    `printf ("Received request: [%s]**\n**", $request);

`    `*//  Do some 'work'*
`    `sleep (1);

`    `*//  Send reply back to client*
`    `$responder->send("World");
}

hwserver.php: Hello World server

package guide;

//
//  Hello World server in Java
//  Binds REP socket to tcp://*:5555
//  Expects "Hello" from client, replies with "World"
//

import org.zeromq.SocketType;
import org.zeromq.ZMQ;
import org.zeromq.ZContext;

public class hwserver
{
    public static void main(String[] args) throws Exception
    {
        try (ZContext context = new ZContext()) {
            // Socket to talk to clients
            ZMQ.Socket socket = context.createSocket(SocketType.REP);
            socket.bind("tcp://*:5555");

            while (!Thread.currentThread().isInterrupted()) {
                byte[] reply = socket.recv(0);
                System.out.println(
                    "Received " + ": [" + new String(reply, ZMQ.CHARSET) + "]"
                );

                String response = "world";
                socket.send(response.getBytes(ZMQ.CHARSET), 0);

                Thread.sleep(1000); //  Do some 'work'
            }
        }
    }
}

hwserver.java: Hello World server

The server in other languages:

[hwserver: Hello World server in C](javascript:😉

C++ | C# | Clojure | CL | Delphi | Erlang | F# | Felix | Go | Haskell | Haxe | Java | Lua | Node.js | Objective-C| Perl | PHP | Python | Q | Racket | Ruby | Scala | Tcl | Ada | Basic | ooc

Here’s the client code:

[hwclient: Hello World client in C](javascript:😉

C++ | C# | Clojure | CL | Delphi | Erlang | F# | Felix | Go | Haskell | Haxe | Java | Lua | Node.js | Objective-C| Perl | PHP | Python | Q | Racket | Ruby | Scala | Tcl | Ada | Basic | ooc

这看起来太简单了,不太现实,但是正如我们已经知道的,ZeroMQ套接字具有超能力。您可以同时将数千个客户机扔到这个服务器上,它将继续愉快而快速地工作。有趣的是,先启动客户机,然后再启动服务器,看看它是如何工作的,然后再考虑一下这意味着什么。
让我们简要地解释一下这两个程序实际上在做什么。它们创建要使用的ZeroMQ context 和socket。不要担心这些词的意思。你会知道的。服务器将其REP (reply) socket 绑定到端口5555。服务器在一个循环中等待一个请求,每次都用一个响应来响应。客户机发送请求并从服务器读取响应。

如果您关闭服务器(Ctrl-C)并重新启动它,客户机将无法正常恢复。从进程崩溃中恢复并不那么容易。
创建一个可靠的request-reply流非常复杂,直到可靠的Request-Reply模式才会涉及它。

幕后发生了很多事情,但对我们程序员来说,重要的是代码有多短、多好,以及即使在重负载下也不会崩溃的频率。这是request-reply模式,可能是使用ZeroMQ的最简单方法。它映射到RPC和经典的 client/server模型。

需要对Strings小小的注意

除了以字节为单位的大小外,ZeroMQ对您发送的数据一无所知。这意味着您要负责安全地格式化它,以便应用程序能够读取它。为对象和复杂数据类型执行此操作是专门库(如协议缓冲区)的工作。但即使是字符串,你也要小心。

在C语言和其他一些语言中,字符串以空字节结束。我们可以发送一个字符串,如“HELLO”与额外的空字节:

zmq_send (requester, "Hello", 6, 0);

但是,如果您从另一种语言发送一个字符串,它可能不会包含那个空字节。例如,当我们用Python发送相同的字符串时,我们这样做:

socket.send ("Hello")

然后连接到线路上的是长度(对于较短的字符串是一个字节)和作为单个字符的字符串内容。

图 3 - ZeroMQ的 string

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hDem9Win-1611294599078)(https://github.com/imatix/zguide/raw/master/images/fig3.png)]

如果您从C程序中读取这段代码,您将得到一个看起来像字符串的东西,并且可能意外地表现得像字符串(如果幸运的话,这5个字节后面跟着一个无辜的潜伏的null),但是它不是一个正确的字符串。当您的客户机和服务器不同意字符串格式时,您将得到奇怪的结果。

当您在C语言中从ZeroMQ接收字符串数据时,您不能简单地相信它已经安全终止。每次读取字符串时,都应该为额外的字节分配一个带空间的新缓冲区,复制字符串,并使用null正确地终止它。

因此,让我们建立一个规则,即ZeroMQ字符串是指定长度的,并且在传输时不带null。在最简单的情况下(在我们的示例中我们将这样做),ZeroMQ字符串整洁地映射到ZeroMQ消息框架,它看起来像上面的图—长度和一些字节。

在C语言中,我们需要做的是接收一个ZeroMQ字符串并将其作为一个有效的C字符串发送给应用程序:

*//`  `Receive ZeroMQ string from socket and convert into C string//`  `Chops string at 255 chars, if it's longer*
**static** char *
s_recv (void *socket) {
`    `char buffer [256];
`    `int size = zmq_recv (socket, buffer, 255, 0);
`    `**if** (size == -1)
`        `**return** NULL;
`    `**if** (size > 255)
`        `size = 255;
`    `buffer [size] = \0;
`    `*/\* use strndup(buffer, sizeof(buffer)-1) in \*nix **/
`    `**return** strdup (buffer);
}

这是一个方便的helper函数,本着使我们可以有效重用的精神,让我们编写一个类似的s_send函数,它以正确的ZeroMQ格式发送字符串,并将其打包到一个可以重用的头文件中。

结果是zhelpers.h,它是一个相当长的源代码,而且只对C开发人员有乐趣,所以请在闲暇时阅读它。

版本报告

ZeroMQ有几个版本,通常,如果遇到问题,它会在以后的版本中得到修复。所以这是一个很有用的技巧,可以准确地知道您实际链接的是哪个版本的ZeroMQ。

这里有一个小程序可以做到这一点:

[version: ZeroMQ version reporting in C](javascript:😉

C++ | C# | Clojure | CL | Delphi | Erlang | F# | Felix | Go | Haskell | Java | Lua | Node.js | Objective-C | Perl| PHP | Python | Q | Ruby | Scala | Tcl | Ada | Basic | Haxe | ooc | Racket

传达信息

第二个经典模式是单向数据分发,其中服务器将更新推送到一组客户机。让我们看一个示例,它推出由邮政编码、温度和相对湿度组成的天气更新。我们将生成随机值,就像真实的气象站所做的那样。
这是服务器。我们将为这个应用程序使用端口5556:

[wuserver: Weather update server in C](javascript:😉

C++ | C# | Clojure | CL | Delphi | Erlang | F# | Felix | Go | Haskell | Haxe | Java | Lua | Node.js | Objective-C| Perl | PHP | Python | Racket | Ruby | Scala | Tcl | Ada | Basic | ooc | Q

这个更新流没有起点也没有终点,就像一个永无止境的广播。

下面是客户端应用程序,它监听更新流并获取与指定zip code有关的任何内容,默认情况下,纽约是开始任何冒险的好地方:

[wuclient: Weather update client in C](javascript:😉

C++ | C# | Clojure | CL | Delphi | Erlang | F# | Felix | Go | Haskell | Haxe | Java | Lua | Node.js | Objective-C| Perl | PHP | Python | Racket | Ruby | Scala | Tcl | Ada | Basic | ooc | Q

图 4 - Publish-Subscribe

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cNF9ORLA-1611294599080)(https://github.com/imatix/zguide/raw/master/images/fig4.png)]

注意,当您使用 SUB socket 时,必须使用zmq_setsockopt()和SUBSCRIBE设置订阅,如下面的代码所示。如果不设置任何订阅,就不会收到任何消息。这是初学者常犯的错误。订阅者可以设置许多订阅,这些订阅被添加到一起。也就是说,如果更新匹配任何订阅,订阅方将接收更新。订阅者还可以取消特定的订阅。订阅通常是,但不一定是可打印的字符串。请参阅zmq_setsockopt()了解其工作原理。

PUB-SUB socket 对(双方的意思)是异步的。客户机在循环中执行zmq_recv()(或者它只需要一次)。试图向 SUB socket发送消息将导致错误(单向的只能收不能发)。类似地,服务在需要的时候执行zmq_send(),但是不能在PUB scoket上执行zmq_recv()(单向的只能发不能收)。

理论上,对于ZeroMQ sockets,哪一端连接和哪一端绑定并不重要。然而,在实践中有一些未记录的差异,我将在稍后讨论。现在,绑定PUB并连接SUB,除非您的网络设计不允许这样做。

关于 PUB-SUB sockets,还有一件更重要的事情需要了解:您不知道订阅者何时开始接收消息。即使启动订阅服务器,等一下,然后启动发布服务器,订阅服务器也始终会错过发布服务器发送的第一个消息。这是因为当订阅服务器连接到发布服务器时(这需要一点时间,但不是零),发布服务器可能已经在发送消息了。

这种“慢速加入者”症状经常出现在很多人身上,我们将对此进行详细解释。
记住ZeroMQ执行异步I/O,即,在后台。假设有两个节点按如下顺序执行此操作:

  • 订阅者连接到端点并接收和计数消息。
  • 发布者绑定到端点并立即发送1,000条消息。

那么订阅者很可能不会收到任何东西。您会闪烁(困扰?),检查是否设置了正确的过滤器,然后重试一次,订阅者仍然不会收到任何内容。

建立TCP连接涉及到握手和握手,握手需要几毫秒,这取决于您的网络和对等点之间的跳数。在这段时间里,ZeroMQ可以发送许多消息。为了便于讨论,假设建立一个连接需要5毫秒,并且相同的链接每秒可以处理1M条消息。在订阅者连接到发布者的5毫秒期间,发布者只需要1毫秒就可以发送那些1K消息。

在Sockets and Patterns中,我们将解释如何同步发布者和订阅者,以便在订阅者真正连接并准备好之前不会开始发布数据。有一个简单而愚蠢的方法可以延迟发布,那就是sleep。但是,不要在实际应用程序中这样做,因为它非常脆弱、不优雅且速度很慢。使用sleep向您自己证明发生了什么,然后等待Sockets and Patterns来查看如何正确地执行此操作。

同步的另一种选择是简单地假设发布的数据流是无限的,没有开始和结束。还有一种假设是订阅者不关心在启动之前发生了什么。这是我们如何构建天气客户端示例的。

因此,客户端订阅其选择的zip code,并为该zip code收集100个更新。如果zip code是随机分布的,这意味着大约有一千万次来自服务器的更新。您可以启动客户机,然后启动服务器,客户机将继续工作。您可以随时停止和重启服务器,客户机将继续工作。当客户机收集了它的100个更新后,它计算平均值,打印并退出。

关于发布-订阅(发布-订阅) publish-subscribe (pub-sub) 模式的几点:

  • 订阅服务器可以连接到多个发布服务器,每次使用一个连接调用。然后,数据将到达并交错(“公平排队”),这样就不会有一个发布者淹没其他发布者。

  • 如果发布者没有连接的订阅者,那么它将删除所有消息。

  • 如果您正在使用TCP,而订阅服务器很慢,则消息将在发布服务器上排队。稍后,我们将研究如何使用“高水位标记(high-water mark)”来保护publishers 不受此影响。

从ZeroMQ v3.x,当使用连接的协议(tcp://或ipc://)时,过滤发生在发布端。
使用epgm://协议,过滤发生在订阅方。在ZeroMQ v2.x,所有过滤都发生在订阅端。

我的笔记本电脑是2011年的英特尔i5,接收和过滤1000万条信息的时间是这样的:

$ time wuclient
Collecting updates from weather server...
Average temperature for zipcode '10001 ' was 28F

real    0m4.470s
user    0m0.000s
sys     0m0.008s

Divide and Conquer(分而治之)

图 5 - Parallel Pipeline

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WHhVyrO1-1611294599082)(https://github.com/imatix/zguide/raw/master/images/fig5.png)]

作为最后一个例子(您肯定已经厌倦了有趣的代码,并希望重新研究比较抽象规范的语言学讨论),让我们来做一些超级计算。然后咖啡。我们的超级计算应用程序是一个相当典型的并行处理模型。我们有:

  • 可同时完成多项任务的ventilator
  • 一组处理任务的workers
  • 从工作进程收集结果的sink

在现实中,workers 在超级快的机器上运行,可能使用gpu(图形处理单元)来做艰难的计算。这是ventilator 。它会生成100个任务,每个任务都有一条消息告诉worker睡眠几毫秒:

[taskvent: Parallel task ventilator in C](javascript:😉

C++ | C# | Clojure | CL | Delphi | Erlang | F# | Felix | Go | Haskell | Haxe | Java | Lua | Node.js | Objective-C| Perl | PHP | Python | Ruby | Scala | Tcl | Ada | Basic | ooc | Q | Racket

这是worker应用程序。它接收到一条消息,休眠几秒钟,然后发出信号,表示它已经完成:
[taskwork: Parallel task worker in C](javascript:😉

C++ | C# | Clojure | CL | Delphi | Erlang | F# | Felix | Go | Haskell | Haxe | Java | Lua | Node.js | Objective-C| Perl | PHP | Python | Ruby | Scala | Tcl | Ada | Basic | ooc | Q | Racket

下面是sink应用程序。它收集了100个任务,然后计算出整个处理过程花费了多长时间,这样我们就可以确认,如果有多个任务,那么这些工人确实是并行运行的:
[tasksink: Parallel task sink in C](javascript:😉

C++ | C# | Clojure | CL | Delphi | Erlang | F# | Felix | Go | Haskell | Haxe | Java | Lua | Node.js | Objective-C| Perl | PHP | Python | Ruby | Scala | Tcl | Ada | Basic | ooc | Q | Racket

  • 1 worker: total elapsed time: 5034 msecs.
  • 2 workers: total elapsed time: 2421 msecs.
  • 4 workers: total elapsed time: 1018 msecs.

让我们更详细地看看这段代码的一些方面:

  • worker将上游连接到ventilator ,下游连接到sink。这意味着可以任意添加worker。如果worker绑定到他们的端点,您将需要(a)更多的端点和(b)每次添加worker时修改ventilator 和/或sink。我们说ventilator 和sink是我们建筑的“稳定”部分,worker是建筑的“动态”部分。

  • 我们必须在所有worker正在启动和运行后开始批处理(We have to synchronize the start of the batch with all workers being up and running.)。这是ZeroMQ中一个相当常见的问题,没有简单的解决方案。’ zmq_connect '方法需要一定的时间。因此,当一组worker连接到ventilator 时,第一个成功连接的worker将在短时间内获得大量信息,而其他worker也在连接。如果不以某种方式同步批处理的开始,系统就根本不会并行运行。试着把ventilator 里的等待时间去掉,看看会发生什么。

  • ventilator 的PUSH socket 将任务分配给worker(假设他们在批处理开始输出之前都连接好了)。这就是所谓的“负载平衡”,我们将再次详细讨论它。

  • sink的PULL均匀地收集worker的结果。这叫做“公平排队”。
    图 6 - Fair Queuing

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pkkp7vy1-1611294599085)(https://github.com/imatix/zguide/raw/master/images/fig6.png)]

管道模式(pipeline pattern)还表现出“慢连接者”综合征,导致指责PUSH sockets不能正确地平衡负载。如果您正在使用 PUSH 和 PULL,而您的一个worker获得的消息比其他worker多得多,这是因为这个PULL socket连接得比其他worker更快,并且在其他worker设法连接之前捕获了大量消息。如果您想要适当的负载平衡,您可能需要查看Advanced Request-Reply Patterns中的负载平衡模式。

ZeroMQ编程

看过一些例子之后,您一定很想开始在一些应用程序中使用ZeroMQ。在你开始之前,深呼吸,放松,并思考一些基本的建议,这会帮你减轻很多压力和困惑。

  • 循序渐进的学习ZeroMQ。它只是一个简单的API,但它隐藏了大量的可能性。慢慢地把握每一种可能性。

  • 写好代码。丑陋的代码隐藏了问题,让别人很难帮助你。您可能已经习惯了无意义的变量名,但是阅读您代码的人不会习惯。使用真实的单词,而不是“我太粗心了,不能告诉您这个变量的真正用途”。使用一致的缩进和干净的布局。写好代码,你的世界就会更舒适。

  • 一边做一边测试。当您的程序无法工作时,您应该知道应该归咎于哪五行。当你使用极具魅力的ZeroMQ的时候,这一点尤其正确,因为在你开始尝试的几次之后,它都不会起作用。

    • 当您发现有些东西不像预期的那样工作时,将您的代码分成几部分,测试每一部分,看看哪一部分不工作。ZeroMQ允许你编写模块化代码;利用这一点。
  • 根据需要进行抽象(类、方法等)。如果你复制/粘贴了很多代码,你也会复制/粘贴错误。

正确理解 Context

ZeroMQ应用程序总是从创建Context开始,然后使用Context创建sockets。在C语言中,它是zmq_ctx_new()调用。您应该在流程中创建并使用一个Context。从技术上讲,Context是一个进程中所有sockets的容器,它充当inproc sockets的传输,inproc sockets是在一个进程中连接线程的最快方式。如果在运行时一个流程有两个Context,那么它们就像独立的ZeroMQ实例。如果这是你明确想要的,好的,否则记住:

Call zmq_ctx_new() once at the start of a process, and zmq_ctx_destroy() once at the end.

在流程开始时调用zmq_ctx_new()一次,在流程结束时调用zmq_ctx_destroy()一次。
如果使用fork()系统调用,那么在fork之后和子进程代码的开头执行zmq_ctx_new()。通常,您希望在子进程中执行有趣的(ZeroMQ)操作,而在父进程中执行乏味的流程管理。

退出前清理

一流的程序员与一流的杀手有相同的座右铭:当你完成工作时,总是要清理干净。当您在Python之类的语言中使用ZeroMQ时,会自动释放一些内容。但是在使用C语言时,必须小心地释放对象,否则会导致内存泄漏、应用程序不稳定,通常还会产生坏的因果报应。

内存泄漏是一回事,但是ZeroMQ对如何退出应用程序非常挑剔。原因是技术性的和痛苦的,但是结果是,如果您打开任何sockets ,zmq_ctx_destroy()函数将永远挂起。即使关闭所有sockets ,默认情况下,如果有挂起连接或发送,zmq_ctx_destroy()将永远等待,除非在关闭这些sockets 之前将这些sockets 的逗留时间设置为零。

我们需要担心的ZeroMQ对象是 messages, sockets, 和 contexts。幸运的是,它非常简单,至少在简单的程序中:

  • 可以时使用zmq_send()和zmq_recv(),因为它避免了使用zmq_msg_t对象。

  • 如果您确实使用zmq_msg_recv(),那么总是在使用完接收到的消息后立即释放它,方法是调用zmq_msg_close()。

  • 如果您打开和关闭了许多sockets,这可能是您需要重新设计应用程序的标志。在某些情况下,在销毁上下文之前不会释放sockets句柄。

  • 退出程序后,关闭socket,然后调用zmq_ctx_destroy()。这会销毁context。

这至少是C开发的情况。在具有自动对象销毁的语言中,离开作用域时将销毁套接字和上下文。
如果使用异常,则必须在类似“final”块的地方进行清理,这与任何资源都是一样的。

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

首先,不要尝试从多个线程使用同一个socket。请不要解释为什么你认为这将是非常有趣的,只是请不要这样做。接下来,您需要关闭具有正在进行的请求的每个socket。正确的方法是设置一个较低的逗留值(1秒),然后关闭socket。如果您的语言绑定在销毁context时没有自动为您完成此任务,我建议发送一个补丁。

最后,销毁context。这将导致任何阻塞接收或轮询或发送附加线程(即,共享context)返回一个错误。捕获该错误,然后设置逗留,关闭该线程中的socket,然后退出。不要两次破坏相同的Context。主线程中的zmq_ctx_destroy将阻塞,直到它所知道的所有socket都安全关闭为止。

瞧!这是非常复杂和痛苦的,任何称职的语言绑定作者都会自动地这样做,使socket关闭舞蹈变得不必要。

为什么我们需要ZeroMQ

既然您已经看到了ZeroMQ的作用,让我们回到“为什么”。

现在的许多应用程序都是由跨越某种网络(LAN或Internet)的组件组成的。因此,许多应用程序开发人员最终都会进行某种消息传递。一些开发人员使用消息队列产品,但大多数时候他们自己使用TCP或UDP来完成。这些协议并不难使用,但是从a向B发送几个字节与以任何一种可靠的方式进行消息传递之间有很大的区别。

让我们看看在开始使用原始TCP连接各个部分时所面临的典型问题。任何可重用的消息层都需要解决所有或大部分问题:

  • 我们如何处理I/O?我们的应用程序是阻塞还是在后台处理I/O ?这是一个关键的设计决策。阻塞I/O会创建伸缩性不好的体系结构。但是后台I/O很难正确地执行。
  • 我们如何处理动态组件,即,暂时消失的碎片?我们是否将组件正式划分为“客户端”和“服务器”,并要求服务器不能消失?如果我们想把服务器连接到服务器呢?我们是否每隔几秒钟就尝试重新连接?
  • 我们如何在网络上表示消息?我们如何设置数据的框架,使其易于读写,不受缓冲区溢出的影响,对小消息有效,但对于那些戴着派对帽子跳舞的猫的大型视频来说,这已经足够了吗?
  • 我们如何处理无法立即交付的消息?特别是,如果我们正在等待一个组件重新联机?我们是丢弃消息,将它们放入数据库,还是放入内存队列?
  • 我们在哪里存储消息队列?如果从队列读取的组件非常慢,导致我们的队列增加,会发生什么?那么我们的策略是什么呢?
  • 我们如何处理丢失的消息?我们是等待新数据、请求重发,还是构建某种确保消息不会丢失的可靠性层?如果这个层本身崩溃了呢?
  • 如果我们需要使用不同的网络传输怎么办?比如说,多播而不是TCP单播?还是IPv6 ?我们是否需要重写应用程序,还是在某个层中抽象传输?
  • 我们如何路由消息?我们可以向多个对等点发送相同的消息吗?我们可以将回复发送回原始请求者吗?
  • 我们如何为另一种语言编写API ?我们是重新实现一个线级协议,还是重新打包一个库?如果是前者,如何保证栈的高效稳定?如果是后者,我们如何保证互操作性?
  • 我们如何表示数据,以便在不同的体系结构之间读取数据?我们是否对数据类型强制执行特定的编码?这是消息传递系统的工作,而不是更高一层的工作。
  • 我们如何处理网络错误?我们是等待并重试,默不作声地忽略它们,还是中止?

以一个典型的开源项目为例,比如Hadoop Zookeeper,在src/ C /src/ Zookeeper . C中读取C API代码。当我在2013年1月读到这段代码时,它是4200行神秘代码,其中有一个未文档化的客户机/服务器网络通信协议。我认为这是有效的,因为它使用轮询而不是选择。但实际上,Zookeeper应该使用通用消息层和显式文档化的有线级协议。对于团队来说,一遍又一遍地构建这个特定的轮子是非常浪费的。

但是如何创建可重用的消息层呢?为什么在如此多的项目需要这种技术的时候,人们仍然在用一种很困难的方式来完成它,在他们的代码中驱动TCP套接字,并一次又一次地解决长列表中的问题?

事实证明,构建可重用的消息传递系统是非常困难的,这就是为什么很少有自由/开源软件项目尝试过,以及为什么商业消息传递产品是复杂的、昂贵的、不灵活的和脆弱的。2006年,iMatix设计了AMQP,它开始为自由/开源软件开发人员提供消息系统的第一个可重用配方。AMQP比其他许多设计都要好,但仍然相对复杂、昂贵和脆弱。学习使用它需要几周的时间,而创建当事情变得棘手时不会崩溃的稳定的体系结构需要几个月的时间。

图 7 - Messaging as it Starts

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JMWV27Re-1611294599086)(https://github.com/imatix/zguide/raw/master/images/fig7.png)]

大多数消息传递项目,如AMQP,都试图通过发明一个新的概念“broker”来解决这一长串问题,该概念负责寻址、路由和排队,从而以可重用的方式解决这些问题。这将导致客户机/服务器协议或一些未文档化协议之上的一组api,这些协议允许应用程序与此broker通信。在减少大型网络的复杂性方面,Brokers 是一件很好的事情。但是在Zookeeper这样的产品中添加基于代理的消息会让情况变得更糟,而不是更好。这将意味着添加一个额外的大框和一个新的单点故障。broker 迅速成为一个瓶颈和一个需要管理的新风险。如果软件支持它,我们可以添加第二个、第三个和第四个broker ,并制定一些故障转移方案。人们这样做。它创造了更多的活动部件,更多的复杂性,以及更多需要打破的东西。

以broker 为中心需要自己的operations team。你确实需要日日夜夜地观察这些brokers,当他们开始行为不端时,你要用棍子打他们。你需要盒子,你需要备份盒子,你需要人们来管理这些盒子。它只值得为大型应用程序做很多移动的部分,由几个团队的人在几年的时间内构建。

图 8 - Messaging as it Becomes

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nPIUc2o8-1611294599087)(https://github.com/imatix/zguide/raw/master/images/fig8.png)]

因此,中小型应用程序开发人员陷入了困境。它们要么避免网络编程,要么开发不可伸缩的单片应用程序。或者他们跳入网络编程,使脆弱、复杂的应用程序难以维护。或者他们押注于一个消息传递产品,最终开发出可伸缩的应用程序,这些应用程序依赖于昂贵且容易崩溃的技术。一直没有真正好的选择,这也许就是为什么messaging 在很大程度上停留在上个世纪,并激起强烈的情感:对用户来说是负面的,对那些销售支持和许可的人来说是欢欣鼓舞的。

我们需要的是能够完成消息传递功能的东西,但它的实现方式非常简单和廉价,可以在任何应用程序中运行,成本几乎为零。它应该是一个链接的库,没有任何其他依赖关系。没有额外的移动部件,所以没有额外的风险。它应该运行在任何操作系统上,并且可以使用任何编程语言。

这就是ZeroMQ:一个高效的、可嵌入的库,它解决了应用程序需要在不花费太多成本的情况下在网络上保持良好弹性的大部分问题。

特别地:

  • 它在后台线程中异步处理I/O。这些线程使用无数据结构与应用程序线程通信,因此并发ZeroMQ应用程序不需要锁、信号量或其他等待状态。

  • 组件可以动态进出,ZeroMQ将自动重新连接。这意味着您可以以任何顺序启动组件。您可以创建“面向服务的体系结构”(service-oriented architecture, soa),其中服务可以随时加入和离开网络。

  • 它在需要时自动对消息进行排队。它很聪明地做到了这一点,在对消息进行排队之前,尽可能地将消息推送到接收端。

  • 它有办法处理过满的队列(称为“高水位”)。当队列已满时,ZeroMQ会根据您正在执行的消息类型(所谓的“模式”)自动阻塞发送者或丢弃消息。

  • 它允许您的应用程序通过任意传输相互通信:TCP、多播、进程内、进程间。您不需要更改代码来使用不同的传输。

  • 它使用依赖于消息传递模式的不同策略安全地处理慢速/阻塞的readers 。

  • 它允许您使用各种模式路由消息,比如请求-应答和发布-订阅。这些模式是取决于你如何创建拓扑结构的,即网络的结构。

  • 它允许您创建代理来通过一个调用对消息进行排队、转发或捕获。代理可以降低网络的互连复杂性。

  • 它通过在网络上使用一个简单的框架,完全按照发送的方式传递整个消息。如果您写了一条10k的消息,您将收到一条10k的消息。

  • 它不将任何格式强加于消息。它们是从0到gb大小的水滴。当您想要表示数据时,您可以在顶部选择一些其他产品,例如msgpack、谷歌的协议缓冲区等。

  • 它通过在有意义的情况下自动重试来智能地处理网络错误。

  • 它可以减少你的碳足迹。用更少的CPU做更多的事情意味着您的机器使用更少的能量,并且您可以让旧的机器使用更长时间。 Al Gore会喜欢ZeroMQ的。

  • 实际上ZeroMQ做的远不止这些。
    它对如何开发支持网络的应用程序具有颠覆性的影响。从表面上看,它是一个受套接字启发的API,您可以在其上执行’ zmq_recv() ‘和’ zmq_send() '。但是消息处理很快成为中心循环,您的应用程序很快就分解为一组消息处理任务。它优雅自然。它是可伸缩的:每个任务都映射到一个节点,节点之间通过任意传输进行通信。一个进程中的两个节点(节点是一个线程)、一个框中的两个节点(节点是一个进程)或一个网络上的两个节点(节点是一个框)—都是一样的,没有应用程序代码更改。

(可伸缩性的scoket)

Socket Scalability(可伸缩性的scoket)

让我们看看ZeroMQ的可伸缩性。下面是一个shell脚本,它先启动天气服务器,然后并行地启动一堆客户机:

wuserver &
wuclient 12345 &
wuclient 23456 &
wuclient 34567 &
wuclient 45678 &
wuclient 56789 &

As the clients run, we take a look at the active processes using the top command’, and we see something like (on a 4-core box):

PID  USER  PR  NI  VIRT  RES  SHR S %CPU %MEM   TIME+  COMMAND
7136  ph   20   0 1040m 959m 1156 R  157 12.0 16:25.47 wuserver
7966  ph   20   0 98608 1804 1372 S   33  0.0  0:03.94 wuclient
7963  ph   20   0 33116 1748 1372 S   14  0.0  0:00.76 wuclient
7965  ph   20   0 33116 1784 1372 S    6  0.0  0:00.47 wuclient
7964  ph   20   0 33116 1788 1372 S    5  0.0  0:00.25 wuclient
7967  ph   20   0 33072 1740 1372 S    5  0.0  0:00.35 wuclient

让我们想一下这里发生了什么。气象服务器只有一个套接字,但是这里我们让它并行地向五个客户机发送数据。我们可以有成千上万的并发客户端。服务器应用程序不会看到它们,也不会直接与它们对话。所以ZeroMQ套接字就像一台小服务器,默默地接受客户机请求,并以网络最快的速度将数据发送给它们。它是一个多线程服务器,可以从CPU中挤出更多的能量。

从ZeroMQ v2.2升级到ZeroMQ v3.2

Compatible Changes(兼容变更)

这些更改不会直接影响现有的应用程序代码:

  • Pub-sub filtering现在运行在在publisher而不是在subscriber,这在许多pub-sub 用例中显著提高了性能 You can mix v3.2 and v2.1/v2.2 publishers and subscribers safely.

  • ZeroMQ v3.2 has many new API methods (zmq_disconnect(), zmq_unbind(), zmq_monitor(), zmq_ctx_set(), etc.)

不兼容的变更

这些是影响应用程序和语言绑定的主要领域:(These are the main areas of impact on applications and language bindings):

  • Changed send/recv methods: zmq_send() and zmq_recv() have a different, simpler interface, and the old functionality is now provided by zmq_msg_send() and zmq_msg_recv(). Symptom: compile errors. Solution: fix up your code.

  • 这两种方法成功时返回正值,错误时返回-1。在v2。他们成功时总是零回报。症状:工作正常时明显的错误。解决方案:严格测试返回代码= -1,而不是非零.

  • zmq_poll() 现在等待毫秒,而不是微秒。症状:应用程序停止响应(实际上响应慢了1000倍)。解决方案::在所有zmq_poll调用中,使用下面定义的ZMQ_POLL_MSEC宏。

  • “ZMQ_NOBLOCK”现在称为“ZMQ_DONTWAIT”。症状:在“ZMQ NOBLOCK”宏上编译失败。

  • “ZMQ_HWM”socket 选项现在分为“ZMQ_SNDHWM”和“ZMQ_RCVHWM”。症状:在’ ZMQ_HWM '宏上编译失败。

  • 大多数但不是所有的’ zmq_getsockopt() ‘选项现在都是整数值。症状:运行时错误返回’ zmq_setsockopt ‘和’ zmq_getsockopt '。

  • ’ ZMQ_SWAP ‘选项已被删除。症状:在’ ZMQ_SWAP '上编译失败。解决方案:重新设计使用此功能的任何代码。

Suggested Shim Macros

对于希望在两个v2上运行的应用程序。x和v3.2,例如语言绑定,我们的建议是尽可能地模拟v3.2。这里有一些C宏定义,可以帮助您的C/ c++代码跨两个版本工作(取自CZMQ):

\#ifndef ZMQ_DONTWAIT
\#`   `define ZMQ_DONTWAIT`     `ZMQ_NOBLOCK
\#endif
\#if ZMQ_VERSION_MAJOR == 2
\#`   `define zmq_msg_send(msg,sock,opt) zmq_send (sock, msg, opt)
\#`   `define zmq_msg_recv(msg,sock,opt) zmq_recv (sock, msg, opt)
\#`   `define zmq_ctx_destroy(context) zmq_term(context)
\#`   `define ZMQ_POLL_MSEC`    `1000`        `*//  zmq_poll is usec*
\#`   `define ZMQ_SNDHWM ZMQ_HWM
\#`   `define ZMQ_RCVHWM ZMQ_HWM
\#elif ZMQ_VERSION_MAJOR == 3
\#`   `define ZMQ_POLL_MSEC`    `1`           `*//  zmq_poll is msec*
\#endif

Warning: 不稳定的范例!

传统的网络编程建立在一个socket 与一个connection、一个peer通信的一般假设之上。有多播协议,但这些都是外来的。当我们假设““one socket = one connection””时,我们以某种方式扩展架构。我们创建逻辑线程,其中每个线程使用一个socket和一个peer。我们在这些线程中放置intelligence 和状态。
在ZeroMQ领域,sockets是快速后台通信引擎的入口,这些引擎可以自动地为您管理一整套连接。您无法查看、处理、打开、关闭或将状态附加到这些连接。无论您使用阻塞发送、接收或轮询,您只能与socket通信,而不是它为您管理的连接。连接是私有的,不可见的,这是ZeroMQ可伸缩性的关键。
这是因为,与socket通信的代码可以处理任意数量的连接,而无需更改周围的任何网络协议。ZeroMQ中的消息传递模式比应用程序代码中的消息传递模式扩展得更便宜。

所以一般的假设不再适用。当您阅读代码示例时,您的大脑将尝试将它们映射到您所知道的内容。您将读取“socket”并认为“啊,这表示到另一个节点的连接”。这是错误的。当你读到“thread”时,你的大脑又会想,“啊,一个thread代表了与另一个节点的连接”,你的大脑又会出错。
如果你是第一次读这本指南的话,要意识到这一点,直到你在一两天内编写ZeroMQ代码(可能是三到四天),你可能会感到困惑,特别是ZeroMQ使事情多么简单,你可以试着把这个普遍的假设强加给ZeroMQ,它不会工作。然后你将经历你的启蒙和信任的时刻,当一切都变得清晰的时候,你将经历“zap-pow-kaboom satori”的时刻。

Chapter 2 - Sockets and Patterns

在第1章—基础知识中,我们将ZeroMQ作为驱动器,并提供了一些主要ZeroMQ模式的基本示例:请求-应答、发布-订阅和管道。在本章中,我们将亲自动手,开始学习如何在实际程序中使用这些工具。

我们将讨论:

  • 如何创建和使用ZeroMQ Sockets。
  • 如何在Sockets上发送和接收消息。
    如何围绕ZeroMQ的异步I/O模型构建应用程序。
    如何在一个线程中处理多个Sockets。
    如何正确处理致命和非致命错误。
    如何处理像Ctrl-C这样的中断信号。
    如何干净地关闭ZeroMQ应用程序。
    如何检查ZeroMQ应用程序的内存泄漏。
    如何发送和接收多部分消息。
    如何跨网络转发消息。
    如何构建一个简单的消息队列代理(broker)。
    如何使用ZeroMQ编写多线程应用程序。
    如何使用ZeroMQ在线程之间发送信号。
    如何使用ZeroMQ来协调节点网络。
    如何为发布-订阅创建和使用消息信封。
    使用HWM (high-water mark)来防止内存溢出。

The Socket API

说句老实话,ZeroMQ对你耍了个花招,对此我们不道歉。这是为了你好,我们比你更伤心。ZeroMQ提供了一个熟悉的基于Socket的API,要隐藏一堆消息处理引擎需要付出很大的努力。然而,结果将慢慢地修正您关于如何设计和编写分布式软件的世界观。

Socket实际上是网络编程的标准API, as well as being useful for stopping your eyes from falling onto your cheeks(怎么翻译? 大跌眼镜?)。ZeroMQ对开发人员特别有吸引力的一点是,它使用Socket和messages ,而不是其他任意一组概念。感谢Martin Sustrik的成功。它将“面向消息的中间件”变成了“额外辛辣(Extra Spicy ,升级版)的Sockets!”这让我们对披萨产生了一种奇怪的渴望,并渴望了解更多。
就像最喜欢的菜一样,ZeroMQsockets 很容易消化。sockets 的生命由四个部分组成,就像BSDsockets 一样:

  • 创造和摧毁sockets ,它们一起形成一个插座生命的业力循环(see zmq_socket(), zmq_close()).

  • 通过设置套接字上的选项并在必要时检查它们来配置套接字(see zmq_setsockopt(), zmq_getsockopt()).

  • Plugging sockets into the network topology by creating ZeroMQ connections to and from them (see zmq_bind(), zmq_connect()).

  • 通过创建与它们之间的ZeroMQ连接,将sockets插入网络拓扑(see zmq_msg_send(), zmq_msg_recv()).

注意,套接字总是空指针,消息(我们很快就会讲到)是结构。所以在C语言中,按原样传递sockets ,但是在所有处理消息的函数中传递消息的地址,比如zmq_msg_send()和zmq_msg_recv()。作为一个助记符,请认识到“在ZeroMQ中,您所有的sockets 都属于我们”,但是消息实际上是您在代码中拥有的东西。
创建、销毁和配置Sockets的工作原理与您对任何对象的期望一样。但是请记住ZeroMQ是一个异步的、有弹性的结构。这对我们如何将Sockets插入网络拓扑以及之后如何使用Sockets有一定的影响。

将Sockets插入拓扑中

要在两个节点之间创建连接,可以在一个节点中使用zmq_bind(),在另一个节点中使用zmq_connect()。一般来说,执行zmq_bind()的节点是一个“服务器”,位于一个已知的网络地址上,执行zmq_connect()的节点是一个“客户机”,具有未知或任意的网络地址。因此,我们说“将socket 绑定到端点”和“将socket 连接到端点”,端点就是那个已知的网络地址。

ZeroMQ连接与传统TCP连接有些不同。主要的显著差异是:

  • They go across an arbitrary transport (inproc, ipc, tcp, pgm, or epgm). See zmq_inproc(), zmq_ipc(), zmq_tcp(), zmq_pgm(), and zmq_epgm().

  • 一个将socket可能有许多传出和传入连接。.

  • 没有’ zmq_accept '()方法。当socket绑定到端点时,它将自动开始接受连接

  • 网络连接本身发生在后台,如果网络连接中断,ZeroMQ将自动重新连接(例如,如果peer 消失,然后返回)。

  • 您的应用程序代码不能直接使用这些连接;它们被封装在socket下面。

许多架构遵循某种客户机/服务器模型,其中服务器是最静态的组件,而客户机是最动态的组件,即,他们来了又走的最多。有时存在寻址问题:服务器对客户机可见,但是反过来不一定是这样的。因此,很明显,哪个节点应该执行zmq_bind()(服务器),而哪个节点应该执行zmq_connect()(客户机)。它还取决于您使用的socket的类型,对于不常见的网络体系结构有一些例外。稍后我们将研究socket类型。

现在,假设在启动服务器之前先启动客户机。在传统的网络中,我们会看到一个大大的红色失败标志。但是ZeroMQ让我们任意地开始和停止。只要客户机节点执行zmq_connect(),连接就存在,该节点就可以开始向socket写入消息。在某个阶段(希望是在消息排队太多而开始被丢弃或客户机阻塞之前),服务器会启动,执行zmq_bind(),然后ZeroMQ开始传递消息。
一个服务器节点可以绑定到许多端点(即协议和地址的组合),并且它可以使用一个socket来实现这一点。这意味着它将接受跨不同传输的连接:

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

对于大多数传输,不能像UDP那样两次绑定到同一个端点。然而,ipc传输允许一个进程绑定到第一个进程已经使用的端点。这意味着允许进程在崩溃后恢复。
虽然ZeroMQ试图对哪边绑定和哪边连接保持中立,但还是有区别的。稍后我们将更详细地看到这些。其结果是,您通常应该将“服务器”视为拓扑的静态部分,它绑定到或多或少固定的端点,而将“客户机”视为动态部分,它们来来去去并连接到这些端点。然后,围绕这个模型设计应用程序。它“正常工作”的可能性要大得多。

Sockets 有多个类型。Socket类型定义Sockets的语义、Socket向内和向外路由消息的策略、队列等。您可以将某些类型的Socket连接在一起,例如,publisher Socket和subscriber Socket。Socket在“messaging patterns”中协同工作。稍后我们将更详细地讨论这个问题。
正是能够以这些不同的方式连接Sockets,使ZeroMQ具备了作为消息队列系统的基本功能。在此之上还有一些层,比如代理,我们稍后将讨论它。但从本质上讲,使用ZeroMQ,您可以像孩子的积木玩具一样将各个部分拼接在一起,从而定义您的网络体系结构。

发送和接收消息

要发送和接收消息,可以使用zmq_msg_send()和zmq_msg_recv()方法。这些名称都是传统的,但是ZeroMQ的I/O模型与传统的TCP模型有很大的不同,您需要时间来理解它。

图 9 - TCP sockets are 1 to 1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uTTVUHmB-1611294599088)(https://github.com/imatix/zguide/raw/master/images/fig9.png)]

让我们来看看TCP sockets和ZeroMQ sockets在处理数据方面的主要区别:

  • ZeroMQ套接字像UDP一样携带消息,而不像TCP那样携带字节流。ZeroMQ消息是长度指定的二进制数据。我们很快就会讲到信息;它们的设计是针对性能进行优化的,因此有点棘手。
  • ZeroMQ套接字在后台线程中执行I/O。这意味着消息到达本地输入队列并从本地输出队列发送,无论您的应用程序在忙什么。
  • 根据socket类型,ZeroMQ sockets具有内置的1对n路由行为。

zmq_send()方法实际上并不将消息发送到socket connection(s)。它对消息进行排队,以便I/O线程可以异步发送消息。它不会阻塞,除非在某些异常情况下。因此,当zmq_send()返回到应用程序时,不一定要发送消息。

单播传输(Unicast Transports)

ZeroMQ提供了一组单播传输(inproc、ipc和tcp)和多播传输(epgm、pgm)。多播是一种先进的技术,我们稍后会讲到。不要开始使用它,除非你知道你的扇出比将使1到n单播不可能(Don’t even start using it unless you know that your fan-out ratios will make 1-to-N unicast impossible.)。

对于大多数常见的情况,使用tcp,这是一个断开连接式(disconnected )的tcp传输。它是弹性的,便携式的,和足够快的大多数情况下。我们将此称为断开连接式(disconnected ),因为ZeroMQ的tcp传输不需要在连接到端点之前存在端点。客户机和服务器可以随时连接和绑定,可以来回切换,并且对应用程序保持透明。

进程间ipc传输也是断开连接式(dis

相关文章