zmq 基础使用1


http://www.codedump.info/?p=196

 

zeromq和libevent,ACE这样定位的项目有什么区别没有?
1) libevent封装了对网络I/O,信号,定时器等的处理,可以基于它之上做网络层的开发.
2) ACE封装了不同平台下的系统调用,也提供好几种网络编程的模型.
然而,zeromq不是libevent,也不是ACE,因为它的主要特性是:面向消息进行通信.所以,它提供的是比libevent,ACE处在网络通信中更高一层的组件.使用它,程序员不再需要上面提到的libevent,ACE之类的库需要关心的东西,程序员如果要使用zeromq,只需要做如下的事情:
1) 告知所使用的patten,比如request-reply,pub-sub,push-pull等(下面会详细解释这个pattern).
2) 告知是用于机器之间,还是进程之间,线程之间的通信.
然后,将所需要发送的数据封装到zeromq自带的msg结构体中发送出去,使用者自己关心如何序列化/反序列化这些数据,然后如何处理这些数据就是使用者的事情了.

这样看上来,使用者要关注的事情”高”了一层,大部分的精力都可以放在业务逻辑之上了.简而言之,它让使用者的精力放在了通信模式和业务逻辑上,而不是更下面一层的网络层上.

下面解释一下zeromq常用的几种网络pattern:
1) request-reply
就是一般的C/S架构中,client与server之间一问一答的通信模式,比如最经典的echo服务.需要注意的是,client发送一个request,server必须有一个回应.
这个pattern并不是什么亮点,下面亮点来了.

2) publish-subscribe
server端作为publish端,而任何连接到服务端的client都会成为subscribe端.也就是说,server端会把当前的所有需要publish出去的消息全部发送到当前连接上去的client上.

3) push-pull
server端作为push端,而client端作为pull端.如果有多个client端同时连接到这个server,则服务器会在内部做一个负载均衡,采用平均分配的算法,将所有的消息均衡发布到client端上.

看上去,很稀松平常?接下来亮点真的来了.
2),3)两种模式中,无论是server端还是client端在启动时都是不知道client实际数量的,这就意味着,一个使用zeromq搭建的服务,可以进行”热更新”.
考虑如下一种场景.一个server端做为一组服务器集群最上层的一个proxy,起到负载均衡的作用,将请求按照它下面对应服务器集群依次派发到不同的client端进行处理.某个时刻可能处理的机器只有2台,而随着负载越来越大,可能需要3台机器了,这个时候如果使用zeromq的push-pull搭建的proxy端,则可以不用对之前搭建的server,client端进行停机,只需要新启动一个client连接上去,proxy层就会自动根据当前的机器分配平均派发任务了.cool.

实际上,这些模式并不是什么新东西,只不过zeromq为使用者做了一个封装,而不是像libevent,ACE等还局限在网络层部分的封装,它关注的是通信层,通信模式等.

个人感觉,zeromq部分解决了erlang所要解决的问题:在多台机器中通信,派发任务等,是分布式通信的利器,但是局限于语言的限制,它没有办法做的跟erlang一样的完善(erlang已经可以算的上一个简易微型的OS了),但是许多的时候,似乎只使用这一部分功能也就足够了.

zeromq的代码量,截至到我目前阅读的2.0.10-stable版本,也只有不到2W行代码.提供出去的API也极为简单,但是内部的实现比较”绕”,zeromq是我阅读过的项目中少数的非常需要依赖调试工具跟进代码才能看懂代码流程的项目,同时代码中类的继承层次也比较多,阅读起来并不像它提供的API那样简单直白.后续会对其中的一些难点做一些分析.

 

https://github.com/anjuke/zguide-cn/blob/master/chapter1.md

在使用SUB套接字时,必须使用zmq_setsockopt()方法来设置订阅的内容。如果你不设置订阅内容,那将什么消息都收不到,新手很容易犯这个错误。订阅信息可以是任何字符串,可以设置多次。只要消息满足其中一条订阅信息,SUB套接字就会收到。订阅者可以选择不接收某类消息,也是通过zmq_setsockopt()方法实现的。

 

PUB-SUB套接字组合是异步的。客户端在一个循环体中使用zmq_recv()接收消息,如果向SUB套接字发送消息则会报错;类似地,服务端可以不断地使用zmq_send()发送消息,但不能在PUB套接字上使用zmq_recv()。

关于PUB-SUB套接字,还有一点需要注意:你无法得知SUB是何时开始接收消息的。就算你先打开了SUB套接字,后打开PUB发送消息,这时SUB还是会丢失一些消息的,因为建立连接是需要一些时间的。很少,但并不是零。

我们知道在建立TCP连接时需要进行三次握手,会耗费几毫秒的时间,而当节点数增加时这个数字也会上升。在这么短的时间里,ZMQ就可以发送很多很多消息了。举例来说,如果建立连接需要耗时5毫秒,而ZMQ只需要1毫秒就可以发送完这1000条消息。

关于发布-订阅模式的几点说明:

  • 订阅者可以连接多个发布者,轮流接收消息;
  • 如果发布者没有订阅者与之相连,那它发送的消息将直接被丢弃;
  • 如果你使用TCP协议,那当订阅者处理速度过慢时,消息会在发布者处堆积。以后我们会讨论如何使用阈值(HWM)来保护发布者。
  • 在目前版本的ZMQ中,消息的过滤是在订阅者处进行的。也就是说,发布者会向订阅者发送所有的消息,订阅者会将未订阅的消息丢弃。

 

正确地使用上下文

ZMQ应用程序的一开始总是会先创建一个上下文,并用它来创建套接字。在C语言中,创建上下文的函数是zmq_init()。一个进程中只应该创建一个上下文。从技术的角度来说,上下文是一个容器,包含了该进程下所有的套接字,并为inproc协议提供实现,用以高速连接进程内不同的线程。如果一个进程中创建了两个上下文,那就相当于启动了两个ZMQ实例。如果这正是你需要的,那没有问题,但一般情况下:

在一个进程中使用zmq_init()函数创建一个上下文,并在结束时使用zmq_term()函数关闭它

如果你使用了fork()系统调用,那每个进程需要自己的上下文对象。如果在调用fork()之前调用了zmq_init()函数,那每个子进程都会有自己的上下文对象。通常情况下,你会需要在子进程中做些有趣的事,而让父进程来管理它们。

 

ZMQ对象包括:消息、套接字、上下文。

  • 不要在多个线程中使用同一个套接字。不要去想为什么,反正别这么干就是了。
  • 关闭所有的套接字,并在主程序中关闭上下文对象。
  • 如果仍有处于阻塞状态的recv或poll调用,应该在主程序中捕捉这些错误,并在相应的线程中关闭套接字。不要重复关闭上下文,zmq_term()函数会等待所有的套接字安全地关闭后才结束。

 记住一点,在ZMQ中所有的套接字都是由ZMQ管理的,只有消息是由程序员管理的

ZMQ连接和传统的TCP连接是有区别的,主要有:

  • 使用多种协议,inproc(进程内)、ipc(进程间)、tcp、pgm(广播)、epgm
  • 当客户端使用zmq_connect()时连接就已经建立了,并不要求该端点已有某个服务使用zmq_bind()进行了绑定;
  • 连接是异步的,并由一组消息队列做缓冲;
  • 连接会表现出某种消息模式,这是由创建连接的套接字类型决定的;
  • 一个套接字可以有多个输入和输出连接;
  • ZMQ没有提供类似zmq_accept()的函数,因为当套接字绑定至端点时它就自动开始接受连接了;
  • 应用程序无法直接和这些连接打交道,因为它们是被封装在ZMQ底层的。

 

服务端节点可以仅使用一个套接字就能绑定至多个端点。也就是说,它能够使用不同的协议来建立连接:

zmq_bind (socket, "tcp://*:5555"); zmq_bind (socket, "tcp://*:9999"); zmq_bind (socket, "ipc://myserver.ipc");

当然,你不能多次绑定至同一端点,这样是会报错的。客户端节点也可以使用一个套接字同时建立多个连接

我们在设计架构时,应该遵循“服务端是稳定的,客户端是灵活的“原则,这样就不太会出错。

https://github.com/anjuke/zguide-cn/blob/master/chapter2.md

 

1                              2

TCP套接字和ZMQ套接字之间在传输数据方面的区别:

  • ZMQ套接字传输的是消息,而不是字节(TCP)或帧(UDP)。消息指的是一段指定长度的二进制数据块。
  • ZMQ套接字在后台进行I/O操作,也就是说无论是接收还是发送消息,它都会先传送到一个本地的缓冲队列,这个内存队列的大小是可以配置的。
  • ZMQ套接字可以和多个套接字进行连接(如果套接字类型允许的话)。TCP协议只能进行点对点的连接,而ZMQ则可以进行一对多(类似于无线广播)、多对多(类似于邮局)、多对一(类似于信箱),当然也包括一对一的情况。
  • ZMQ套接字可以发送消息给多个端点(扇出模型),或从多个端点中接收消息(扇入模型)

调用zmq_send()方法时其实并没有真正将消息发送给套接字连接。消息会在一个内存队列中保存下来,并由后台的I/O线程异步地进行发送。如果不出意外情况,这一行为是非阻塞的。所以说,即便zmq_send()有返回值,并不能代表消息已经发送。

当你在用zmq_msg_init_data()初始化消息后,你不能重用或是释放这条消息,否则ZMQ的I/O线程会认为它在传输垃圾数据。这对初学者来讲是一个常犯的错误,下文我们会讲述如何正确地处理消息。

ZMQ提供了一组单播传输协议(inporc, ipc, tcp),和两个广播协议(epgm, pgm)。

进程内协议,即inproc,可以在同一个进程的不同线程之间进行消息传输,它比ipc或tcp要快得多。这种协议有一个要求,必须先绑定到端点,才能建立连接,也许未来也会修复。通常的做法是先启动服务端线程,绑定至端点,后启动客户端线程,连接至端点。

 

向套接字写入一个消息时可能会将消息发送给很多节点,相应的,套接字又会从所有已建立的连接中接收消息。zmq_recv()方法使用了公平队列的算法来决定接收哪个连接的消息。

I/O线程

我们提过ZMQ是通过后台的I/O线程进行消息传输的。一个I/O线程已经足以处理多个套接字的数据传输要求,当然,那些极端的应用程序除外。这也就是我们在创建上下文时传入的1所代表的意思:

void *context = zmq_init (1);

ZMQ应用程序和传统应用程序的区别之一就是你不需要为每个套接字都创建一个连接。单个ZMQ套接字可以处理所有的发送和接收任务。如,当你需要向一千个订阅者发布消息时,使用一个套接字就可以了;当你需要向二十个服务进程分发任务时,使用一个套接字就可以了;当你需要从一千个网页应用程序中获取数据时,也是使用一个套接字就可以了。

ZMQ会为你做些什么:

它会将消息快速高效地发送给其他节点,这里的节点可以是线程、进程、或是其他计算机;

ZMQ为应用程序提供了一套简单的套接字API,不用考虑实际使用的协议类型(进程内、进程间、TPC、或广播);

当节点调动时,ZMQ会自动进行连接或重连;

无论是发送消息还是接收消息,ZMQ都会先将消息放入队列中,并保证进程不会因为内存溢出而崩溃,适时地将消息写入磁盘;

ZMQ会处理套接字异常;

所有的I/O操作都在后台进行;

ZMQ不会产生死锁。

 

ZMQ的核心消息模式有:

  • 请求-应答模式 将一组服务端和一组客户端相连,用于远程过程调用或任务分发。

  • 发布-订阅模式 将一组发布者和一组订阅者相连,用于数据分发。

  • 管道模式 使用扇入或扇出的形式组装多个节点,可以产生多个步骤或循环,用于构建并行处理架构。

我们在第一章中已经讲述了这些模式,不过还有一种模式是为那些仍然认为ZMQ是类似TCP那样点对点连接的人们准备的:

  • 排他对接模式 将两个套接字一对一地连接起来,这种模式应用场景很少,我们会在本章最末尾看到一个示例。

ZMQ的传输单位是消息,即一个二进制块。你可以使用任意的序列化工具,如谷歌的Protocal Buffers、XDR、JSON等,将内容转化成ZMQ消息。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM