Nest.js 微服务入门教程

目录

  1. 微服务基础概念
  2. [Nest 创建微服务](#Nest 创建微服务 "#%E4%BA%8Cnest-%E5%88%9B%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1")
  3. [Monorepo 与 Library](#Monorepo 与 Library "#%E4%B8%89monorepo-%E4%B8%8E-library")
  4. 配置中心与注册中心
  5. [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")
  6. [Nest 集成 Etcd](#Nest 集成 Etcd "#%E5%85%ADnest-%E9%9B%86%E6%88%90-etcd")
  7. [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")
  8. [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)                          │
│  - 集中配置管理                                          │
│  - 动态配置更新                                          │
└───────────────────────────────────────────────────────┘

参考文档:

相关推荐
沐沐师1 小时前
Redis 入门教程
redis·nestjs
万象观察录6 小时前
微服务架构下的API全生命周期安全防护办法
安全·微服务·架构
谷无姜7 小时前
QuizForge:在不停踩坑后的技术决策复盘
前端·nestjs
夏天拐跑了西瓜1 天前
Spring Cloud 微服务实战(八):Hystrix熔断降级——从服务雪崩到断路器模式
spring cloud·hystrix·微服务
vHelios1 天前
【电商项目】短信测试遇到的问题与解决方案:InaccessibleObjectException报错、单元测试通过但未接收到验证码
java·微服务
天天喝旺仔1 天前
gRPC 流式通信实战:从一元调用到双向流,吃透四种 RPC 模式(Go 版)
分布式·http·微服务·rpc·go
野熊佩骑2 天前
Kubernetes实战系列文章(三) 之 K8S运维常用命令
linux·运维·docker·微服务·云原生·容器·kubernetes
ZYJCSZKJ2 天前
基于微服务架构的本地生活POI团购系统设计与高并发实践
微服务·架构·生活
重生之我是Java开发战士2 天前
【Spring Cloud】认识微服务与工程搭建
spring·spring cloud·微服务