Node.js微服务框架(Moleculer)
如果想在 Node.js 项目中使用微服务框架的话,推荐 Moleculer,它有丰富的功能、完善的文档、高效的性能和灵活的配置,我们先看一张架构图:
上图描述了如下的应用场景:
- 用户请求通过 HTTP 协议到达 API 网关层
- Gateway 服务解析请求并将数据和指令交给 Broker
- Broker 通过 Transporter 调用微服务内部函数,返回处理后的数据
从这张图可以看到 Moleculer 接管了内部的服务注册与发现、服务之间的消息通信以及负载均衡等底层复杂逻辑,用户只需要写核心业务逻辑即可,大大提升了开发效率。
核心概念
服务 Service
服务就是一个独立的业务单元,本质上就是一个 JS 对象,例如下面的代码实现了完整的加减乘除功能,我们可以给它起名为 math
服务:
module.exports = {
name: 'math',
actions: {
add(ctx) {
const { a, b } = ctx.params
return a + b
},
subtract(ctx) {
const { a, b } = ctx.params
return a - b
}
// 省略代码
},
}
节点 Node
节点就是本地或网络上的一个 Node 进程,每个节点内部可以承载多个服务。例如定义了数学计算 math
和统计分析 statistic
两个服务,放在了 services
目录下,目录结构为:
services
├── math.service.js
└── statistic.service.js
现将其在同一个 Node 进程中启动。
const { ServiceBroker } = require('moleculer')
const broker = new ServiceBroker()
broker.loadServices('./services')
broker.start().catch((e) => console.error(e))
那么这个启动的 Node 进程就是一个 Moleculer 节点。
本地服务 Local services
在同一个节点上的服务彼此互为本地服务,它们共享硬件资源,通过局部总线进行通信,无任何网络延迟。比如说上面的 math
和 statistic
服务之间互为本地服务,可以在 statistic
里面直接调用 math
中的方法,这个时候并没有走传输介质,而是进程内部调用,效率很高。
module.exports = {
name: 'statistic',
actions: {
calculate(ctx) {
return ctx.call(`math.add`, { a: 3, b: 4 })
},
},
}
远程服务 Remote Services
不同节点上的服务彼此互为远程服务,它们之间通过某种传输介质(例如消息中间件)进行通信。例如我们又起了一个 Node 进程,里面加载了 user
和 account
服务:
services
├── account.service.js
└── user.service.js
那么 user
和 math
之间就互为远程服务,因为它们不在一个 Node 进程下面,例如在 user.get
中调用另外一个进程的 math.multiply
方法,就必须通过传输介质进行通信。
module.exports = {
name: 'user',
actions: {
async get(ctx) {
return ctx.call('math.multiply', { a: 3, b: 10 })
},
},
}
服务中介 Service Broker
服务中介是 Moleculer 框架的核心,负责服务之间的通信(包括本地和远程服务),每个节点都有一个服务中介。
const { ServiceBroker } = require('moleculer')
const broker = new ServiceBroker()
broker
实例有很多方法,例如:
- 通过
loadService
方法加载服务 - 通过
emit
方法触发事件 - 通过
call
方法发送请求
传输介质 Transporter
传输介质负责节点之间的通信,是服务之间传输消息的通信总线,可以触发事件、发送请求、接收响应。
也就是说如果 Node1 中的 user
服务想发消息给 Node2 中的 math
服务,需要通过传输介质进行通信,Moleculer 中提供了多种传输介质,例如:
- nats(
yarn add nats
) - redis(
yarn add ioredis
) - kafka(
yarn add kafka-node
) - mqtt(
yarn add mqtt
) - amqp(
yarn add amqplib
) - 等等,还可以自定义传输介质
const { ServiceBroker } = require('moleculer')
const broker = new ServiceBroker({
transporter: 'nats://localhost:4222', // 使用 nats 传输介质
// transporter: 'redis://localhost:6379', // 使用 redis 传输介质
// transporter: 'kafka://localhost:2181', // 使用 kafka 传输介质
// transporter: 'mqtt://localhost:1883', // 使用 mqtt 传输介质
// transporter: "amqp://localhost:5672", // 使用 amqp 传输介质
})
网关 Gateway
网关将内部服务暴露给终端用户,它就是一个常规的 Moleculer 服务,内部集成了 HTTP 或 WebSocket 功能,将用户请求转发到内部服务。官方提供了一个 RESTful 的 API 网关:
yarn add moleculer-web # 或者 npm i moleculer-web
使用方法:
const { ServiceBroker } = require('moleculer')
const ApiService = require('moleculer-web')
const broker = new ServiceBroker({ transporter: 'nats://localhost:4222' })
broker.createService(ApiService)
broker.start()
这个时候,默认会在 localhost:3000
端口开启一个 RESTful 的 HTTP 服务:
curl 'localhost:3000/math/subtract?a=5&b=3'
如果 math
服务启动,那么请求就会被 Moleculer 转发给 math.subtract
方法进行处理并返回结果 2
。
命令行工具
Moleculer 最让开发者喜欢的一点就是它提供了非常强大的命令行工具 moleculer-repl,采用约定大于配置的规范自动加载 services
目录下 xxx.service.js
文件,在开发环境支持热加载,也就是说自动监听代码变化,保存即更新服务,无需重启。在 package.json 中配置如下:
"scripts": {
"dev": "moleculer-runner --repl --hot services",
"start": "moleculer-runner"
},
在 moleculer-repl 提供的命令行里面可以看到所有的方法和事件:
还可以直接调试各种方法,例如:
call "math.add" --a 5 --b 6 # 命令行传参
call "math.add" --load my-params.json # 从 文件中加载参数
call "math.add" --save my-response.json # 把返回结果保存到文件
而且 moleculer-runner 可以直接上生产环境,自带日志管理、错误调试、缓存、参数校验等功能。
数据库连接
Moleculer 封装了 moleculer-db 库,把 CRUD 的逻辑打包进去,并且对接了 mongo、mongoose 和 sequelize ORM 框架,增删改查不需要写一行代码,只要引入相应的库和适配器即可:
const DbService = require('moleculer-db')
const mongoose = require('mongoose')
const MongooseAdapter = require('moleculer-db-adapter-mongoose')
然后在创建服务的时候,添加下面的参数:
broker.createService({
name: 'order',
mixins: [DbService],
adapter: new MongooseAdapter('mongodb://localhost:27017/test'),
model: mongoose.model(
'order',
mongoose.Schema({
orderNo: { type: String },
price: { type: Number, default: 0 },
})
),
})
这个时候会自动生成 count
、 create
、 find
、 get
、 insert
、 list
、 remove
、 update
八个方法,开箱即用。
中间件
Moleculer 同样支持中间件,框架有内置的中间件,用户也可以定义自己的中间件,还是 express 中熟悉的洋葱模型:
在 Moleculer 中,中间件本质上就是一个包含高阶函数的对象,可以应用在方法、事件等各种场景,例如下面的错误捕获中间件:
module.exports = {
name: 'error',
localAction(next, action) {
return async function (ctx) {
try {
return await next(ctx)
} catch (e) {
console.log('出错的Action', action.name)
throw e
}
}
}
}
上面只是初步介绍了 Moleculer 的核心知识点,其实还有非常多强大的功能,感兴趣的可以参阅官方文档。
本文示例代码地址:
git clone git@github.com:keliq/moleculer-demo.git
转载自:https://juejin.cn/post/6882685336829296648