浅析网络开发框架——ZeroMQ

1、背景介绍
     据官方文档介绍,ZeroMQ是一个可伸缩的分布式或者高并发的异步网络消息库。不同于其他的服务,例如RabbitMQ等消息队列服务,以一种可独立运营的服务存在,ZeroMQ是一套高效的socket library,是对BSD socket进行的上层封装。在传统的BSD网络开发模型中,采用的是socket与socket之间的消息传输,即1:1的消息传输链接,在ZeroMQ中是node与node之间的消息传输,node之间存在多条数据链接,即N: M的消息传输链接。ZeroMQ在底层实现了关于进程通信、网络通信、线程通信等各种细节的封装,让开发者更多的关注应用层的开发。

2、ZeroMQ的主要特点
    1)I/O操作属于后台异步操作,同时采用的是无锁数据结构,提高应用的高并发性;
    2)存在断线重连机制,Server、Client启动的无序性;
    3)消息的消费者处理速度比较慢时,会导致消息的生产者阻塞或者,可以在使用过程中进行设置;
    4)消息内容可以使用任何的格式,框架本身不对消息格式做任何的限制;

3、ZeroMQ的基本使用模型
    ZeroMQ提供了三个经常使用的基本模型,分别为:Request—Reply、Publish—Subscribe、Parallel PipeLine三种工作模型,通过这三种工作模型可以衍生出很多的使用模型。下面分别对这三种模型进行简单的介绍:
    1)Request—Reply模型
    Request—Reply模型与传统的BSD网络开发模型类似,也就是俗称的“应答模型”,具体模型如下图所示
浅析网络开发框架——ZeroMQ_第1张图片
   Server代码如下:
#include 
#include 
#include 
#include 
#include 

int main (void)
{
    //  Socket to talk to clients
    void *context = zmq_ctx_new ();
    void *responder = zmq_socket (context, ZMQ_REP);
    int rc = zmq_bind (responder, "tcp://*:5555");
    assert (rc == 0);

    while (1) {
        char buffer [10];
        zmq_recv (responder, buffer, 10, 0);
        printf ("Received Hello\n");
        sleep (1);          //  Do some 'work'
        zmq_send (responder, "World", 5, 0);
    }
    return 0;
}

Client代码如下:
#include 
#include 
#include 
#include 

int main (void)
{
    printf ("Connecting to hello world server...\n");
    void *context = zmq_ctx_new ();
    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++) {
        char buffer [10];
        printf ("Sending Hello %d...\n", request_nbr);
        zmq_send (requester, "Hello", 5, 0);
        zmq_recv (requester, buffer, 10, 0);
        printf ("Received World %d\n", request_nbr);
    }
    zmq_close (requester);
    zmq_ctx_destroy (context);
    return 0;
}

    在使用该模型过程中存在几个注意事项:
    a)必须采用严格的发送——接受的顺序,否则会导致此次的发送或者接受失败;
    b)区别传统的BSD开发,Server先启动,Client再启动的流程,在ZeroMQ中Server与Client启动顺序没有要求;
    c)消息内容的无关性,ZeroMQ不关心传输数据的具体内容,由发送或者接收方负责编码或者解析;


2)Pub-Sub模型
    Pub-Sub模型是一个服务器将消息发送给多个客户端,类似于群发短信的业务场景
浅析网络开发框架——ZeroMQ_第2张图片
   
  Pub侧代码如下:
  
#include "zhelpers.h"

int main (void)
{
    //  Prepare our context and publisher
    void *context = zmq_ctx_new ();
    void *publisher = zmq_socket (context, ZMQ_PUB);
    int rc = zmq_bind (publisher, "tcp://*:5556");
    assert (rc == 0);
    rc = zmq_bind (publisher, "ipc://weather.ipc");
    assert (rc == 0);

    //  Initialize random number generator
    srandom ((unsigned) time (NULL));
    while (1) {
        //  Get values that will fool the boss
        int zipcode, temperature, relhumidity;
        zipcode     = randof (100000);
        temperature = randof (215) - 80;
        relhumidity = randof (50) + 10;

        //  Send message to all subscribers
        char update [20];
        sprintf (update, "%05d %d %d", zipcode, temperature, relhumidity);
        s_send (publisher, update);
    }
    zmq_close (publisher);
    zmq_ctx_destroy (context);
    return 0;
}

  Sub侧代码如下:
   
#include "zhelpers.h"

int main (int argc, char *argv [])
{
    //  Socket to talk to server
    printf ("Collecting updates from weather server…\n");
    void *context = zmq_ctx_new ();
    void *subscriber = zmq_socket (context, ZMQ_SUB);
    int rc = zmq_connect (subscriber, "tcp://localhost:5556");
    assert (rc == 0);

    //  Subscribe to zipcode, default is NYC, 10001
    char *filter = (argc > 1)? argv [1]: "10001 ";
    rc = zmq_setsockopt (subscriber, ZMQ_SUBSCRIBE,
                         filter, strlen (filter));
    assert (rc == 0);

    //  Process 100 updates
    int update_nbr;
    long total_temp = 0;
    for (update_nbr = 0; update_nbr < 100; update_nbr++) {
        char *string = s_recv (subscriber);

        int zipcode, temperature, relhumidity;
        sscanf (string, "%d %d %d",
            &zipcode, &temperature, &relhumidity);
        total_temp += temperature;
        free (string);
    }
    printf ("Average temperature for zipcode '%s' was %dF\n",
        filter, (int) (total_temp / update_nbr));

    zmq_close (subscriber);
    zmq_ctx_destroy (context);
    return 0;
}

   Pub-Sub模型使用过程中的注意事项为:
    a)需要在服务中显式设置服务是PUB还是SUB,即在服务中需要明确在系统中扮演的角色;
    b)扮演PUB角色的服务,不可以调用zmq_recv,扮演SUB角色的服务,不可以调用zmq_send角色,否则会导致服务出错;
    c)PUB允许存在多个SUB服务,SUB服务也允许从多个PUB中订阅数据;
    d)使用这种模型最适合的场景为:SUB在启动时,不关心PUB已经发送的数据内容;
    e)PUB在没有接收到SUB连接请求的情况下,PUB会丢弃全部的数据;
    f)如果SUB的处理效率比较慢,PUB侧会维持消息队列;
    g)根据设置的过滤条件的不同,过滤条件生效的位置不同;

    3)Parallel PipeLine
    并发模型主要用于并发任务执行的场景中,例如Storm等并行计算的模型框架中均使用到该模型,详细的模型图如下所示
浅析网络开发框架——ZeroMQ_第3张图片
在并发模型中,存在三类服务角色:任务分发、任务执行、任务结果整理共三个角色
任务分发代码:
#include "zhelpers.h"

int main (void)
{
    void *context = zmq_ctx_new ();

    //  Socket to send messages on
    void *sender = zmq_socket (context, ZMQ_PUSH);
    zmq_bind (sender, "tcp://*:5557");

    //  Socket to send start of batch message on
    void *sink = zmq_socket (context, ZMQ_PUSH);
    zmq_connect (sink, "tcp://localhost:5558");

    printf ("Press Enter when the workers are ready: ");
    getchar ();
    printf ("Sending tasks to workers...\n");

    //  The first message is "0" and signals start of batch
    s_send (sink, "0");

    //  Initialize random number generator
    srandom ((unsigned) time (NULL));

    //  Send 100 tasks
    int task_nbr;
    int total_msec = 0;     //  Total expected cost in msecs
    for (task_nbr = 0; task_nbr < 100; task_nbr++) {
        int workload;
        //  Random workload from 1 to 100msecs
        workload = randof (100) + 1;
        total_msec += workload;
        char string [10];
        sprintf (string, "%d", workload);
        s_send (sender, string);
    }
    printf ("Total expected cost: %d msec\n", total_msec);

    zmq_close (sink);
    zmq_close (sender);
    zmq_ctx_destroy (context);
    return 0;
}

任务执行代码:
int main (void)
{
    //  Socket to receive messages on
    void *context = zmq_ctx_new ();
    void *receiver = zmq_socket (context, ZMQ_PULL);
    zmq_connect (receiver, "tcp://localhost:5557");

    //  Socket to send messages to
    void *sender = zmq_socket (context, ZMQ_PUSH);
    zmq_connect (sender, "tcp://localhost:5558");

    //  Process tasks forever
    while (1) {
        char *string = s_recv (receiver);
        printf ("%s.", string);     //  Show progress
        fflush (stdout);
        s_sleep (atoi (string));    //  Do the work
        free (string);
        s_send (sender, "");        //  Send results to sink
    }
    zmq_close (receiver);
    zmq_close (sender);
    zmq_ctx_destroy (context);
    return 0;
}

任务结果搜集代码:
#include "zhelpers.h"

int main (void)
{
    //  Prepare our context and socket
    void *context = zmq_ctx_new ();
    void *receiver = zmq_socket (context, ZMQ_PULL);
    zmq_bind (receiver, "tcp://*:5558");

    //  Wait for start of batch
    char *string = s_recv (receiver);
    free (string);

    //  Start our clock now
    int64_t start_time = s_clock ();

    //  Process 100 confirmations
    int task_nbr;
    for (task_nbr = 0; task_nbr < 100; task_nbr++) {
        char *string = s_recv (receiver);
        free (string);
        if ((task_nbr / 10) * 10 == task_nbr)
            printf (":");
        else
            printf (".");
        fflush (stdout);
    }
    //  Calculate and report duration of batch
    printf ("Total elapsed time: %d msec\n",
        (int) (s_clock () - start_time));

    zmq_close (receiver);
    zmq_ctx_destroy (context);
    return 0;
}

参考文献:
    http://zguide.zeromq.org/page:all  

你可能感兴趣的:(Linux)