1. 当单体遇到分布式:微服务的前世今生
去年双十一那天,某电商平台的订单服务突然崩溃——这是典型单体架构的"午夜惊魂"。当业务发展到千万级日活时,服务拆分就成为必然选择。就像乐高积木需要模块化设计,我们的应用系统也需要通过微服务实现业务解耦。
在Node.js生态中,Express和NestJS这对"轻量级与重量级"组合,就像是瑞士军刀与多功能工具箱的区别。举个直观的例子:传统Express搭建的简单用户服务可能只有300行代码,而采用NestJS的商品服务却能在同等功能下保持更好的可维护性。
2. 技术栈选择:Express的轻与NestJS的重
2.1 Express的敏捷之刃
(技术栈:Express + TypeScript)
// user-service/app.ts
import express, { Request, Response } from 'express';
import { UserRepository } from './repositories/user.repository';
const app = express();
app.use(express.json());
// 用户查询接口
app.get('/users/:id', async (req: Request, res: Response) => {
try {
const user = await UserRepository.findById(req.params.id);
res.json({ data: user });
} catch (error) {
res.status(500).json({ error: '服务不可用' });
}
});
// 健康检查端点
app.get('/health', (req, res) => {
res.sendStatus(200);
});
app.listen(3000, () => {
console.log('用户服务已启动:3000');
});
这段代码展示了典型Express微服务的核心要素:路由管理、错误处理、健康检查。但缺乏依赖注入等企业级功能,当需要实现服务注册等治理功能时,就需要自行封装中间件。
2.2 NestJS的架构之美(技术栈:NestJS + TypeScript)
// product-service/src/product.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { ProductService } from './product.service';
@Controller('products')
export class ProductController {
constructor(private readonly productService: ProductService) {}
@Get(':id')
async getProduct(@Param('id') id: string) {
return this.productService.findById(id);
}
}
// product-service/src/product.module.ts
import { Module } from '@nestjs/common';
import { ProductController } from './product.controller';
import { ProductService } from './product.service';
@Module({
controllers: [ProductController],
providers: [ProductService]
})
export class ProductModule {}
NestJS通过装饰器和模块化设计,实现了开箱即用的依赖注入。产品服务的领域层、应用层天然分离,配合CLI工具能快速生成服务脚手架。
3. 微服务治理三板斧
3.1 服务发现:微服务的黄页电话簿(技术栈:NestJS + Consul)
// gateway/src/app.module.ts
import { ConsulModule } from '@nest-micro/consul';
@Module({
imports: [
ConsulModule.register({
host: 'consul-server',
port: '8500',
service: {
id: 'gateway',
name: 'api-gateway'
}
})
]
})
export class AppModule {}
通过集成Consul实现服务注册发现,就像给每个微服务分配专属名片。当用户服务扩容时,网关会自动感知新实例,智能分配流量。
3.2 配置中心:动态更新的魔法书(技术栈:Express + Apollo)
// order-service/config-loader.js
const { ApolloClient } = require('apollo-client');
const configClient = new ApolloClient({
configServer: 'http://config:8080',
appId: 'order-service'
});
// 热更新配置
configClient.on('refresh', (newConfig) => {
process.env.RATE_LIMIT = newConfig.rateLimit;
});
采用Apollo配置中心实现动态参数调整。当需要紧急调整限流阈值时,无需重启服务即可生效,仿佛给运行中的飞机更换发动机。
3.3 断路器模式:系统的紧急制动器(技术栈:NestJS + Resilience)
// payment-service/src/payment.service.ts
import { CircuitBreaker } from 'resilience-js';
export class PaymentService {
@CircuitBreaker({
timeout: 3000,
errorThreshold: 0.3,
resetTimeout: 10000
})
async processPayment(order: Order) {
return axios.post('http://bank-service/pay', order);
}
}
当银行服务响应超时率达到30%时,支付服务会自动熔断,避免雪崩效应。就像电路系统中的保险丝,在过载时主动切断故障链路。
4. 混合架构实战:Express与NestJS的协奏曲
在实际电商系统中,我们可以这样设计:
- 用户服务(Express):高频但逻辑简单的读取操作
- 商品服务(NestJS):需要严格领域模型的核心业务
- 订单服务(NestJS):涉及复杂事务管理的重点模块
混合部署的关键在于统一通信协议。建议所有服务都暴露RESTful接口,并使用JSON-RPC进行内部通信:
// Express服务调用NestJS服务示例
const { JSONRPCClient } = require('json-rpc');
const inventoryClient = new JSONRPCClient({
url: 'http://inventory-service/rpc'
});
router.post('/orders', async (req, res) => {
try {
const stock = await inventoryClient.request('checkStock', req.body);
// ...后续处理
} catch (error) {
res.status(503).json({ error: '库存服务暂不可用' });
}
});
5. 场景与边界的艺术
5.1 适用场景分析
- 社交平台:用户关系服务适合用Express快速迭代,即时通讯服务需要NestJS保证稳定性
- IoT系统:设备管理服务适合NestJS维护状态,数据采集服务可用Express处理高并发
- 金融系统:账户核心采用NestJS严格分层,对账服务用Express处理批量任务
5.2 技术选型决策树
是否需要类型系统? → 是 → NestJS
↓
否 → 是否需要快速验证原型? → 是 → Express
↓
否 → 需要哪些企业级功能?
6. 避坑指南:血泪换来的经验
服务拆分粒度:某旅游平台曾把机票搜索拆分成10个微服务,导致调试噩梦。建议按照业务变更频率拆分,高频变动的服务独立部署。
版本地狱:在电商系统中,采用语义化版本控制:
GET /api/v1/products Accept: application/json; version=1.2
日志追踪:使用全链路ID关联日志,ELK方案配置示例:
morgan(':traceId :method :url')
数据一致性:采用Saga模式时,补偿操作要比主操作更健壮。建议补偿接口实现幂等性:
@Post('/cancel') async cancelOrder(@Body() payload: { id: string, reason: string }) { return this.orderService.cancel(payload.id); // 幂等实现 }
7. 未来已来:Serverless与微服务的碰撞
当函数计算遇上微服务架构,新的可能性正在展开。比如将Express服务部署为Serverless函数:
functions:
userService:
handler: user-service/app.handler
events:
- http: GET /users/{id}
这种混合架构既能享受微服务的灵活性,又能获得Serverless的弹性扩缩能力。但需要注意冷启动问题,可通过预置并发优化。