[nodejs] RibbitMQ nodejs实践

89 阅读6分钟

最近心血来潮,突然对RibbitMQ来了兴趣,其实在很早以前,我就大致了解过mq的工作原理,但是一直自从毕业工作到现在,一直都没有在实际的业务场景中使用过,所以搁置了好久,最近正好有空抽出了些时间,于是决定学习一波并记录下来,方便日后有需要的时候拿出来看看,同时也给社区的小伙伴们提供一些参考,如有错误的地方,还请指正出来,谢谢!


首先,学习RibbitMQ,我们得先知道什么是RibbitMQ:

RabbitMQ 是一个开源的消息代理软件,也被称为消息队列系统。它的主要功能是实现不同系统之间的通信,通过发送和接收消息来解耦和异步化应用程序。RabbitMQ 使用高级消息队列协议(AMQP,Advanced Message Queuing Protocol)作为其核心协议。

RibbitMQ的核心概念

  1. 生产者(Producer):
    负责发送消息的应用或服务。生产者将消息发送到 RabbitMQ。
  2. 消费者(Consumer):
    负责接收和处理消息的应用或服务。消费者从 RabbitMQ 中获取消息。
  3. 队列(Queue):
    消息的存储地点。生产者将消息发送到队列,消费者从队列中读取消息。
  4. 交换机(Exchange):
    生产者将消息发送到交换机,交换机根据绑定规则(Routing Rules)决定将消息发送到哪个队列。
  5. 绑定(Binding):
    交换机和队列之间的关联规则,决定消息的路由方式。
  6. 路由键(Routing Key):
    消息的路由规则,用于匹配绑定规则。
  7. 消息确认(Acknowledgment):
    RabbitMQ 支持消息确认机制,确保消息从队列成功传递给消费者。

主要特点

  1. 跨语言支持:
    RabbitMQ 支持多种编程语言(如 Python、Java、Go、C# 等),适合多样化的开发需求。
  2. 灵活的路由:
    提供多种路由机制(如直连、主题、扇出、头部路由等),满足复杂的消息传递需求。
  3. 高可用性:
    RabbitMQ 支持集群模式,确保系统的高可用性和可靠性。
  4. 插件支持:
    提供丰富的插件,例如监控插件、Web 管理界面、分布式功能等。
  5. 消息持久化:
    支持消息持久化到磁盘,避免消息因系统宕机而丢失。

常见使用场景

  1. 任务队列:
    例如处理耗时任务时,将任务消息放入队列,由后台服务异步处理。
  2. 异步通信:
    系统之间解耦,生产者发送消息后立即返回,不等待消费者处理完成。
  3. 消息广播:
    通过扇出交换机将消息广播到多个队列,支持实时通知和多订阅者场景。
  4. 工作负载分发:
    在多消费者之间分配任务,平衡负载。

以上为RabbitMQ的一些核心概念以及常见的使用场景。接下来,我们开始安装RibbitMQ并使用。

RibbitMQ安装

打开RibbitMQ官方文档 进入到以下页面

image.png 选择对应的操作系统下载,我这边使用的macOS的操作系统,所以直接使用brew安装即可

brew install rabbitmq

如果是其他系统的小伙伴,可选择对应的操作系统进行下载即可,这里就不展开说明了。 下载完成后,使用以下命令启动RibbitMQ:

brew services start rabbitmq

此时,我们访问http://localhost:15672/#/来到以下页面

image.png 账号密码默认都是guest,登录成功后,可以看到MQ的可视化面板如下图所示:

image.png 至此,RibbitMQ,安装已完成。


接下来,我们实现一个小demo,实现将订单通过api发送到RibbitMQ队列中,并实现从RibbitMQ队列消费订单数据,模拟订单支付处理的过程。

首先,我们安装依赖:

npm install amqplib express

而后编写send.js 消息发送方(生产者)

const express = require('express');
const amqp = require('amqplib/callback_api');
const app = express();
app.use(express.json());

let channel = null;

// 连接 RabbitMQ 并创建一个频道
amqp.connect('amqp://localhost:5672', (error, connection) => {
    if (error) {
        throw error;
    }
    connection.createChannel((err, ch) => {
        if (err) {
            throw err;
        }
        channel = ch;
        // 创建队列
        const queue = 'orderQueue';

        channel.assertQueue(queue, { durable: true });
    });
});


// 订单服务 API
app.post('/order', (req, res) => {
    const order = { userId: req.body.userId, productId: req.body.productId };

    // 将订单发送到 RabbitMQ 队列
    channel.sendToQueue('orderQueue', Buffer.from(JSON.stringify(order)), {
        persistent: true // 持久化消息(避免服务器宕机) 底层原理是 存储在磁盘
    });
    console.log("Order sent to queue:", order);

    res.status(201).json({ message: 'Order placed successfully!' });
});

// 启动订单服务
app.listen(3008, () => {
    console.log('Order service is running on port 3008');
});

启动send服务

node send.js

通过apifox去请求order接口: image.png 此时,控制台将输出刚刚发送到RibbitMQ队列的信息

image.png 同时,我们可以通过可视化控制面板看到,我们刚刚创建的队列及待消费的数据数量监控

image.png


接下来,我们编写receive.js用于接收队列中的消息,并模拟处理的过程:

// payment-service.js 消费者
const amqp = require('amqplib/callback_api');

// 连接 RabbitMQ 并创建一个频道
amqp.connect('amqp://localhost:5672', (error, connection) => {
    if (error) {
        throw error;
    }
    connection.createChannel((err, channel) => {
        if (err) {
            throw err;
        }

        const queue = 'orderQueue';
        
     /**
      *  durable: true:确保消息在服务器重启时不会丢失,将队列设置为持久性。
      *  队列的结构和属性仍然会被保留,底层原理是,存储在磁盘。 缺点占用磁盘空间
      *  优点:
      *  持久性队列:适合需要确保队列结构不丢失的场景。
      *  持久性消息:适合需要保证消息在系统崩溃时不丢失的场景
      *  缺点:
      *  每条消息都需要写入磁盘,可能导致延迟增加
      *  持久性消息会占用更多的磁盘空间
      *  */

        // 确保队列存在
        channel.assertQueue(queue, {
            durable: true // 队列和交换机的持久化
        });

        // 从队列中消费消息
        console.log('Waiting for messages in %s. To exit press CTRL+C', queue);
        channel.consume(queue, (msg) => {
            const order = JSON.parse(msg.content.toString());
            console.log('Received order:', order);

            // 假设支付处理逻辑
            setTimeout(() => {
                console.log('Payment processed for order:', order);
                channel.ack(msg); // 确认消息处理完成
            }, 1000);
        }, {
            noAck: false // 确保消息处理完成后才确认
        });
    });
});

运行receive.js程序

node receive.js

此时,控制台将输出程序中消费队列中的数据的打印内容,如下:

image.png (前面三个空对象是因为我在调用api时没有往body传值)
可以发现,刚刚我们send进队列中的五条订单数据,已经被消费了,同时也可以在可视化控制面板中看到:

image.png


以上,就是RibbitMQ的最简单工作原理。当然,这只是冰山一角,后续还有很多的知识,诸如交换机,死信队列,以及一些常见的参数,例如nack() 和 noAck()的区别等等,这些都是需要继续进行深入学习的,一起加油吧!