目录
- 微服务基础概念
- [Nest 创建微服务](#Nest 创建微服务 "#%E4%BA%8Cnest-%E5%88%9B%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1")
- [Monorepo 与 Library](#Monorepo 与 Library "#%E4%B8%89monorepo-%E4%B8%8E-library")
- 配置中心与注册中心
- [Etcd 实现配置中心和注册中心](#Etcd 实现配置中心和注册中心 "#%E4%BA%94etcd-%E5%AE%9E%E7%8E%B0%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E5%92%8C%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83")
- [Nest 集成 Etcd](#Nest 集成 Etcd "#%E5%85%ADnest-%E9%9B%86%E6%88%90-etcd")
- [Nacos 实现配置中心和注册中心](#Nacos 实现配置中心和注册中心 "#%E4%B8%83nacos-%E5%AE%9E%E7%8E%B0%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E5%92%8C%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83")
- [gRPC 跨语言通信](#gRPC 跨语言通信 "#%E5%85%ABgrpc-%E8%B7%A8%E8%AF%AD%E8%A8%80%E9%80%9A%E4%BF%A1")
一、微服务基础概念
1.1 什么是微服务?
微服务架构是将单体应用拆分成多个独立的、松耦合的服务,每个服务负责特定的业务功能。
单体架构 vs 微服务架构:
| 特性 | 单体架构 | 微服务架构 |
|---|---|---|
| 代码管理 | 所有代码在一个项目 | 代码拆分到多个项目 |
| 部署方式 | 整体部署 | 独立部署 |
| 扩展性 | 只能整体扩展 | 按需扩展单个服务 |
| 维护难度 | 项目大了难维护 | 相对容易维护 |
1.2 为什么需要微服务?
- 项目越来越大,模块越来越多,代码难以维护
- 某些业务模块需要单独扩展,但单体架构只能整体扩展
- 团队可以并行开发不同的微服务
1.3 微服务通信方式
微服务之间一般不用 HTTP ,而是用 TCP:
- HTTP 会携带大量 header,增大通信开销
- TCP 更高效,适合服务间通信
1.4 配置中心和注册中心
配置中心:
- 集中管理各微服务的配置信息
- 修改配置后自动通知所有服务
- 避免配置散落在各个服务中
注册中心:
- 管理微服务的所有实例节点
- 支持服务注册、服务发现
- 动态感知节点的增删
二、Nest 创建微服务
2.1 安装微服务包
bash
npm install @nestjs/microservices --save
2.2 创建微服务
微服务端(提供 TCP 端口)
typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: {
port: 8888,
},
},
);
await app.listen();
}
bootstrap();
微服务暴露方法
typescript
// app.controller.ts
@Controller()
export class AppController {
// 使用 @MessagePattern 处理请求-响应式调用
@MessagePattern('sum')
sum(numArr: Array<number>): number {
return numArr.reduce((total, item) => total + item, 0);
}
// 使用 @EventPattern 处理事件式调用(不需要返回值)
@EventPattern('log')
log(str: string) {
console.log(str);
}
}
2.3 主服务连接微服务
导入 ClientsModule
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
@Module({
imports: [
ClientsModule.register([
{
name: 'USER_SERVICE',
transport: Transport.TCP,
options: {
port: 8888,
},
},
])
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
注入并调用微服务
typescript
// app.controller.ts
@Controller()
export class AppController {
@Inject('USER_SERVICE')
private userClient: ClientProxy;
// 调用 @MessagePattern 声明的方法(请求-响应)
@Get('sum')
calc(@Query('num') str) {
const numArr = str.split(',').map((item) => parseInt(item));
return this.userClient.send('sum', numArr);
}
// 调用 @EventPattern 声明的方法(事件式)
@Get('log')
log() {
return this.userClient.emit('log', '求和');
}
}
2.4 通信方式对比
| 装饰器 | 客户端方法 | 说明 |
|---|---|---|
@MessagePattern |
client.send() |
请求-响应式,一问一答 |
@EventPattern |
client.emit() |
事件式,只发不收 |
2.5 TCP 通信消息格式
微服务之间传输的是 JSON 格式:
json
// 请求
{"pattern": "sum", "data": [1, 2, 3], "id": "3b4a92305a76109bf0e79"}
// 响应
{"response": 6, "isDisposed": true, "id": "3b4a92305a76109bf0e79"}
三、Monorepo 与 Library
3.1 什么是 Monorepo?
Monorepo 是在一个 Git 仓库中管理多个项目的方式。
为什么需要 Monorepo?
- 微服务可能有很多项目
- 多个仓库难以维护
- 公共代码需要复用
3.2 创建 Monorepo 应用
bash
# 创建 Nest 项目
nest new monorepo-test
# 添加新的应用
nest g app app2
执行后目录结构变为:
bash
monorepo-test/
├── apps/
│ ├── monorepo-test/ # 原有应用
│ └── app2/ # 新添加的应用
├── nest-cli.json
└── package.json
3.3 运行指定应用
bash
# 运行默认应用
npm run start:dev
# 运行指定应用
npm run start:dev app2
# 构建指定应用
npm run build
npm run build app2
3.4 nest-cli.json 配置
json
{
"projects": {
"monorepo-test": {
"type": "application",
"root": "apps/monorepo-test",
"entryFile": "main",
"sourceRoot": "apps/monorepo-test/src"
},
"app2": {
"type": "application",
"root": "apps/app2",
"entryFile": "main",
"sourceRoot": "apps/app2/src"
}
}
}
3.5 创建 Library(公共库)
bash
nest g lib lib1
生成的目录结构:
arduino
libs/
└── lib1/
└── src/
├── lib1.module.ts
├── lib1.service.ts
└── index.ts
3.6 使用 Library
typescript
// 在应用中导入 Library
import { Lib1Module } from '@app/lib1';
@Module({
imports: [Lib1Module],
// ...
})
export class AppModule {}
// 注入并使用 Library 的 Service
@Inject(Lib1Service)
private lib: Lib1Service;
@Get('aaa')
aaa() {
return 'aaa' + this.lib.xxx();
}
3.7 编译 Library
bash
npm run start:dev lib1
npm run build lib1
四、配置中心与注册中心
4.1 配置中心的作用
- 集中管理配置:所有微服务的配置统一存放
- 动态更新:配置修改后自动通知所有服务
- 环境隔离:支持不同环境的配置管理
4.2 注册中心的作用
- 服务注册:服务启动时向注册中心注册
- 服务发现:调用服务前查询可用的实例
- 健康检查:通过心跳检测服务是否存活
- 动态伸缩:节点增删自动感知
4.3 常见的中间件
| 中间件 | 特点 |
|---|---|
| Etcd | K8s 使用,轻量级,key-value 存储 |
| Nacos | 阿里开源,自带控制台,功能丰富 |
| Apollo | 携程开源,功能强大,配置管理专精 |
| Eureka | Spring Cloud 默认,Netflix 开源 |
五、Etcd 实现配置中心和注册中心
5.1 什么是 Etcd?
Etcd 是一个分布式 key-value 存储服务,K8s 就是用它来实现配置中心和注册中心。
5.2 启动 Etcd
使用 Docker 启动:
bash
docker run -d --name etcd \
-p 2379:2379 \
-e ETCD_ROOT_PASSWORD=your_password \
bitnami/etcd:latest
5.3 Etcdctl 命令行工具
bash
# 基本操作
etcdctl put key value # 设置
etcdctl get key # 获取
etcdctl del key # 删除
# 前缀查询
etcdctl get --prefix /services
# 监听变化
etcdctl watch key
# 认证
etcdctl --user=root --password=xxx get key
5.4 Node.js 连接 Etcd
bash
npm install etcd3
javascript
const { Etcd3 } = require('etcd3');
const client = new Etcd3({
hosts: 'http://localhost:2379',
auth: {
username: 'root',
password: 'your_password'
}
});
// 查询
const value = await client.get('/services/a').string();
// 前缀查询
const keys = await client.getAll().prefix('/services').keys();
// 监听
const watcher = await client.watch().key('/services/a').create();
watcher.on('put', (req) => {
console.log('put', req.value.toString());
});
watcher.on('delete', (req) => {
console.log('delete');
});
5.5 实现配置中心
javascript
// 保存配置
async function saveConfig(key, value) {
await client.put(key).value(value);
}
// 读取配置
async function getConfig(key) {
return await client.get(key).string();
}
// 删除配置
async function deleteConfig(key) {
await client.delete().key(key);
}
5.6 实现注册中心
javascript
// 服务注册
async function registerService(serviceName, instanceId, metadata) {
const key = `/services/${serviceName}/${instanceId}`;
const lease = client.lease(10); // 10秒租约
await lease.put(key).value(JSON.stringify(metadata));
lease.on('lost', async () => {
console.log('租约过期,重新注册...');
await registerService(serviceName, instanceId, metadata);
});
}
// 服务发现
async function discoverService(serviceName) {
const instances = await client.getAll()
.prefix(`/services/${serviceName}`).strings();
return Object.entries(instances)
.map(([key, value]) => JSON.parse(value));
}
// 监听服务变更
async function watchService(serviceName, callback) {
const watcher = await client.watch()
.prefix(`/services/${serviceName}`).create();
watcher.on('put', async event => {
callback(await discoverService(serviceName));
});
watcher.on('delete', async event => {
callback(await discoverService(serviceName));
});
}
六、Nest 集成 Etcd
6.1 基础集成
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { Etcd3 } from 'etcd3';
@Module({
imports: [],
controllers: [AppController],
providers: [
AppService,
{
provide: 'ETCD_CLIENT',
useFactory() {
const client = new Etcd3({
hosts: 'http://localhost:2379',
auth: {
username: 'root',
password: 'your_password'
}
});
return client;
}
}
],
})
export class AppModule {}
6.2 封装 EtcdService
typescript
// etcd.service.ts
@Injectable()
export class EtcdService {
@Inject('ETCD_CLIENT')
private client: Etcd3;
async saveConfig(key, value) {
await this.client.put(key).value(value);
}
async getConfig(key) {
return await this.client.get(key).string();
}
async deleteConfig(key) {
await this.client.delete().key(key);
}
async registerService(serviceName, instanceId, metadata) {
const key = `/services/${serviceName}/${instanceId}`;
const lease = this.client.lease(10);
await lease.put(key).value(JSON.stringify(metadata));
lease.on('lost', async () => {
await this.registerService(serviceName, instanceId, metadata);
});
}
async discoverService(serviceName) {
const instances = await this.client.getAll()
.prefix(`/services/${serviceName}`).strings();
return Object.entries(instances)
.map(([key, value]) => JSON.parse(value));
}
async watchService(serviceName, callback) {
const watcher = await this.client.watch()
.prefix(`/services/${serviceName}`).create();
watcher.on('put', async event => {
callback(await this.discoverService(serviceName));
});
watcher.on('delete', async event => {
callback(await this.discoverService(serviceName));
});
}
}
6.3 封装动态模块
typescript
// etcd.module.ts
@Module({})
export class EtcdModule {
static forRoot(options?: IOptions): DynamicModule {
return {
module: EtcdModule,
providers: [
EtcdService,
{
provide: 'ETCD_CLIENT_OPTIONS',
useValue: options
},
{
provide: 'ETCD_CLIENT',
useFactory(options: IOptions) {
return new Etcd3(options);
},
inject: ['ETCD_CLIENT_OPTIONS']
}
],
exports: [EtcdService]
};
}
static forRootAsync(options: EtcdModuleAsyncOptions): DynamicModule {
return {
module: EtcdModule,
providers: [
EtcdService,
{
provide: 'ETCD_CLIENT_OPTIONS',
useFactory: options.useFactory,
inject: options.inject || []
},
{
provide: 'ETCD_CLIENT',
useFactory(options: IOptions) {
return new Etcd3(options);
},
inject: ['ETCD_CLIENT_OPTIONS']
}
],
exports: [EtcdService]
};
}
}
6.4 使用动态模块
typescript
// 使用 forRoot
EtcdModule.forRoot({
hosts: 'http://localhost:2379',
auth: { username: 'root', password: 'xxx' }
})
// 使用 forRootAsync(支持异步)
EtcdModule.forRootAsync({
async useFactory(configService: ConfigService) {
return {
hosts: configService.get('etcd_hosts'),
auth: {
username: configService.get('etcd_auth_username'),
password: configService.get('etcd_auth_password')
}
};
},
inject: [ConfigService]
})
七、Nacos 实现配置中心和注册中心
7.1 什么是 Nacos?
Nacos 是阿里巴巴开源的配置中心和服务注册中心,自带 Web 控制台。
7.2 启动 Nacos
bash
docker run -d --name nacos \
-p 8848:8848 \
-e MODE=standalone \
nacos/nacos-server:latest
访问控制台:http://localhost:8848/nacos
- 用户名:nacos
- 密码:nacos
7.3 Node.js 连接 Nacos
bash
npm install nacos
7.4 服务注册中心用法
javascript
import Nacos from 'nacos';
const client = new Nacos.NacosNamingClient({
serverList: ['127.0.0.1:8848'],
namespace: 'public',
logger: console
});
await client.ready();
// 服务注册
const instance = { ip: '127.0.0.1', port: 8080 };
await client.registerInstance('aaaService', instance);
// 服务注销
await client.deregisterInstance('aaaService', instance);
// 服务发现
const instances = await client.getAllInstances('aaaService');
// 监听服务变化
client.subscribe('aaaService', content => {
console.log('当前实例:', content);
});
7.5 配置中心用法
javascript
import { NacosConfigClient } from 'nacos';
const client = new NacosConfigClient({
serverAddr: 'localhost:8848'
});
// 发布配置
await client.publishSingle('config', 'DEFAULT_GROUP', '{"host":"127.0.0.1"}');
// 获取配置
const config = await client.getConfig('config', 'DEFAULT_GROUP');
// 删除配置
await client.remove('config', 'DEFAULT_GROUP');
// 监听配置变化
client.subscribe({ dataId: 'config', group: 'DEFAULT_GROUP' },
content => {
console.log('配置变更:', content);
}
);
7.6 Etcd vs Nacos
| 特性 | Etcd | Nacos |
|---|---|---|
| 控制台 | 无,需命令行 | 自带 Web 控制台 |
| 部署 | 轻量 | 相对较重 |
| 生态 | K8s 生态 | Spring Cloud 生态 |
| 功能 | 基础 KV 存储 | 功能丰富 |
八、gRPC 跨语言通信
8.1 什么是 gRPC?
gRPC 是 Google 开源的跨语言远程过程调用(RPC)框架,基于 HTTP/2 和 Protocol Buffers。
8.2 为什么需要 gRPC?
- HTTP 是文本传输,效率低
- 微服务不需要面向客户端,只需服务间通信
- 不同语言(Java、Go、Python、Node)需要统一的通信协议
8.3 Protocol Buffers
Protocol Buffers 是一种语言中立、平台中立的序列化协议。
protobuf
syntax = "proto3";
package book;
service BookService {
rpc FindBook (BookById) returns (Book) {}
}
message BookById {
int32 id = 1;
}
message Book {
int32 id = 1;
string name = 2;
string desc = 3;
}
8.4 安装依赖
bash
npm install @grpc/grpc-js @grpc/proto-loader @nestjs/microservices
8.5 gRPC 服务端
typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { GrpcOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';
async function bootstrap() {
const app = await NestFactory.createMicroservice<GrpcOptions>(
AppModule,
{
transport: Transport.GRPC,
options: {
url: 'localhost:8888',
package: 'book',
protoPath: join(__dirname, 'book/book.proto'),
},
},
);
await app.listen();
}
bootstrap();
typescript
// app.controller.ts
@Controller()
export class AppController {
@GrpcMethod('BookService', 'FindBook')
findBook(data: { id: number }) {
const items = [
{ id: 1, name: 'Book 1', desc: 'Description 1' },
{ id: 2, name: 'Book 2', desc: 'Description 2' },
];
return items.find(({ id }) => id === data.id);
}
}
8.6 gRPC 客户端
typescript
// app.module.ts
@Module({
imports: [
ClientsModule.register([
{
name: 'BOOK_PACKAGE',
transport: Transport.GRPC,
options: {
url: 'localhost:8888',
package: 'book',
protoPath: join(__dirname, 'book/book.proto'),
},
},
]),
],
// ...
})
export class AppModule {}
typescript
// app.controller.ts
@Controller()
export class AppController {
@Inject('BOOK_PACKAGE')
private client: ClientGrpc;
private bookService: BookService;
onModuleInit() {
this.bookService = this.client.getService('BookService');
}
@Get('book/:id')
getBook(@Param('id') id: number) {
return this.bookService.findBook({ id });
}
}
8.7 配置 proto 文件复制
json
// nest-cli.json
{
"assets": ["**/*.proto"],
"watchAssets": true
}
九、最佳实践总结
9.1 微服务拆分原则
- 按业务领域拆分(DDD)
- 单一职责原则
- 服务间松耦合
9.2 通信方式选择
| 场景 | 推荐方式 |
|---|---|
| 同语言微服务 | TCP + JSON |
| 跨语言微服务 | gRPC |
| 客户端调用 | HTTP/HTTPS |
| 异步解耦 | 消息队列 |
9.3 配置管理建议
- 敏感配置使用环境变量
- 不同环境使用不同配置
- 配置变更需要版本控制
9.4 服务注册与发现
- 服务启动时自动注册
- 定时发送心跳
- 消费端缓存服务列表
- 监听服务变更实时更新
十、常用命令速查表
| 命令 | 说明 |
|---|---|
nest g app <name> |
添加新的 Nest 应用 |
nest g lib <name> |
创建 Library |
npm run start:dev <app> |
运行指定应用 |
npm run build <app> |
构建指定应用 |
npm run start:dev lib1 |
编译 Library |
docker run -d bitnami/etcd |
启动 Etcd |
docker run -d nacos/nacos-server |
启动 Nacos |
十一、架构图示例
scss
┌─────────────────────────────────────────────────────────┐
│ 客户端 (HTTP) │
└─────────────────────────┬───────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────┐
│ API Gateway │
└────┬─────────────────────────────────────┬──────────────┘
│ │
┌────▼──────────┐ gRPC/TCP ┌───────────▼──────────┐
│ User Service │◄───────────►│ Order Service │
└───────────────┘ └───────────────────────┘
│ │
│ │
┌────▼───────────────────────────────▼──────────────────┐
│ 注册中心 (Etcd/Nacos) │
│ - 服务注册/发现 │
│ - 健康检查 │
└───────────────────────────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────┐
│ 配置中心 (Etcd/Nacos) │
│ - 集中配置管理 │
│ - 动态配置更新 │
└───────────────────────────────────────────────────────┘
参考文档:
- Nest.js 官方文档:docs.nestjs.com/microservic...
- gRPC 官方文档:grpc.io/docs/