快速了解
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对多,但只有一个接收方消费了消息
- 1对多,多方消费了消息,比如前端比较熟悉的事件的发布订阅,事件注册后,他会一直存在事件中心上,使用方可以监听订阅,一旦触发事件从map中得到对应的队列,按顺序执行回调。
- 1对多,路由匹配
- 1对多,正则模式匹配
- 相比点对点,服务端会给客户端对应的响应状态
消息队列最大的优势在于解耦、异步处理、流量削峰。同时框架层面,通过消息确认的回调机制也保证了数据回滚后可靠性、消息协议和数据格式的可扩展性、延迟队列等等优势。
docker 安装 RabbitMQ
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等,所以我们得使用两外基于此的包。
npm install ---save @golevelup/nestjs-rabbitmq
模块初始化
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 {}
订阅消息
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();
}
}
}
发送消息
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 和竞争消费者,增强应用的异步处理能力