史上最快消息内核——ZeroMQ(转)
打算学习一下ZeroMQ,好好的研究研究其代码,所以转这篇文章作为学习的开始…….
ZeroMQ是一个很有个性的项目,它原来是定位为“史上最快消息队列”,所以名字里面有“MQ”两个字母,但是后来逐渐演变发展,慢慢淡化了消息队列的身影,改称为消息内核,或者消息层了。从网络通信的角度看,它处于会话层之上,应用层之下,有了它,你甚至不需要自己写一行的socket函数调用就能完成复杂的网络通信工作。
借用官方的例子:
客户端(发送N个“Hello”消息到服务端,接受回应):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
// // Hello World client // Connects REQ socket to tcp://localhost:5555 // Sends "Hello" to server, expects "World" back // #include <zmq.h> #include <string.h> #include <stdio.h> #include <unistd.h> int main () {
void *context = zmq_init (1);
// Socket to talk to server
printf ( "Connecting to hello world server...\n" );
void *requester = zmq_socket (context, ZMQ_REQ);
zmq_connect (requester, "tcp://localhost:5555" );
int request_nbr;
for (request_nbr = 0; request_nbr != 10; request_nbr++) {
zmq_msg_t request;
zmq_msg_init_data (&request, "Hello" , 6, NULL, NULL);
printf ( "Sending request %d...\n" , request_nbr);
zmq_send (requester, &request, 0);
zmq_msg_close (&request);
zmq_msg_t reply;
zmq_msg_init (&reply);
zmq_recv (requester, &reply, 0);
printf ( "Received reply %d: [%s]\n" , request_nbr,
( char *) zmq_msg_data (&reply));
zmq_msg_close (&reply);
}
zmq_close (requester);
zmq_term (context);
return 0;
} |
服务端(接收客户端的消息,返回“World”给客户端):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// // Hello World server in C++ // Binds REP socket to tcp://*:5555 // Expects "Hello" from client, replies with "World" // #include <zmq.hpp> #include <unistd.h> #include <stdio.h> #include <string.h> 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);
printf ( "Received request: [%s]\n" ,
( char *) request.data ());
// Do some \'work\'
sleep (1);
// Send reply back to client
zmq::message_t reply (6);
memcpy (( void *) reply.data (), "World" , 6);
socket.send (reply);
}
return 0;
} |
一个套接字相关的调用都没有,一个网络程序就写好了,生活真美好啊。
与其他的消息队列相比,ZeroMQ有以下一些特点
1.点对点无中间节点。
传统的消息队列都需要一个消息服务器来存储转发消息。而ZeroMQ则放弃了这个模式,把侧重点放在了点对点的消息传输上,并且(试图)做到极致。以为消息服务器最终还是转化为服务器对其他节点的点对点消息传输上。ZeroMQ能缓存消息,但是是在发送端缓存。ZeroMQ里有水位设置的相关接口来控制缓存量。当然,ZeroMQ也支持传统的消息队列(通过zmq_device来实现)。
2.强调消息收发模式。
在点对点的消息传输上ZeroMQ将通信的模式做了归纳,比如常见的订阅模式(一个消息发多个客户),分发模式(N个消息平均分给X个客户)等等。下面是目前支持的消息模式配对,任何一方都可以做为服务端。
- PUB and SUB
- REQ and REP
- REQ and XREP
- XREQ and REP
- XREQ and XREP
- XREQ and XREQ
- XREP and XREP
- PUSH and PULL
- PAIR and PAIR
3.以统一接口支持多种底层通信方式(线程间通信,进程间通信,跨主机通信)。
如果你想把本机多进程的软件放到跨主机的环境里去执行,通常要将IPC接口用套接字重写一遍。非常麻烦。而有了ZeroMQ就方便多了,只要把通信协议从"ipc:///xxx"改为
"tcp://*.*.*.*:****"就可以了,其他代码通通不需要改,如果这个是从配置文件里读的话,那么程序就完全不要动了,直接复制到其他机器上就可以了。以为ZeroMQ为我们做了很多。
4.异步,强调性能。
ZeroMQ设计之初就是为了高性能的消息发送而服务的,所以其设计追求简洁高效。它发送消息是异步模式,通过单独出一个IO线程来实现,所以消息发送调用之后不要立刻释放相关资源哦,会出错的(以为还没发送完),要把资源释放函数交给ZeroMQ让ZeroMQ发完消息自己释放。
目前ZeroMQ还不是非常成熟(本文写作时最新版是2.0.10版),设计上还有一点点小缺陷,比如不能得到客户端的IP,丢消息等。不过,开发很活跃,很有潜力。另外,ZeroMQ配合Protocol buffer使用真是绝了。
from:http://blog.dccmx.com/2011/02/zeromq/
ZeroMQ的想法和初衷是好的,让你用串行的思路写异步的消息处理程序。并且在此基础之上提炼出了常见的消息模式,让你写起程序来更加省心。库替你完成了异步消息发送、故障恢复、甚至连磁盘的buffer都提供了。不可谓不全面。同时提供的接口非常简洁。
但是,用很少的接口封装复杂的逻辑通常的结果就是:当你的业务场景跟库的设计者设想的业务场景一样时,你回很爽。但当业务有差别,或者对细节有要求时,你会很惨。用trick能解决已经算是较好的情况了。
欺骗性质的封装。
当你zmq_connect返回成功的时候,不要高兴,这并不意味着你真的创建好链接了,因为即使你连一个根本没人监听的地址也会成功,为什么呢,因为ZeroMQ认为它提供了故障恢复自动重连的机制。后面会自动重连上的。可是如果是地址错了或者对方挂了,这辈子都不会连上了,这库就傻掉了。
再比如,你发个消息,又返回成功,也不要高兴,这并不意味着对方已经能收到了。因为消息是异步发送的,而且ZeroMQ提供了消息缓存,暂时发不出去,ZeroMQ认为后面是能发得出去的。于是消息被缓存起来。于是你想设下这个缓存,发现最低为1,也就是说至少缓存一条消息。如果这消息发不出去,你也收不回来。程序如果要停,消息就丢了。这跟ZeroMQ实现异步的方式有关。ZeroMQ是用线程的方式实现异步的。如果不缓存到IO线程,那还怎么个异步法。
过度抽象且不提供细节。
ZeroMQ建立在socket之上,隐藏了细节。但是,有时候当你需要这些细节的时候,就很纠结了。比如我收到一个消息,我想知道底层连接的信息,似乎是不大可能的,因为一个ZeroMQ套接字后面可能有很多底层的套接字(而且你不可控,也不可知)。类似的情况还有不能访问消息缓冲等等。
总之,ZeroMQ把网络通信从头到脚封装了一遍,提供了一个整体的解决方案。如果我们全盘接受这个解决方案,那么没有问题,但如果只需要一部分,并且对其他部分有要求,那么会很难受。如果应用场景稍微底层一些就会发现,本来可靠的tcp被这么一封装变得不可靠了,只能把它当作一个高级的UDP来用,弱要强调消息传输的可靠性,还需要用户做很多工作。略显无奈。
当然,一下抱怨也是基于特定场景(在一些场景下ZeroMQ还是很完美的)。说明ZeroMQ本不是为此而生。又何必强求。但是官方的态度似乎是想将它做成新一代的通用的socket,这应该还有很长的路要走。
from: http://blog.dccmx.com/2011/12/dark-side-of-zeromq/