作为一名linux下的c/c++后台服务器开发的工程师,最近才接触ZeroMQ,确实有点“相见恨晚”啊。不过“好饭不怕晚”,该好好的享受一下大餐了!
ROUTER/DEALER模式
常见的后台服务模型
作为一名C/C++后台服务器开发的工程师,一定很熟悉这样的服务框架:
一个IO线程负责监听和接受客户端的请求,接收完请求后,封装成一个任务(Task),然后丢入任务队列管理器(TaskManager)中,至于 怎么调度和管理这些任务(Task),任务管理器(TaskManager)就得采用一定的调度算法来管理和分派这些任务到线程池 (ThreadPool)中,然后由指定的工作线程(Worker)来完成这些任务。同时工作线程(Worker)和任务管理器 (TaskManager)还必须有一个通知机制,当Worker处理完Task的时候,能够高效的将结果给会到TaskManager,从而回复给请求 的客户端。
怎么样, 后台服务器开发还是很有技术含量的吧,但还是白菜价啊!自己手工实现上面的东东,怎么着都得捣鼓一阵子吧。无奈产品经理说,一阵子是多久,我要马上就好!工程师只能吓尿。。。
ZeroMQ搭建一个broker
但是,方法总比困难多。ZeroMQ来救场了。ROUTER/DEALER模式,就可以做一个broker,轻松解放生产力!
ROUTER其实指的是ZeroMQ里面的一种套接字类型( ZMQ_ROUTER类型)。这个套接字把接收到的请求(ZMQ_REQ)公平的排队分发,而且ROUTER还会标志客户的身份,从而确保能够将应答数据 给到客户端。这样就刚好可以解决了我们想要的“监听和接受客户端请求,并丢入任务管理器”。【这里的任务管理器指的是DEALER】
DEALER其实指的是ZeroMQ里面的一种套接字类型(ZMQ_DEALER)。这个套接字会把“任务”负载均衡的分发到后端的工作线程(或进 程)Worker的。并且,当DEALER接受到Worker的处理结果的时候,DEALER还会把处理结果数据传递给ROUTER,由ROUTER将应 答回给客户端。这样一来,DEALER也解决了我们想要的“任务管理调度和Worker分配工作的问题”。
ROUTER/DEALER的优点
没错,就是简单使用ZeroMQ提供的ROUTER/DEALER组合模式,可以轻松搭建一个高性能异步的C/C++后台服务框架。ROUTER可 以高效的接收客户端的请求,而DEALER可以负载均衡的调度后端Worker工作。当客户端的请求特别多,后端Worker处理不过来,需要增加 Worker的时候,也非常简单,新加入的Worker直接Connect到DEALER即可。如此运维起来也非常高效,后端可以非常简单的横向扩展!
友情提示
值的一提的是,ROUTER又叫做XREP,DEALER又叫做XREQ,刚开始学习的时候,看别人的代码,一头雾水。看了下zmq的api,才发现他们是一个东东。另外zmq的各个版本的api不兼容,这个得注意。我这次学习使用的是zeromq-4.0.4 版本。
/* Deprecated aliases */
#define ZMQ_XREQ ZMQ_DEALER
#define ZMQ_XREP ZMQ_ROUTER
搭建一个高性能的异步后台服务框架
框架图
ROUTER/DEALER模式搭建一个broker(Device)
这个Device很关键,起着承上启下的作用,没有它,那这个后台框架就不复存在了。虽然很关键,但是实现起来却非常的简单,简单的组合一下ZMQ_ROUTER和ZMQ_DEALER套接字即可。看一下代码吧:
【提示】这里暂且叫做deviceFrame.cpp吧。这里使用了zmq_poll来管理ZMQ_ROUTER和ZMQ_DEALER套接字的事 件,其实有更简单的api来帮助我们(后面再介绍)。不过zmq_poll的方式,对于我这个新手来说,类比epoll来理解,很合适。
代码:deviceFrame.cpp
#include
#include
#include
#include
#include
/*
* 将客户端请求公平的转发到Dealer。
* 然后由Dealer内部负载均衡的派发任务到各个Worker。
*/
int DoRouter(void* router, void* dealer)
{
while (1) {
int64_t more = 1;
zmq_msg_t message;
zmq_msg_init(&message);
int rc = zmq_msg_recv(&message, router, 0);
size_t more_size = sizeof(more);
zmq_getsockopt(router, ZMQ_RCVMORE, &more, &more_size);
zmq_msg_send(&message, dealer, more ? ZMQ_SNDMORE : 0);
printf("[%ld] router deliver request to dealer. rc = %d, more = %ld\n", time(NULL), rc, more);
zmq_msg_close(&message);
if (!more) {
break; // Last message part
}
}
printf("[%ld]----------DoRouter----------\n\n", time(NULL));
return 0;
}
/*
* Dealer将后端Worker的应答数据转发到Router。
* 然后由Router寻址将应答数据准确的传递给对应的client。
* 值得注意的是,Router对client的寻址方式,得看client的‘身份’。
* 临时身份的client,Router会为其生成一个uuid进行标识。
* 永久身份的client,Router直接使用该client的身份。
*/
int DoDealer(void* router, void* dealer)
{
while (1) {
int64_t more = 1;
zmq_msg_t message;
// Process all parts of the message
zmq_msg_init(&message);
int rc = zmq_msg_recv(&message, dealer, 0);
size_t more_size = sizeof (more);
zmq_getsockopt(dealer, ZMQ_RCVMORE, &more, &more_size);
zmq_msg_send(&message, router, more ? ZMQ_SNDMORE : 0);
printf("[%ld] dealer deliver reply to router. rc = %d, more = %ld\n", time(NULL), rc, more);
zmq_msg_close(&message);
if (!more) {
break; // Last message part
}
}
printf("[%ld]----------DoDealer----------\n\n", time(NULL));
return 0;
}
int main(int argc, char** argv)
{
if (argc < 5) {
printf("usage : %s router_ip router_port dealer_ip dealer_port\n", argv[0]);
return -1;
}
int major, minor, patch;
zmq_version (&major, &minor, &patch);
printf ("Current 0MQ version is %d.%d.%d\n", major, minor, patch);
printf("===========================================\n\n");
char addr[128] = {0};
void* context = zmq_ctx_new();
snprintf(addr, sizeof(addr), "tcp://%s:%s", argv[1], argv[2]);
void* router = zmq_socket(context, ZMQ_ROUTER);
int rc = zmq_bind(router, addr);
printf("[%ld] router bind %s %s.\n", time(NULL), addr, (rc ? "error" : "ok"));
if (rc) {
printf("[%ld] router bind error : %s\n", time(NULL), zmq_strerror(zmq_errno()));
zmq_close(router);
zmq_ctx_term(context);
return -2;
}
snprintf(addr, sizeof(addr), "tcp://%s:%s", argv[3], argv[4]);
void* dealer = zmq_socket(context, ZMQ_DEALER);
rc = zmq_bind(dealer, addr);
printf("[%ld] dealer bind %s %s.\n", time(NULL), addr, (rc ? "error" : "ok"));
if (rc) {
printf("[%ld] dealer bind error : %s\n", time(NULL), zmq_strerror(zmq_errno()));
zmq_close(router);
zmq_close(dealer);
zmq_ctx_term(context);
return -3;
}
zmq_pollitem_t items [] = {
{router, 0, ZMQ_POLLIN, 0}
, {dealer, 0, ZMQ_POLLIN, 0} };
while (1) {
rc = zmq_poll(items, sizeof(items)/sizeof(zmq_pollitem_t), 1000);
if (rc < 0) {
printf("[%ld] zmq_poll error: %d\n", time(NULL), rc);
break;
}
printf("[%ld] zmq_poll rc = %d\n", time(NULL), rc);
if (rc < 1) {
continue;
}
if (items[0].revents & ZMQ_POLLIN) {
/* router可读事件,说明有client请求过来了。 */
printf("[%ld] zmq_poll catch one router event!\n", time(NULL));
DoRouter(router, dealer);
}
else if (items[1].revents & ZMQ_POLLIN) {
/* dealer可读事件,说明有worker的应答数据到来了。 */
printf("[%ld] zmq_poll catch one dealer event!\n", time(NULL));
DoDealer(router, dealer);
}
else {
printf("[%ld] zmq_poll Don't Care this evnet!\n", time(NULL));
}
}
zmq_close(router);
zmq_close(dealer);
zmq_ctx_term(context);
printf("[%ld] good bye and good luck!\n", time(NULL));
return 0;
}
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include
#include
#include
#include
#include
/*
* 将客户端请求公平的转发到Dealer。
* 然后由Dealer内部负载均衡的派发任务到各个Worker。
*/
intDoRouter(void*router,void*dealer)
{
while(1){
int64_tmore=1;
zmq_msg_tmessage;
zmq_msg_init(&message);
intrc=zmq_msg_recv(&message,router,0);
size_tmore_size=sizeof(more);
zmq_getsockopt(router,ZMQ_RCVMORE,&more,&more_size);
zmq_msg_send(&message,dealer,more?ZMQ_SNDMORE:0);
printf("[%ld] router deliver request to dealer. rc = %d, more = %ld\n",time(NULL),rc,more);
zmq_msg_close(&message);
if(!more){
break;// Last message part
}
}
printf("[%ld]----------DoRouter----------\n\n",time(NULL));
return0;
}
/*
* Dealer将后端Worker的应答数据转发到Router。
* 然后由Router寻址将应答数据准确的传递给对应的client。
* 值得注意的是,Router对client的寻址方式,得看client的‘身份’。
* 临时身份的client,Router会为其生成一个uuid进行标识。
* 永久身份的client,Router直接使用该client的身份。
*/
intDoDealer(void*router,void*dealer)
{
while(1){
int64_tmore=1;
zmq_msg_tmessage;
// Process all parts of the message
zmq_msg_init(&message);
intrc=zmq_msg_recv(&message,dealer,0);
size_tmore_size=sizeof(more);
zmq_getsockopt(dealer,ZMQ_RCVMORE,&more,&more_size);
zmq_msg_send(&message,router,more?ZMQ_SNDMORE:0);
printf("[%ld] dealer deliver reply to router. rc = %d, more = %ld\n",time(NULL),rc,more);
zmq_msg_close(&message);
if(!more){
break;// Last message part
}
}
printf("[%ld]----------DoDealer----------\n\n",time(NULL));
return0;
}
intmain(intargc,char**argv)
{
if(argc<5){
printf("usage : %s router_ip router_port dealer_ip dealer_port\n",argv[0]);
return-1;
}
intmajor,minor,patch;
zmq_version(&major,&minor,&patch);
printf("Current 0MQ version is %d.%d.%d\n",major,minor,patch);
printf("===========================================\n\n");
charaddr[128]={0};
void*context=zmq_ctx_new();
snprintf(addr,sizeof(addr),"tcp://%s:%s",argv[1],argv[2]);
void*router=zmq_socket(context,ZMQ_ROUTER);
intrc=zmq_bind(router,addr);
printf("[%ld] router bind %s %s.\n",time(NULL),addr,(rc?"error":"ok"));
if(rc){
printf("[%ld] router bind error : %s\n",time(NULL),zmq_strerror(zmq_errno()));
zmq_close(router);
zmq_ctx_term(context);
return-2;
}
snprintf(addr,sizeof(addr),"tcp://%s:%s",argv[3],argv[4]);
void*dealer=zmq_socket(context,ZMQ_DEALER);
rc=zmq_bind(dealer,addr);
printf("[%ld] dealer bind %s %s.\n",time(NULL),addr,(rc?"error":"ok"));
if(rc){
printf("[%ld] dealer bind error : %s\n",time(NULL),zmq_strerror(zmq_errno()));
zmq_close(router);
zmq_close(dealer);
zmq_ctx_term(context);
return-3;
}
zmq_pollitem_titems[]={
{router,0,ZMQ_POLLIN,0}
,{dealer,0,ZMQ_POLLIN,0}};
while(1){
rc=zmq_poll(items,sizeof(items)/sizeof(zmq_pollitem_t),1000);
if(rc<0){
printf("[%ld] zmq_poll error: %d\n",time(NULL),rc);
break;
}
printf("[%ld] zmq_poll rc = %d\n",time(NULL),rc);
if(rc<1){
continue;
}
if(items[0].revents&ZMQ_POLLIN){
/* router可读事件,说明有client请求过来了。 */
printf("[%ld] zmq_poll catch one router event!\n",time(NULL));
DoRouter(router,dealer);
}
elseif(items[1].revents&ZMQ_POLLIN){
/* dealer可读事件,说明有worker的应答数据到来了。 */
printf("[%ld] zmq_poll catch one dealer event!\n",time(NULL));
DoDealer(router,dealer);
}
else{
printf("[%ld] zmq_poll Don't Care this evnet!\n",time(NULL));
}
}
zmq_close(router);
zmq_close(dealer);
zmq_ctx_term(context);
printf("[%ld] good bye and good luck!\n",time(NULL));
return0;
}
编译
[leoox@Study demo]$ g++ -o deviceFrame -I/home/leoox/local/zeromq-4.0.4/include/ deviceFrame.cpp /home/leoox/local/zeromq-4.0.4/lib/libzmq.a -lpthread -lrt
/home/leoox/local/zeromq-4.0.4/lib/libzmq.a(libzmq_la-ipc_listener.o): In function zmq::ipc_listener_t::set_address(char const*)':
/home/leoox/soft/zeromq-4.0.4/src/ipc_listener.cpp:127: warning: the use oftempnam’ is dangerous, better use mkstemp'
[leoox@Study demo]$
这样就得到了一个可执行程序deviceFrame,不过编译过程中,还是遇到了warning的,ZeroMQ用到了不安全的函数“tempnam”,无关大局,忽略之。
运行
将这个Device运行起来。这里叫它Device,一则是ZeroMQ里面有Device的说法,二则是确实像一个设备一样,高效的解放了劳动力。
从日志可以看到,我们的Device已经正常启动了,不过目前暂时没有请求进来,所以都是zmq_poll超时醒来(rc = 0,意味着没有任何事件到来。)
[leoox@Study demo]$ ./deviceFrame 127.0.0.1 9090 127.0.0.1 9091
Current 0MQ version is 4.0.4
===========================================
[1428333949] router bind tcp://127.0.0.1:9090 ok.
[1428333949] dealer bind tcp://127.0.0.1:9091 ok.
[1428333950] zmq_poll rc = 0
[1428333951] zmq_poll rc = 0
[1428333952] zmq_poll rc = 0
REP搭建Worker工作进程
为了方便演示和学习,这里采用另外一个进程的方式。而且这样的好处是,可以启动任意多个Worker,连接到上面的Device的DEALER中。
ZMQ_REP套接字,其实就是一个“应答”,即,把应答数据回复给ZMQ_REQ,他们是严格的一问一答的方式。不过组合上 ZMQ_ROUTER,ZMQ_DEALER模式后,后台Worker不再是服务的死穴,可以通过横向扩展多个Worker来提高处理ZMQ_REQ的能 力。(当然加上zmq_poll这种reactor多路复用机制性能更佳!)
代码:repWorker.cpp
#include
#include
#include
#include
#include
int main(int argc, char** argv)
{
if (argc < 4) {
printf("usage : %s ip port index\n", argv[0]);
return -1;
}
int major, minor, patch;
zmq_version (&major, &minor, &patch);
printf ("Current 0MQ version is %d.%d.%d\n", major, minor, patch);
printf("===========================================\n\n");
char addr[128] = {0};
snprintf(addr, sizeof(addr), "tcp://%s:%s", argv[1], argv[2]);
void* context = zmq_ctx_new();
void* worker = zmq_socket(context, ZMQ_REP);
int rec = zmq_connect(worker, addr);
if (rec) {
printf("zmq_connect %s error:%d\n", addr, rec);
zmq_close(worker);
zmq_ctx_term(context);
return -2;
}
printf("worker zmq_connect %s done!\n", addr);
char buf[128] = {0};
while (1) {
rec = zmq_recv(worker, buf, sizeof(buf), 0);
printf("[%ld] recv request(%s) from client, rec = %d, request.len = %d\n", time(NULL), buf, rec, strlen(buf));
snprintf(buf, sizeof(buf), "worker=%s&result=world&time=%ld", argv[3], time(NULL));
rec = zmq_send(worker, buf, strlen(buf), 0);
printf("[%ld] send reply(%s) to client, rec = %d, reply.len = %d\n", time(NULL), buf, rec, strlen(buf));
printf("[%ld]------------------------\n\n", time(NULL));
}
zmq_close(worker);
zmq_ctx_term(context);
printf("good bye and good luck!\n");
return 0;
}
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
38
39
40
41
42
43
44
45
46
47
48
49
50
#include
#include
#include
#include
#include
intmain(intargc,char**argv)
{
if(argc<4){
printf("usage : %s ip port index\n",argv[0]);
return-1;
}
intmajor,minor,patch;
zmq_version(&major,&minor,&patch);
printf("Current 0MQ version is %d.%d.%d\n",major,minor,patch);
printf("===========================================\n\n");
charaddr[128]={0};
snprintf(addr,sizeof(addr),"tcp://%s:%s",argv[1],argv[2]);
void*context=zmq_ctx_new();
void*worker=zmq_socket(context,ZMQ_REP);
intrec=zmq_connect(worker,addr);
if(rec){
printf("zmq_connect %s error:%d\n",addr,rec);
zmq_close(worker);
zmq_ctx_term(context);
return-2;
}
printf("worker zmq_connect %s done!\n",addr);
charbuf[128]={0};
while(1){
rec=zmq_recv(worker,buf,sizeof(buf),0);
printf("[%ld] recv request(%s) from client, rec = %d, request.len = %d\n",time(NULL),buf,rec,strlen(buf));
snprintf(buf,sizeof(buf),"worker=%s&result=world&time=%ld",argv[3],time(NULL));
rec=zmq_send(worker,buf,strlen(buf),0);
printf("[%ld] send reply(%s) to client, rec = %d, reply.len = %d\n",time(NULL),buf,rec,strlen(buf));
printf("[%ld]------------------------\n\n",time(NULL));
}
zmq_close(worker);
zmq_ctx_term(context);
printf("good bye and good luck!\n");
return0;
}
编译:
[leoox@Study demo]$ g++ -o repWorker -I/home/leoox/local/zeromq-4.0.4/include/ repWorker.cpp /home/leoox/local/zeromq-4.0.4/lib/libzmq.a -lpthread -lrt
/home/leoox/local/zeromq-4.0.4/lib/libzmq.a(libzmq_la-ipc_listener.o): In functionzmq::ipc_listener_t::set_address(char const*)’:
/home/leoox/soft/zeromq-4.0.4/src/ipc_listener.cpp:127: warning: the use of tempnam' is dangerous, better usemkstemp’
这样,我们就得到了工作进程repWorker了。
运行:
既然我们手中有资源,那我们就多启动几个repWorker来处理客户端的请求吧。先启动两个吧,repWorker的工作编号为1000和2000。后面方便观察。
启动工作编号为1000的工作进程:
[leoox@Study demo]$ ./repWorker 127.0.0.1 9091 1000
Current 0MQ version is 4.0.4
===========================================
worker zmq_connect tcp://127.0.0.1:9091 done!
启动工作编号为2000的工作进程:
[leoox@Study demo]$ ./repWorker 127.0.0.1 9091 2000
Current 0MQ version is 4.0.4
===========================================
worker zmq_connect tcp://127.0.0.1:9091 done!
随着工人到位,整个后台服务框架就这么简单的搞点了。可以开门大吉了!
REQ发送请求到后端
诚如上面所说的,ZMQ_REQ是“问”的套接字,它需要“答”套接字(ZMQ_REP)。不过目前ZMQ_REP已经被 ROUTER/DEALER华丽的包装成高富帅了。ZMQ_REQ只能通过联系ROUTER,然后由这个ROUTER/DEALER组成的DEVICE帮 忙传递“爱意”到达ZMQ_REP了,然后再默默的期待ROUTER传递ZMQ_REQ的“答复”。
代码:reqClient.cpp
#include
#include
#include
#include
#include
int main(int argc, char** argv)
{
if (argc < 4) {
printf("usage : %s ip port index\n", argv[0]);
return -1;
}
int major, minor, patch;
zmq_version (&major, &minor, &patch);
printf ("Current 0MQ version is %d.%d.%d\n", major, minor, patch);
printf("===========================================\n\n");
char addr[128] = {0};
snprintf(addr, sizeof(addr), "tcp://%s:%s", argv[1], argv[2]);
void* context = zmq_ctx_new();
void* request = zmq_socket(context, ZMQ_REQ);
int rec = zmq_connect(request, addr);
if (rec) {
printf("zmq_connect %s error:%d\n", addr, rec);
zmq_close(request);
zmq_ctx_term(context);
return -2;
}
printf("client zmq_connect %s done!\n", addr);
char buf[128] = {0};
while (1) {
snprintf(buf, sizeof(buf), "index=%s&cmd=hello&time=%ld", argv[3], time(NULL));
rec = zmq_send(request, buf, strlen(buf), 0);
printf("[%ld] send request(%s) to server, rec = %d, request.len = %d\n", time(NULL), buf, rec, strlen(buf));
rec = zmq_recv(request, buf, sizeof(buf), 0);
printf("[%ld] recv reply(%s) from server, rec = %d, reply.len = %d\n", time(NULL), buf, rec, strlen(buf));
printf("[%ld] --------------------------\n\n", time(NULL));
sleep(3);
}
zmq_close(request);
zmq_ctx_term(context);
printf("good bye and good luck!\n");
return 0;
}
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include
#include
#include
#include
#include
intmain(intargc,char**argv)
{
if(argc<4){
printf("usage : %s ip port index\n",argv[0]);
return-1;
}
intmajor,minor,patch;
zmq_version(&major,&minor,&patch);
printf("Current 0MQ version is %d.%d.%d\n",major,minor,patch);
printf("===========================================\n\n");
charaddr[128]={0};
snprintf(addr,sizeof(addr),"tcp://%s:%s",argv[1],argv[2]);
void*context=zmq_ctx_new();
void*request=zmq_socket(context,ZMQ_REQ);
intrec=zmq_connect(request,addr);
if(rec){
printf("zmq_connect %s error:%d\n",addr,rec);
zmq_close(request);
zmq_ctx_term(context);
return-2;
}
printf("client zmq_connect %s done!\n",addr);
charbuf[128]={0};
while(1){
snprintf(buf,sizeof(buf),"index=%s&cmd=hello&time=%ld",argv[3],time(NULL));
rec=zmq_send(request,buf,strlen(buf),0);
printf("[%ld] send request(%s) to server, rec = %d, request.len = %d\n",time(NULL),buf,rec,strlen(buf));
rec=zmq_recv(request,buf,sizeof(buf),0);
printf("[%ld] recv reply(%s) from server, rec = %d, reply.len = %d\n",time(NULL),buf,rec,strlen(buf));
printf("[%ld] --------------------------\n\n",time(NULL));
sleep(3);
}
zmq_close(request);
zmq_ctx_term(context);
printf("good bye and good luck!\n");
return0;
}
编译:
[leoox@Study demo]$ g++ -o reqClient -I/home/leoox/local/zeromq-4.0.4/include/ reqClient.cpp /home/leoox/local/zeromq-4.0.4/lib/libzmq.a -lpthread -lrt
/home/leoox/local/zeromq-4.0.4/lib/libzmq.a(libzmq_la-ipc_listener.o): In function zmq::ipc_listener_t::set_address(char const*)':
/home/leoox/soft/zeromq-4.0.4/src/ipc_listener.cpp:127: warning: the use oftempnam’ is dangerous, better use `mkstemp’
[leoox@Study demo]$
这样我们就顺利的得到请求客户端reqClient了。可以激动的将请求发往我们的高性能的异步后台了。哈哈!
运行:
我们的高性能异步后台已经饥渴难耐的等待客户端的请求了,来见证奇迹的时刻吧!把请求发往端口为9090的ROUTER吧。。。。
为了方便观察,我们把这个客户端的编号设置为1,看看效果!
[leoox@Study demo]$ ./reqClient 127.0.0.1 9090 1
Current 0MQ version is 4.0.4
===========================================
client zmq_connect tcp://127.0.0.1:9090 done!
[1428335643] send request(index=1&cmd=hello&time=1428335643) to server, rec = 33, request.len = 33
[1428335644] recv reply(worker=1000&result=world&time=1428335644) from server, rec = 40, reply.len = 40
[1428335644] ————————–
[1428335647] send request(index=1&cmd=hello&time=1428335647) to server, rec = 33, request.len = 33
[1428335647] recv reply(worker=2000&result=world&time=1428335647) from server, rec = 40, reply.len = 40
[1428335647] ————————–
[1428335650] send request(index=1&cmd=hello&time=1428335650) to server, rec = 33, request.len = 33
[1428335650] recv reply(worker=1000&result=world&time=1428335650) from server, rec = 40, reply.len = 40
[1428335650] ————————–
[1428335653] send request(index=1&cmd=hello&time=1428335653) to server, rec = 33, request.len = 33
[1428335653] recv reply(worker=2000&result=world&time=1428335653) from server, rec = 40, reply.len = 40
[1428335653] ————————–
哈哈,就是这么简单,我们的客户端正常的发请求到后端,并且得到了应答!
Device的运行情况:
截取一段日志,可见Device调度得很顺畅!
【思考】为什么router会传递3帧消息到DEALER呢?明明在客户端代码里面ZMQ_REQ只send了一帧数据呀!
[1428335906] zmq_poll rc = 0
[1428335907] zmq_poll rc = 0
[1428335908] zmq_poll rc = 0
[1428335908] zmq_poll rc = 1
[1428335908] zmq_poll catch one router event!
[1428335908] router deliver request to dealer. rc = 5, more = 1
[1428335908] router deliver request to dealer. rc = 0, more = 1
[1428335908] router deliver request to dealer. rc = 33, more = 0
[1428335908]———-DoRouter———-
[1428335908] zmq_poll rc = 1
[1428335908] zmq_poll catch one dealer event!
[1428335908] dealer deliver reply to router. rc = 5, more = 1
[1428335908] dealer deliver reply to router. rc = 0, more = 1
[1428335908] dealer deliver reply to router. rc = 40, more = 0
[1428335908]———-DoDealer———-
repWorker的工作情况:
截取一段日志,可见repWorker正在热火朝天的工作中,只需要专注的做自己的工作就可以了,其他的如何应答客户端,完全不用担心!
[1428336028] recv request(index=1&cmd=hello&time=14283360288336022) from client, rec = 33, request.len = 40
[1428336028] send reply(worker=1000&result=world&time=1428336028) to client, rec = 40, reply.len = 40
[1428336028]————————
[1428336034] recv request(index=1&cmd=hello&time=14283360348336028) from client, rec = 33, request.len = 40
[1428336034] send reply(worker=1000&result=world&time=1428336034) to client, rec = 40, reply.len = 40
[1428336034]————————
升级版的Device
在上面的示例中(deviceFrame.cpp),我们使用zmq_poll来自己实现了一个Device。虽然代码也很简洁明了,但其实你发现 我们在处理zmq_poll的就绪事件的时候,代码是大同小异的(其实就一个样)。所以这部分代码,zmq已经用一个API帮我们代劳了,进一步释放劳动 力!
这个API就是:
ZMQ_EXPORT int zmq_proxy (void *frontend, void *backend, void *capture);
来看一下升级版的Device的代码,为了区别开来,这里叫做deviceProxy.cpp
代码:deviceProxy.cpp
#include
#include
#include
#include
#include
int main(int argc, char** argv)
{
if (argc < 5) {
printf("usage : %s router_ip router_port dealer_ip dealer_port\n", argv[0]);
return -1;
}
int major, minor, patch;
zmq_version (&major, &minor, &patch);
printf ("Current 0MQ version is %d.%d.%d\n", major, minor, patch);
printf("===========================================\n\n");
char addr[128] = {0};
void* context = zmq_ctx_new();
snprintf(addr, sizeof(addr), "tcp://%s:%s", argv[1], argv[2]);
void* router = zmq_socket(context, ZMQ_ROUTER);
int rc = zmq_bind(router, addr);
printf("[%ld] router bind %s %s.\n", time(NULL), addr, (rc ? "error" : "ok"));
if (rc) {
printf("[%ld] router bind error : %s\n", time(NULL), zmq_strerror(zmq_errno()));
zmq_close(router);
zmq_ctx_term(context);
return -2;
}
snprintf(addr, sizeof(addr), "tcp://%s:%s", argv[3], argv[4]);
void* dealer = zmq_socket(context, ZMQ_DEALER);
rc = zmq_bind(dealer, addr);
printf("[%ld] dealer bind %s %s.\n", time(NULL), addr, (rc ? "error" : "ok"));
if (rc) {
printf("[%ld] dealer bind error : %s\n", time(NULL), zmq_strerror(zmq_errno()));
zmq_close(router);
zmq_close(dealer);
zmq_ctx_term(context);
return -3;
}
zmq_proxy(router, dealer, NULL);
zmq_close(router);
zmq_close(dealer);
zmq_ctx_term(context);
printf("[%ld] good bye and good luck!\n", time(NULL));
return 0;
}
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include
#include
#include
#include
#include
intmain(intargc,char**argv)
{
if(argc<5){
printf("usage : %s router_ip router_port dealer_ip dealer_port\n",argv[0]);
return-1;
}
intmajor,minor,patch;
zmq_version(&major,&minor,&patch);
printf("Current 0MQ version is %d.%d.%d\n",major,minor,patch);
printf("===========================================\n\n");
charaddr[128]={0};
void*context=zmq_ctx_new();
snprintf(addr,sizeof(addr),"tcp://%s:%s",argv[1],argv[2]);
void*router=zmq_socket(context,ZMQ_ROUTER);
intrc=zmq_bind(router,addr);
printf("[%ld] router bind %s %s.\n",time(NULL),addr,(rc?"error":"ok"));
if(rc){
printf("[%ld] router bind error : %s\n",time(NULL),zmq_strerror(zmq_errno()));
zmq_close(router);
zmq_ctx_term(context);
return-2;
}
snprintf(addr,sizeof(addr),"tcp://%s:%s",argv[3],argv[4]);
void*dealer=zmq_socket(context,ZMQ_DEALER);
rc=zmq_bind(dealer,addr);
printf("[%ld] dealer bind %s %s.\n",time(NULL),addr,(rc?"error":"ok"));
if(rc){
printf("[%ld] dealer bind error : %s\n",time(NULL),zmq_strerror(zmq_errno()));
zmq_close(router);
zmq_close(dealer);
zmq_ctx_term(context);
return-3;
}
zmq_proxy(router,dealer,NULL);
zmq_close(router);
zmq_close(dealer);
zmq_ctx_term(context);
printf("[%ld] good bye and good luck!\n",time(NULL));
return0;
}
编译:
g++ -o deviceProxy -I/home/leoox/local/zeromq-4.0.4/include/ deviceProxy.cpp /home/leoox/local/zeromq-4.0.4/lib/libzmq.a -lpthread -lrt
运行和使用方式,与deviceFrame一模一样,这里就不累赘了。
总结
第一次接触ZeroMQ,这么简单的代码就写出了一个高性能的异步后台服务框架,不得不说相当给力。不过作为一个队技术有追求的工程师,还是要探索一下ZeroMQ的ROUTER/DEALER模式的。
1、客户端(ZMQ_REQ)发送请求到ROUTER后,ROUTER是会对客户端进行身份表示的,正式因为有这个身份标示,所以ROUTER才有能力正确的把应答数据准确的传递到来源的客户端。
现在可以回答一下上文的一个思考题了—-ROUTER传递的3帧数据到底是什么数据:
A、第一帧是ROUTER自己加上的消息,这个是ROUTER对ZMQ_REQ所做的一个身份标识。说到身份标识,这里就引入到两种套接字。
一种叫做临时套接字,另外一种叫做永久套接字,他们的区别仅仅是是否使用ZMQ_IDENTITY。
没使用的即默认为临时套接字,我的这个文章里面的例子就是一个临时套接字。对于临时套接字,ROUTER会为它生成一个唯一的UUID,所以可以看到第一帧的长度为5,正是这个UUID。
而使用如下方式设定的套接字,则称为永久套接字。如果这样设置,那第一帧收到的消息长度就是13,而ROUTER也会直接使用www.leoox.com这个身份来标识这个客户端。
zmq_setsockopt(req, ZMQ_IDENTITY, “www.leoox.com”, 13);
B、第二帧是一个空帧,这是由REQ加上去的。可以理解为一个分隔符,当ROUTER遇到这个空帧后,就知道下一帧就是真正的请求数据了,这在多种组合模型里面尤其有用。
C、第三帧显然就是真正的请求数据了。这里的例子比较简单,复杂的例子,客户端可能会通过ZMQ_SNDMORE来发送多帧数据。如果是这样,那ROUTER还会继续收到第四帧,第五帧。。。数据的。
2、REQ到达ROUTER,ROUTER会公平排队,并公平的将REQ发往后端。但当后端能力不足的时候,导致ROUTER派发太慢的时候,ROUTER进入高水位的时候,ROUTER会丢弃掉消息的。所以这个得注意监控后台服务的性能。
3、DEALER会负载均衡的将任务派发后连接到它的各个工作Worker。至于这个负载均衡的算法,目前猜测应该是简单的轮询方式吧。知道的网友,欢迎指导!