Nest.js 微服务入门教程

75 阅读9分钟

目录

  1. 微服务基础概念
  2. Nest 创建微服务
  3. Monorepo 与 Library
  4. 配置中心与注册中心
  5. Etcd 实现配置中心和注册中心
  6. Nest 集成 Etcd
  7. Nacos 实现配置中心和注册中心
  8. gRPC 跨语言通信

一、微服务基础概念

1.1 什么是微服务?

微服务架构是将单体应用拆分成多个独立的、松耦合的服务,每个服务负责特定的业务功能。

单体架构 vs 微服务架构:

特性单体架构微服务架构
代码管理所有代码在一个项目代码拆分到多个项目
部署方式整体部署独立部署
扩展性只能整体扩展按需扩展单个服务
维护难度项目大了难维护相对容易维护

1.2 为什么需要微服务?

  • 项目越来越大,模块越来越多,代码难以维护
  • 某些业务模块需要单独扩展,但单体架构只能整体扩展
  • 团队可以并行开发不同的微服务

1.3 微服务通信方式

微服务之间一般不用 HTTP,而是用 TCP:

  • HTTP 会携带大量 header,增大通信开销
  • TCP 更高效,适合服务间通信

1.4 配置中心和注册中心

配置中心:

  • 集中管理各微服务的配置信息
  • 修改配置后自动通知所有服务
  • 避免配置散落在各个服务中

注册中心:

  • 管理微服务的所有实例节点
  • 支持服务注册、服务发现
  • 动态感知节点的增删

二、Nest 创建微服务

2.1 安装微服务包

npm install @nestjs/microservices --save

2.2 创建微服务

微服务端(提供 TCP 端口)

// 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();

微服务暴露方法

// 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

// 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 {}

注入并调用微服务

// 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 通信方式对比

装饰器客户端方法说明
@MessagePatternclient.send()请求-响应式,一问一答
@EventPatternclient.emit()事件式,只发不收

2.5 TCP 通信消息格式

微服务之间传输的是 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 应用

# 创建 Nest 项目
nest new monorepo-test

# 添加新的应用
nest g app app2

执行后目录结构变为:

monorepo-test/
├── apps/
│   ├── monorepo-test/    # 原有应用
│   └── app2/             # 新添加的应用
├── nest-cli.json
└── package.json

3.3 运行指定应用

# 运行默认应用
npm run start:dev

# 运行指定应用
npm run start:dev app2

# 构建指定应用
npm run build
npm run build app2

3.4 nest-cli.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(公共库)

nest g lib lib1

生成的目录结构:

libs/
└── lib1/
    └── src/
        ├── lib1.module.ts
        ├── lib1.service.ts
        └── index.ts

3.6 使用 Library

// 在应用中导入 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

npm run start:dev lib1
npm run build lib1

四、配置中心与注册中心

4.1 配置中心的作用

  • 集中管理配置:所有微服务的配置统一存放
  • 动态更新:配置修改后自动通知所有服务
  • 环境隔离:支持不同环境的配置管理

4.2 注册中心的作用

  • 服务注册:服务启动时向注册中心注册
  • 服务发现:调用服务前查询可用的实例
  • 健康检查:通过心跳检测服务是否存活
  • 动态伸缩:节点增删自动感知

4.3 常见的中间件

中间件特点
EtcdK8s 使用,轻量级,key-value 存储
Nacos阿里开源,自带控制台,功能丰富
Apollo携程开源,功能强大,配置管理专精
EurekaSpring Cloud 默认,Netflix 开源

五、Etcd 实现配置中心和注册中心

5.1 什么是 Etcd?

Etcd 是一个分布式 key-value 存储服务,K8s 就是用它来实现配置中心和注册中心。

5.2 启动 Etcd

使用 Docker 启动:

docker run -d --name etcd \
  -p 2379:2379 \
  -e ETCD_ROOT_PASSWORD=your_password \
  bitnami/etcd:latest

5.3 Etcdctl 命令行工具

# 基本操作
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

npm install etcd3
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 实现配置中心

// 保存配置
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 实现注册中心

// 服务注册
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 基础集成

// 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

// 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 封装动态模块

// 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 使用动态模块

// 使用 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

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

npm install nacos

7.4 服务注册中心用法

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 配置中心用法

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

特性EtcdNacos
控制台无,需命令行自带 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 是一种语言中立、平台中立的序列化协议。

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 安装依赖

npm install @grpc/grpc-js @grpc/proto-loader @nestjs/microservices

8.5 gRPC 服务端

// 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();
// 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 客户端

// 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 {}
// 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 文件复制

// 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

十一、架构图示例

┌─────────────────────────────────────────────────────────┐
│                      客户端 (HTTP)                       │
└─────────────────────────┬───────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────┐
│                    API Gateway                           │
└────┬─────────────────────────────────────┬──────────────┘
     │                                     │
┌────▼──────────┐  gRPC/TCP   ┌───────────▼──────────┐
│  User Service │◄───────────►│  Order Service        │
└───────────────┘             └───────────────────────┘
     │                               │
     │                               │
┌────▼───────────────────────────────▼──────────────────┐
│           注册中心 (Etcd/Nacos)                        │
│  - 服务注册/发现                                        │
│  - 健康检查                                             │
└───────────────────────────────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────┐
│           配置中心 (Etcd/Nacos)                          │
│  - 集中配置管理                                          │
│  - 动态配置更新                                          │
└───────────────────────────────────────────────────────┘

参考文档: