起因
项目写着写着发现出现了服务与服务之间出现了循环依赖的情况,而且大有愈演愈烈的趋势,虽然说nest
在框架中有提供这种情况下的预备措施,但服务与服务之间存在太多的循环依赖总归不是一个好的现象,对未来可能发生的微服务拆分会造成很大的麻烦,趁着现在项目还不算太复杂,及时解决这个问题避免未来带来更大的维护成本才是当务之急。
消息队列选型
在选用RabbitMq
之前,其实也有考虑其他类型的消息队列,甚至考虑过要不要使用Redis
的消息队列功能,毕竟Redis
未来也是可能引入到项目中替代原生缓存的,在参考了一些文章之后,一篇消息队列详解最后让我打定了主意使用RabbitMq
,里面有这么一段话:
中小型软件公司,建议选RabbitMQ,一方面,erlang语言天生具备高并发的特性,而且他的管理界面用起来十分方便。
他的弊端也在这里,虽然RabbitMQ是开源的,然而国内有几个能定制化开发erlang的程序员呢?
所幸,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug,这点对于中小型公司来说十分重要。
不考虑RocketMQ和kafka的原因是,一方面中小型软件公司不如互联网公司,数据量没那么大,选消息中间件,应首选功能比较完备的,所以kafka排除。
不考虑RocketMQ的原因是,RocketMQ是阿里出品,如果阿里放弃维护RocketMQ,中小型公司一般抽不出人来进行RocketMQ的定制化开发,因此不推荐。
作为一家中小型软件公司的开发,显然我是不想冒着出了bug还要看中间件源码的风险来使用一样东西的,而且谁能拒绝遇到的问题都能从网上找到答案呢
RabbitMq 启动
在使用RabbitMq
之前,我先看了一下nest
的官方文档中微服务的部分(当然,如果你喜欢英文文档,咱也不拦着)。说实话,这篇文档至少对我来说是相当的不友好,这也是我写这篇文章的原因,照着这篇文档真的很难把整个流程跑通,因为很多地方他只顾着自己写得爽了,完全也没考虑到小白的感受。当然,如果你是大佬觉得有这篇官方文档就够了的,那当咱没说,请自便。
而如果你有兴趣,能跟着我把这些都配置完,至少我能保证你可以在nest
框架中做到简单的消息发送和接收。既然我走了弯路,那尽量把路给后来的小白们都掰直了。
安装RabbitMq
差点忘了说RabbitMq
的安装,这个其实没什么好提的,但其实这上面我也遇到了一点点问题,所以还是提一下。在官网选择自己需要下载的版本(这里本来是打算截图讲解一下怎么下载,但想想还是算了,一方面感觉下载界面可能会发生变动,反而造成困扰;二方面,我相信各位的实力可以找到的)。下载完成后会得到一个安装包
在安装的过程中会让你选择安装目录或者如果没安装erlang
的话会让你安装,,我不知道为什么当我修改了软件目录之后安装完成之后会提示打开失败,如果你不想遇到一些奇奇怪怪的问题的话,建议在这一步把你的整理癖收一收,使用默认路径安装。
安装nest依赖
文档中提到这么一句话:
如果你照做了的话,会发现在接下来的使用过程中,你会有很多找不到的类,因为下面还有一句话:
所以完整的依赖应该是:
$ npm i --save @nestjs/microservices
$ npm i --save amqplib amqp-connection-manager
不光是Transport
,本文中提到的所有的类都是来自于@nestjs/microservices
(我在用这个包的时候里面所有的类都不会自动补全,都要手动import,知道要怎么弄的小伙伴也可以教我一下),所以我也不清楚amqplib
有什么作用,如果有知道的小伙伴可以告诉我一下。
main.ts
不知道有多少小伙伴在这一步就卡住了的,因为main.ts
中,app
是有定义的(nest
创建的时候就定义了啊,你可别说你是徒手敲nest
),特别是像我这种替换了日志组件的,必不可能把app初始化方式换了
但是也不用着急,如果你正常引入了上面的依赖,你的app
对象中应该会出现connectMicroservice
方法,来跟着我打:
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
// app的初始化我跳过了
app.connectMicroservice<MicroserviceOptions>({
// 类型:rabbitmq
transport: Transport.RMQ,
options: {
// rabbitmq地址
urls: ['amqp://localhost:5672'],
// 队列名称
queue: 'cats_queue',
queueOptions: {
// 消息是否持久化
durable: false
},
}
})
app.startAllMicroservices();
这一条会给你的服务增加一条全局的链接,用来监听消息用的,没有这个你在下面的@MessagePattern
啥也不是(当然,如果有更好的方法评论区也可以指导一下哈)。
发消息
可能是写文档的大佬觉得这实在是太简单了,以至于压根就没写在文档里,文档里能用作参考的有这么一段
确实是这样,但其实没那么麻烦,首先需要在你的module
中引入这么一段:
ClientsModule.register([
{
name: 'MATH_SERVICE',
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'cats_queue',
queueOptions: {
durable: false
},
},
}
])
其中最需要关注的是name
属性,因为在service中,你需要自动注入连接(就是文档中提到了但没讲怎么来的那个this.client
)时用的就是这个属性中的值。简单来说就是这样:
constructor(@Inject("MATH_SERVICE") private readonly client: ClientProxy) { }
async create() {
const record = "hello";
// send中第一个参数对应的是接收方法的监听字段,也可以放对象,但接收的时候也必须是相同的对象,第二个参数对应的是发送的内容,如果需要在headers中增加内容的话可以参考官方文档
const result = this.client.send('test', record).subscribe();
console.log("result", result);
return "消息发送成功";
}
收消息
收消息就更简单了,咱们一笔带过,这样:
// 这里的'test'对应的是send里的第一个字段
@MessagePattern('test')
test(@Payload() data: string) {
console.log("get message", data);
}
就可以看到发出来的消息了
监控消息
这个部分我不太想详谈,因为其他有比我讲得好得多的文章,大家可以自行查阅,我就不多班门弄斧了,大家可以了解一下RabbitMq
自带的监控平台,默认在15672端口。
结束语
大概就这样,起步是可以正常起步了,还有更多的功能比如说广播啊,或者多队列监听啊之类的配置目前还在探索,以后有机会再更新吧,如果看的人少的话也可能就不更新了,我这人也是挺懒的...