NestJs 使用 RabbitMQ

既然是使用 RabbitMQ 那先不管其他的 把 RabbitMQ 裝上再說

RabbitMQ 安裝

這裏直接找他們官網就行

https://www.rabbitmq.com/download.html

這裏我們選擇使用 docker 安裝 快捷方便

這裏直接參考:

https://juejin.cn/post/7198430801850105916

我們要站在巨人的肩膀上,快速學習,具體命令

RabbitMQ docker方式安裝

# 下載最新的代碼 management 的鏡像
docker pull rabbitmq:management
# 創建數據卷
docker volume create rabbitmq-home
# 啓動容器
docker run -id --name=rabbitmq -v rabbitmq-home:/var/lib/rabbitmq -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin rabbitmq:management

這裏除了掛載數據卷之外,還暴露了兩個端口,以及設定了兩個環境變量:

  • 15672端口:RabbitMQ的管理頁面端口
  • 5672端口:RabbitMQ的消息接收端口
  • RABBITMQ_DEFAULT_USER環境變量:指定RabbitMQ的用戶名,這裏我指定爲admin,大家部署時替換成自己定義的
  • RABBITMQ_DEFAULT_PASS環境變量:指定RabbitMQ的密碼,這裏我指定爲admin,大家部署時替換成自己定義的

這樣容器就部署完成了!在瀏覽器訪問你的服務器地址:15672即可訪問到RabbitMQ的管理界面,用戶名和密碼即爲剛剛指定的環境變量的配置值。

這裏沒有指定LANG=C.UTF-8,是因爲RabbitMQ容器默認就是這個語言環境,無需我們再設定。

image-20230506164331483

訪問管理頁面

http://localhost:15672/
用戶名:admin
密碼:admin

image-20230506164730603

可以看到已經進去了

前置知識

RabbitMQ的exchange、bindingkey、routingkey的關係

https://zhuanlan.zhihu.com/p/37198933 原文

https://www.cnblogs.com/makalochen/p/17378002.html 轉載

總之:

從 AMQP 協議可以看出,Queue、Exchange 和 Binding 構成了 AMQP 協議的核心

  • Producer:消息生產者,即投遞消息的程序。

  • Broker:消息隊列服務器實體。

    • Exchange:消息交換機,它指定消息按什麼規則,路由到哪個隊列。
    • Binding:綁定,它的作用就是把 Exchange 和 Queue 按照路由規則綁定起來。
    • Queue:消息隊列載體,每個消息都會被投入到一個或多個隊列。
  • Consumer:消息消費者,即接受消息的程序。

Binding 表示 Exchange 與 Queue 之間的關係,

我們也可以簡單的認爲隊列對該交換機上的消息感興趣,

綁定可以附帶一個額外的參數 RoutingKey。

Exchange 就是根據這個 RoutingKey 和當前 Exchange 所有綁定的 Binding 做匹配,

如果滿足匹配,就往 Exchange 所綁定的 Queue 發送消息,

這樣就解決了我們向 RabbitMQ 發送一次消息,可以分發到不同的 Queue。

RoutingKey 的意義依賴於交換機的類型。

amqb api 文檔

https://amqp-node.github.io/amqplib/channel_api.html

只有看了官方文檔才能更正確的使用

NesJs 使用 mq 文檔

https://docs.nestjs.cn/9/microservices?id=rabbitmq

日誌依賴

https://www.npmjs.com/package/winston

https://github.com/winstonjs/winston

https://github.com/gremo/nest-winston

https://docs.nestjs.cn/9/techniques?id=日誌

https://juejin.cn/post/7187910528918880311

npm install --save nest-winston winston winston-daily-rotate-file

NestJs 中使用

安裝依賴包

npm i --save amqplib amqp-connection-manager @nestjs/microservices

上面三個包基礎包,這裏還有方便的包

https://github.com/golevelup/nestjs/blob/master/packages/rabbitmq/README.md

所以完整的安裝依賴應該爲

npm i --save amqplib amqp-connection-manager @nestjs/microservices @golevelup/nestjs-rabbitmq

創建 發佈消息模塊

nest g mo mqPublist
nest g s mqPublist

image-20230506175359587

這樣使用cli 工具就自動給我們 將 service 和 module 關聯起來了,並在 全局模塊中註冊了

連接RabbitMQ

在寫其他代碼之前我們首先要保證,連接正常

全局註冊模塊

首先保證我們的 MqPublistModule模塊在全局註冊

app.module.ts

import { MiddlewareConsumer, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsController } from './cats/cats.controller';
import { MakaloModule } from './makalo/makalo.module';
import { UploadModule } from './upload/upload.module';
import { UserModule } from './user/user.module';
import { Module1Module } from './module1/module1.module';
import { ConfigModule } from './config/config.module';
import { PModule } from './p/p.module';

import { MqPublistModule } from './mq-publist/mq-publist.module';
// 日誌模塊
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import 'winston-daily-rotate-file';
@Module({
  imports: [MakaloModule, UploadModule, UserModule, Module1Module,
    ConfigModule.forRoot({ path: '/makalo' }),
    PModule,
    MqPublistModule,
    // 日誌模塊
    WinstonModule.forRoot({
      transports: [
        new winston.transports.DailyRotateFile({
          dirname: `logs`, // 日誌保存的目錄
          filename: '%DATE%.log', // 日誌名稱,佔位符 %DATE% 取值爲 datePattern 值。
          datePattern: 'YYYY-MM-DD', // 日誌輪換的頻率,此處表示每天。
          zippedArchive: true, // 是否通過壓縮的方式歸檔被輪換的日誌文件。
          maxSize: '20m', // 設置日誌文件的最大大小,m 表示 mb 。
          maxFiles: '14d', // 保留日誌文件的最大天數,此處表示自動刪除超過 14 天的日誌文件。
          // 記錄時添加時間戳信息
          format: winston.format.combine(
            winston.format.timestamp({
              format: 'YYYY-MM-DD HH:mm:ss',
            }),
            winston.format.json(),
          ),
        }),
      ],
    }),
  ],
  controllers: [AppController, CatsController],
  providers: [AppService],
})
export class AppModule { }

image-20230508154800610

MqPublistModule 模塊的RabbitMQ 配置

import { Module } from '@nestjs/common';
import { RabbitMQModule, MessageHandlerErrorBehavior } from '@golevelup/nestjs-rabbitmq';
import { MqPublistService } from './mq-publist.service';

@Module({
  imports: [
    RabbitMQModule.forRootAsync(RabbitMQModule, {
      useFactory: () => {
        return {
          // 交換機配置
          exchanges: [
            {
              // 交換機名稱
              name: `exchanges_test`,
              /**
               * 交換機類型
               * direct: 直連交換機,根據消息的路由鍵(routing key)將消息發送到一個或多個綁定的隊列。
                  fanout: 扇形交換機,將消息廣播到所有綁定的隊列,無需指定路由鍵。
                  topic: 主題交換機,根據消息的路由鍵模式匹配將消息發送到一個或多個綁定的隊列。
                  headers: 頭交換機,根據消息的頭部信息將消息發送到一個或多個綁定的隊列。
               */
              type: 'direct',
              // 其他選項
              // 持久化(Durable): 指定交換機、隊列或消息是否需要在服務器重啓後保留
              options: { durable: false },
            },
          ],
          // 連接的url
          uri: 'amqp://admin:admin@localhost:5672',
          /**
           * 用於配置 RabbitMQ 連接的選項。它是一個對象,可以包含以下屬性:
            wait: 一個布爾值,表示是否等待連接成功後纔開始啓動應用程序。默認爲 true。
            rejectUnauthorized: 一個布爾值,表示是否拒絕不受信任的 SSL 證書。默認爲 true。
            timeout: 一個數字,表示連接超時時間(以毫秒爲單位)。默認爲 10000 毫秒。
            heartbeatIntervalInSeconds: 一個數字,表示心跳間隔時間(以秒爲單位)。默認爲 60 秒。
            channelMax: 一個數字,表示最大通道數。默認爲 65535。
            這些選項將影響 RabbitMQ 連接的行爲和性能。您可以根據需要進行調整
           */
          connectionInitOptions: { wait: false },
          /**
           * 用於啓用直接回復模式。當設置爲 true 時,
           * 生產者將使用 replyTo 和 correlationId 字段指定的隊列和標識符來接收響應,
           * 而不是使用默認生成的匿名隊列。這使得消費者可以將響應直接發送到請求者所在的隊列,
           * 從而避免了性能上的開銷和消息傳遞中斷的問題。
           * 
           * 這裏設置爲false
           */
          enableDirectReplyTo: false,
          // 通道的默認預取計數。
          prefetchCount: 300,
          /**
          用於配置 RabbitMQ 消費者訂閱的默認錯誤處理行爲選項。
          當消費者處理消息時出現錯誤時,可以使用該選項來指定消費者應如何處理這些錯誤。
            MessageHandlerErrorBehavior.ACK 表示在發生錯誤時自動確認消息並從隊列中刪除
            以避免消息反覆傳遞和死信隊列的問題。
            如果您想要更多的控制權來處理錯誤,可以將其設置爲 
            MessageHandlerErrorBehavior.NACK,然後手動決定是否重新排隊或丟棄該消息。
           */
          defaultSubscribeErrorBehavior: MessageHandlerErrorBehavior.ACK,
        };
      },
    }),
  ],
  providers: [MqPublistService],
  exports: [MqPublistService],
})
export class MqPublistModule {}

image-20230508143349824

MqPublistService 中的 基本設置

import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';

@Injectable()
export class MqPublistService implements OnModuleInit {
  constructor(
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
    private readonly amqp: AmqpConnection
  ) {}

  /**
   * onModuleInit 是 NestJS 中一個生命週期鉤子方法,
   * 它是 @nestjs/common 模塊提供的 OnModuleInit 接口的一部分。
   * 實現了該接口並實現了 onModuleInit 方法的類,在模塊加載時會自動執行該方法
   */
  async onModuleInit() {
    // 啓動監聽
    this.monitorConn();
  }

  /**
   * rabbitmq連接監聽
   */
  monitorConn() {
    const conn = this.amqp.managedConnection;
    if (conn) {
      conn.on('connect', () => {
        this.logger.info('rabbitmq broker connect');
      });
      conn.on('disconnect', () => {
        this.logger.error('rabbitmq broker disconnect');
      });
    }
    const chan = this.amqp.managedChannel;
    if (chan) {
      chan.on('connect', () => {
        this.logger.info('rabbitmq channel connect');
      });
      chan.on('error', () => {
        this.logger.error('rabbitmq channel error');
      });
      chan.on('close', () => {
        this.logger.error('rabbitmq channel close');
      });
    }
  }
}

image-20230508143536357

啓動

npm run start:dev

image-20230508154847518

這時候我們查看 RabbitMQ 的管理面板,會發現我們配置的交換機出現了

image-20230508155039170

NestJs RabbitMQ 發送隊列消息_案例

如果你看過前置知識,你就知道最重要的三個東西

exchange、routingkey, Queue

上面在NestJs 中已經配置了默認的 交換姬 img

但是 routingkey, Queue 他們之間的綁定關係還沒得呢,這時候我們手動設置一下

打開RabbitMQ 的 管理頁面

http://localhost:15672/

設置 routingkey, Queue 綁定關係

找到這個交換機,點進去

image-20230508170832877

設置 隊列名 和 Routing Key 點擊綁定

image-20230508171157557

image-20230508171232762

這時候 我們就將 exchange、routingkey, Queue 關聯起來了

全局模塊註冊

app.module.ts

import { MiddlewareConsumer, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsController } from './cats/cats.controller';
import { MakaloModule } from './makalo/makalo.module';
import { UploadModule } from './upload/upload.module';
import { UserModule } from './user/user.module';
import { Module1Module } from './module1/module1.module';
import { ConfigModule } from './config/config.module';
import { PModule } from './p/p.module';

import { MqPublistModule } from './mq-publist/mq-publist.module';
// 日誌模塊
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import 'winston-daily-rotate-file';
@Module({
  imports: [MakaloModule, UploadModule, UserModule, Module1Module,
    ConfigModule.forRoot({ path: '/makalo' }),
    PModule,
    MqPublistModule,
    // 日誌模塊
    WinstonModule.forRoot({
      transports: [
        new winston.transports.DailyRotateFile({
          dirname: `logs`, // 日誌保存的目錄
          filename: '%DATE%.log', // 日誌名稱,佔位符 %DATE% 取值爲 datePattern 值。
          datePattern: 'YYYY-MM-DD', // 日誌輪換的頻率,此處表示每天。
          zippedArchive: true, // 是否通過壓縮的方式歸檔被輪換的日誌文件。
          maxSize: '20m', // 設置日誌文件的最大大小,m 表示 mb 。
          maxFiles: '14d', // 保留日誌文件的最大天數,此處表示自動刪除超過 14 天的日誌文件。
          // 記錄時添加時間戳信息
          format: winston.format.combine(
            winston.format.timestamp({
              format: 'YYYY-MM-DD HH:mm:ss',
            }),
            winston.format.json(),
          ),
        }),
      ],
    }),
  ],
  controllers: [AppController, CatsController],
  providers: [AppService],
})
export class AppModule { }

image-20230508172830549

MqPublistModule 模塊配置

mq-publist.module.ts

import { Module } from '@nestjs/common';
import { RabbitMQModule, MessageHandlerErrorBehavior } from '@golevelup/nestjs-rabbitmq';
import { MqPublistService } from './mq-publist.service';

@Module({
  imports: [
    RabbitMQModule.forRootAsync(RabbitMQModule, {
      useFactory: () => {
        return {
          // 交換機配置
          exchanges: [
            {
              // 交換機名稱
              name: `exchanges_test`,
              /**
               * 交換機類型
               * direct: 直連交換機,根據消息的路由鍵(routing key)將消息發送到一個或多個綁定的隊列。
                  fanout: 扇形交換機,將消息廣播到所有綁定的隊列,無需指定路由鍵。
                  topic: 主題交換機,根據消息的路由鍵模式匹配將消息發送到一個或多個綁定的隊列。
                  headers: 頭交換機,根據消息的頭部信息將消息發送到一個或多個綁定的隊列。
               */
              type: 'direct',
              // 其他選項
              // 持久化(Durable): 指定交換機、隊列或消息是否需要在服務器重啓後保留
              options: { durable: false },
            },
          ],
          // 連接的url
          uri: 'amqp://admin:admin@localhost:5672',
          /**
           * 用於配置 RabbitMQ 連接的選項。它是一個對象,可以包含以下屬性:
            wait: 一個布爾值,表示是否等待連接成功後纔開始啓動應用程序。默認爲 true。
            rejectUnauthorized: 一個布爾值,表示是否拒絕不受信任的 SSL 證書。默認爲 true。
            timeout: 一個數字,表示連接超時時間(以毫秒爲單位)。默認爲 10000 毫秒。
            heartbeatIntervalInSeconds: 一個數字,表示心跳間隔時間(以秒爲單位)。默認爲 60 秒。
            channelMax: 一個數字,表示最大通道數。默認爲 65535。
            這些選項將影響 RabbitMQ 連接的行爲和性能。您可以根據需要進行調整
           */
          connectionInitOptions: { wait: false },
          /**
           * 用於啓用直接回復模式。當設置爲 true 時,
           * 生產者將使用 replyTo 和 correlationId 字段指定的隊列和標識符來接收響應,
           * 而不是使用默認生成的匿名隊列。這使得消費者可以將響應直接發送到請求者所在的隊列,
           * 從而避免了性能上的開銷和消息傳遞中斷的問題。
           * 
           * 這裏設置爲false
           */
          enableDirectReplyTo: false,
          // 通道的默認預取計數。
          prefetchCount: 300,
          /**
          用於配置 RabbitMQ 消費者訂閱的默認錯誤處理行爲選項。
          當消費者處理消息時出現錯誤時,可以使用該選項來指定消費者應如何處理這些錯誤。
            MessageHandlerErrorBehavior.ACK 表示在發生錯誤時自動確認消息並從隊列中刪除
            以避免消息反覆傳遞和死信隊列的問題。
            如果您想要更多的控制權來處理錯誤,可以將其設置爲 
            MessageHandlerErrorBehavior.NACK,然後手動決定是否重新排隊或丟棄該消息。
           */
          defaultSubscribeErrorBehavior: MessageHandlerErrorBehavior.ACK,
        };
      },
    }),
  ],
  providers: [MqPublistService],
  exports: [MqPublistService],
})
export class MqPublistModule {}

image-20230508173027708

MqPublistService 封裝

mq-publist.service.ts

import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { WINSTON_MODULE_NEST_PROVIDER, WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';

@Injectable()
export class MqPublistService implements OnModuleInit {
  constructor(
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
    private readonly amqp: AmqpConnection
  ) {}

  /**
   * onModuleInit 是 NestJS 中一個生命週期鉤子方法,
   * 它是 @nestjs/common 模塊提供的 OnModuleInit 接口的一部分。
   * 實現了該接口並實現了 onModuleInit 方法的類,在模塊加載時會自動執行該方法
   */
  async onModuleInit() {
    // 啓動監聽
    this.monitorConn();
  }

  /**
   * rabbitmq連接監聽
   */
  monitorConn() {
    const conn = this.amqp.managedConnection;
    if (conn) {
      conn.on('connect', () => {
        this.logger.info('rabbitmq broker connect');
      });
      conn.on('disconnect', () => {
        this.logger.error('rabbitmq broker disconnect');
      });
    }
    const chan = this.amqp.managedChannel;
    if (chan) {
      chan.on('connect', () => {
        this.logger.info('rabbitmq channel connect');
      });
      chan.on('error', () => {
        this.logger.error('rabbitmq channel error');
      });
      chan.on('close', () => {
        this.logger.error('rabbitmq channel close');
      });
    }
  }

  // exchange
  private readonly exc_test = `exchanges_test`;
   // routingKey
  private readonly routingKey_test = 'routingKey_test';

  /**
   * rabbitmq發送消息
   * @param message
   */
  async pubMQMsgTest(message: any): Promise<void> {
    await this.amqp.publish(this.exc_test, this.routingKey_test, message);
    this.logger.info(
      `amqp publish message -> exchange : ${this.exc_test}, routingKey : ${this.routingKey_test},message : ${JSON.stringify(
        message,
      )}`,
    );
  }
}

image-20230508173153066

其他模塊中使用

import { MqPublistService } from '../mq-publist/mq-publist.service';

constructor(
    private readonly mqPublishService: MqPublistService,
  ) { }

// 發送 RabbitMQ 消息
this.mqPublishService.pubMQMsgTest('test send push RabbitMQ');

image-20230508173505452

RabbitMQ 管理頁面中查看

image-20230508173558842

單擊隊列名直接跳轉到對應的隊列

image-20230508173634690

image-20230508173756729

NestJs RabbitMQ 訂閱隊列消息_案例

nest g mo mqSubscribe
nest g s mqSubscribe

image-20230508175258258

image-20230508185755082

MqSubscribeModule

mq-subscribe.module.ts

import { Module } from '@nestjs/common';
import { MqSubscribeService } from './mq-subscribe.service';

@Module({
  providers: [MqSubscribeService]
})
export class MqSubscribeModule {}

image-20230508185850275

MqSubscribeService

mq-subscribe.service.ts

import { Inject, Injectable } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
import { Logger } from 'winston';

@Injectable()
export class MqSubscribeService {
  constructor(
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
  ) { }

  @RabbitSubscribe({
    // 交換機
    exchange: `exchanges_test`,
    routingKey: [
      'routingKey_test',
    ],
    // 隊列
    queue: `queue_test`,
    // 持久化配置
    queueOptions: { durable: true },
  })
  // 收到隊列的訂閱消息自動調用該方法
  async subscribe(data: any): Promise<any> {
    const routingKey = arguments[1].fields.routingKey;
    console.log('arguments[1].fields.exchange :', arguments[1].fields.exchange);
    console.log('routingKey :', routingKey);
    console.log('data:', data);
    this.logger.info(
      `amqp receive msg,exchange is ${arguments[1].fields.exchange},routingKey is ${routingKey},msg is ${JSON.stringify(
        data,
      )}`,
    );
  }
}

image-20230508185924832

使用上面的發送消息再次訪問

http://localhost:3000/p

image-20230508190030734

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章