消息队列是什么?以及其工具使用的具体例子(c++)

消息队列

在计算机科学中,消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。

它是一个类似中间组件的东西,它可以被不同的服务,不同的线程,不同的进程锁调用。它是一种传送数据的中间件,帮助数据从一个实体中往另一个实体中过渡。一般适用于一个实体往队列里发送数据,另外的几个实体要往队列里读取数据。
广义的消息队列:KafKa、RabbitMQ、RocketMQ等,还有ZeroMQ这个特殊的消息队列。
一个合格的消息队列一般都会遵循以下几种原则:

1)可靠性,消息不能丢失

2)支持集群

3)性能足够好

4)开源

使用场景

  1. 异步处理
    将必要的步骤和次要的步骤分开来,比如直播场景中,打赏直播间礼物有三个步骤 a. 扣除金币 b. 发送礼物消 c.计算礼物榜单。如果我们按照平时的流程来做就是等待完这些后端的请求全都处理完之后再返回去,这里会造成客户端等待时延高的问题出现。如果引入了消息队列了,就可以把后面两步放到消息队列中。

  2. 发布订阅

  3. 高并发缓冲

  4. 秒杀系统
    比如要做一个秒杀系统,需要做到一个高速响应,这个时候,就要把服务的内容拆开来。
    在用户下单的时候只是将库存来进行计算,之后的订单发送和短信通知和统计,这些可以丢到消息队列里面,后端作为生产者将已经秒杀成功的请求来发送到消息队列,消费者有订单的服务和短信的服务有统计的服务,这些服务就从消息队列里面取数据然后做相应的处理。这里的组件都是跨服务的,不是在一个服组件里面去做的。

  5. 流量控制(削峰)
    在大流量的场景下
    使用消息队列隔离网关和后端服务,以达到流量控制和保护后端服务的目的。
    网关将用户数据发送到消息队列,然后服务从消息队列中取数据其处理。这个服务可以是多台机器,如果服务处理不过来的时候消息队列的数量会不断的增长,最终可能会超出消息队列里所能存储的大小,或者超出了消息队列的性能巅峰,这个时候就可以通过消息队列来拒绝服务来削峰(即限流),如果削峰的情况出现的话就要进行扩容了。

  6. 解耦
    早期的系统,发布订阅的时候(假设A->B)是要在A上配也要在B上配,如果新加一个订阅者比如C,也需要在A上写上C的服务API然后再调用,这样非常的麻烦。为了降低耦合度,需要把A的消息直接打到消息队列上,然后C只用从消息队列上去取数据,以后无论是新加入D,E,F,G也都是如此,这样就实现了解耦。

  7. 普通发布

ZeroMQ

这一个消息队列非常的特殊和它传统意义上的消息队列服务有点不一样,实际上他根本不是一个服务器,它就是类似在Socket API上多加了一层封装。将网络通讯、进程通讯和线程通讯抽象为统一的API接口。它和Socket API的区别就是,我们的Socket是端对端的,而ZMQ是可以N:M的。点对点连接需要显式地建立连接、销毁连接、选择协议(TCP/UDP)和处理错误等,而ZMQ屏蔽了这些细节,让你的网络编程更为简单。ZMQ用于node与node间的通信,node可以是主机或者是进程。ZMQ在底层实现了关于进程通信、网络通信、线程通信等各种细节的封装,让开发者更多的关注应用层的开发。
2、ZeroMQ的主要特点
1)I/O操作属于后台异步操作,同时采用的是无锁数据结构,提高应用的高并发性;
2)存在断线重连机制,Server、Client启动的无序性;
3)消息的消费者处理速度比较慢时,会导致消息的生产者阻塞或者,可以在使用过程中进行设置;
4)消息内容可以使用任何的格式,框架本身不对消息格式做任何的限制
ZMQ有三个基本的模型

  1. Request-Reply
    它是传统的应答模式,要严格的采用发送—接受的顺序,否则会导致此次的发送或者接受失败;它的Server和Cilent之间没有启动顺序的需求,ZeroMQ不关心传输数据的具体内容,由发送或者接收方负责编码或者解析;这里放c++的程序实例。
/**************************服务端****************************/
 #include <zmq.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
 #include <assert.h>

 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;
 }
/****************************客户端*********************************/
 #include <zmq.h>
 #include <string.h>
  #include <stdio.h>
  #include <unistd.h>

 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;
 }

  1. Publisher-Subscriber模型(发布订阅)

    这个模型可以允许服务器将消息发送给多个客户端,类似于群发短信的业务场景,发布端单向分发数据,且不关心是否把全部信息发送给订阅端。如果发布端开始发布信息时,订阅端尚未连接上来,则这些信息会被直接丢弃。订阅端未连接导致信息丢失的问题,可以通过与请求回应模型组合来解决。订阅端只负责接收,而不能反馈,且在订阅端消费速度慢于发布端的情况下,会在订阅端堆积数据。该模型主要用于数据分发。天气预报、微博明星粉丝可以应用这种经典模型。

    #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;
    }
    
  2. Parallel Pipeline(并发模型)

主要用于任务执行的场景中,Server端作为Push端,而Client端作为Pull端,如果有多个Client端同时连接到Server端,则Server端会在内部做一个负载均衡,采用平均分配的算法,将所有消息均衡发布到Client端上。与发布订阅模型相比,推拉模型在没有消费者的情况下,发布的消息不会被消耗掉;在消费者能力不够的情况下,能够提供多消费者并行消费解决方案。该模型主要用于多任务并行。

// taskvent
#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 msec
    for (task_nbr = 0; task_nbr < 100; task_nbr++) {
        int workload;
        //  Random workload from 1 to 100 msec
        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);
    sleep (1);              //  Give 0MQ time to deliver

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

// taskwork
#include "zhelpers.h"

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

    //  Socket to receive messages on
    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);
        //  Simple progress indicator for the viewer
        fflush (stdout);
        printf ("%s.", string);

        //  Do the work
        s_sleep (atoi (string));
        free (string);

        //  Send results to sink
        s_send (sender, "");
    }
	zmq_close (receiver);
    zmq_close (sender);
    zmq_ctx_destroy (context);
    return 0;
}

// tasksink
#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;
}

// result
// #1个工作人员
// 总计经过时间:5034毫秒
// #2个工作人员
// 总计经过时间:2421毫秒
// #4个工作人员
// 总计经过时间:1018毫秒

RabbitMQ

RabbitMQ是一个老牌的MQ,它是使用Erlang编写的,RabbitMQ的slogan是"Messaging that just works",“开箱即用的消息队列”,轻量级,容易部署和使用。RabbitMQ的特点是它有一个Exchange模块,支持灵活的配置,这是和其他消息队列不一样的地方。RabbitMQ对消息堆积不友好,如果有大量消息堆积,性能会急剧下降。RabbitMQ的编程语言是Erlang,这是一个非常小众的编程语言,而且学习曲线陡峭,如果要做二次开发,对开发人员是一个挑战

Kafka

kafka是Apache的顶级项目,最早是LinkedIn开发的,设计的目的是用于处理海量的日志。在早期版本中为了性能,设计方面做了牺牲,导致消息可靠性不能保证。后来逐渐补齐短板,目前Kafaka已经是一个成熟的消息队列,在可靠性,稳定性以及性能等方面已经可以满足大部分业务需求。
Kafka还有一大优势,Kafka与周边生态的兼容性是最好的,没有之一,尤其是大数据和流计算领域,几乎开源的软件系统都会支持Kafaka

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值