Nest 接入消息队列 RabbitMQ

快速了解

RabbitMQ 是一套开源、轻量级消息队列 (消息中间件),是由 LShift 提供的一个 Advanced Message Queuing Protocol (AMQP 高级消息队列协议,也是一个消息代理的规范) 的开源实现,由以高性能、健壮以及可伸缩性出名的 Erlang 写成。它可以部署在分布式项目中,保证数据的可靠性。

RabbitMQ 优势很明显该有的都有,生态完备,缺点为资源占用相对高,学习成本高。

  • ActiveMQ:优点包括高可用性、支持多种消息协议,但社区对 ActiveMQ 5.x 的维护越来越少,且在高吞吐量场景下使用较少。
  • Kafka:以高吞吐量和低延迟著称,适合大数据实时计算和日志采集,但可能在单机超过 64 个队列/分区时CPU负载会明显升高,且社区更新较慢。
  • RocketMQ:具有高吞吐量、分布式架构和消息 0 丢失的优点,但支持的客户端语言不多,社区活跃度一般。

消息队列可以简单理解为异步的远程 RPC 调用的消息队列。

异步怎么理解:假设一个接口需要调取短信的 60s 的结果后,我们才能根据这个结果做对应的逻辑判断,这个时候如果一直等待那么也会一直占用线程、cpu 和内存资源,高并发的场景下会出问题。所以我们可以将此任务转到一个消息代理服务中,一旦回调成功然后再将消息传递到对应的接收方即可,这样就不会阻塞和占用资源了。

消息发送一般有发起方、也有接收方(队列),而接收方会存在于消息代理服务中(一般我们把消息发送到安装了消息中间件的服务叫做消息代理 message broker),那么中间一定有一个处理匹配规则 (exchange 路由)来约束哪个接收方消费消息,这种约束规则你可以简单理解这么几种: 1对1(direct),1对多(fanout),根据header、正则匹配 (topic)。

在官网可以简单了解 www.rabbitmq.com/tutorials

各消息队列的类型:

  1. 点对点
  2. 竞态关系的1对多,但只有一个接收方消费了消息
  3. 1对多,多方消费了消息,比如前端比较熟悉的事件的发布订阅,事件注册后,他会一直存在事件中心上,使用方可以监听订阅,一旦触发事件从map中得到对应的队列,按顺序执行回调。
  4. 1对多,路由匹配
  5. 1对多,正则模式匹配
  6. 相比点对点,服务端会给客户端对应的响应状态

消息队列最大的优势在于解耦、异步处理、流量削峰。同时框架层面,通过消息确认的回调机制也保证了数据回滚后可靠性、消息协议和数据格式的可扩展性、延迟队列等等优势。

docker 安装 RabbitMQ

js 复制代码
docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p15671:15671 -p 15672:15672 rabbit mq:management

在 15672 端口可以打开可视化界面管理消息队列,账号密码默认都为 guest

在后台我们只要创建 exchange 并配置类型、消息处理、参数等,创建 queue,通过binding 将 exchange 和 queue 绑定即可。

Nest 如何接入 RabbitMQ

对于开发者来说我们需要引入包、根据公司规范定义好匹配规则、定义 publisher(消息的生产者)、exchange(交换器)、queue、consumer、binding(消息队列和交换器之间的关联,Exchange 和 Queue 的绑定可以是多对多的关系)、VirtualHost(mq的虚拟机)、broker、channel(多路复用tcp中的虚拟双向数据流通道)的方法等。

Nest 接入文档 nest.nodejs.cn/microservic...

官网是通过 amqplib 和 amqp-connection-manager 核心库连接配置的,ampb协议的规范包和 amqp-connection-manager 基础连接管理已经满足了我们开发需求,但是它并没有装饰器、不同消息发送的传递模式,也就是上文说的fanout、direct等,所以我们得使用两外基于此的包。

js 复制代码
npm install ---save @golevelup/nestjs-rabbitmq

模块初始化

js 复制代码
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
import { Module } from '@nestjs/common';
import { MessagingController } from './messaging/messaging.controller';
import { MessagingService } from './messaging/messaging.service';

@Module({
  imports: [
    RabbitMQModule.forRoot(RabbitMQModule, {
      exchanges: [
        {
          name: 'exchange1',
          type: 'topic',
        },
      ],
      uri: 'amqp://rabbitmq:rabbitmq@localhost:5672',
      // 选择哪个信道传递
      channels: {
        'channel-1': {
          prefetchCount: 15,
          default: true,
        },
        'channel-2': {
          prefetchCount: 2,
        },
      },
      // 处理消息的数据格式,比如json序列化和反序列化
       deserializer: (message: Buffer, msg: ConsumeMessage) => {
        const decodedMessage = myCustomDeserializer(
          msg.toString(),
          msg.properties.headers
        );
        return decodedMessage;
      },
      serializer: (msg: any) => {
        const encodedMessage = myCustomSerializer(msg);
        return Buffer.from(encodedMessage);
      },
    }),
    RabbitExampleModule,
  ],
  providers: [MessagingService],
  controllers: [MessagingController],
})
export class RabbitExampleModule {}

订阅消息

js 复制代码
import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
import { Injectable } from '@nestjs/common';
import { ConsumeMessage } from 'amqplib';

@Injectable()
export class MessagingService {
  @RabbitSubscribe({
    exchange: 'exchange1',
    routingKey: 'subscribe-route',
    queue: 'subscribe-queue',
    assertQueueErrorHandler: myErrorHandler //错误处理
  })
  public async pubSubHandler(msg: {}, amqpMsg: ConsumeMessage) {
    console.log(`Correlation id: ${amqpMsg.properties.correlationId}`);
    if (someCondition) {
        return 42;
    } else if (requeueCondition) {
      // ack 确认机制
      // 没有成功收到消息,重新入队
      return new Nack(true);
    } else {
      // 没有成功收到消息
      return new Nack();
    }
  }
}

发送消息

js 复制代码
amqpConnection.publish('some-exchange', 'routing-key', { msg: 'hello world' });
// promise做法
const response = await amqpConnection.request<ExpectedReturnType>({
  exchange: 'exchange1',
  routingKey: 'rpc',
  payload: {
    request: 'val',
  },
  timeout: 10000, 
});

详细文档可以参阅于此 github.com/golevelup/n...

总结

最后总结下 NestJS 接入 RabbitMQ 主要包括以下几个步骤:

  • 安装:通过 npm 或 yarn 安装 @golevelup/nestjs-rabbitmq 库。
  • 配置:在 NestJS 模块中配置 RabbitMQ 连接和交换机信息。
  • 发送消息:使用服务和 AmqpConnection 发布消息到 RabbitMQ 交换机。
  • 订阅消息:使用 RabbitSubscribe 装饰器订阅队列并处理接收到的消息。
  • 错误处理:通过 Nack 实例控制消息确认行为,处理消息接收中的错误。
  • 高级功能:支持高级模式如 RPC 和竞争消费者,增强应用的异步处理能力
相关推荐
2401_857439693 小时前
Spring Boot新闻推荐系统:用户体验优化
spring boot·后端·ux
进击的女IT4 小时前
SpringBoot上传图片实现本地存储以及实现直接上传阿里云OSS
java·spring boot·后端
Мартин.4 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
一 乐5 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
昨天;明天。今天。5 小时前
案例-表白墙简单实现
前端·javascript·css
数云界5 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd5 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常5 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer5 小时前
Vite:为什么选 Vite
前端
小御姐@stella5 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js