微服务设计模式实战
参与过三个微服务项目,有成功的,也有失败的。总结一些设计模式。
单体 vs 微服务
| 维度 | 单体 | 微服务 |
|---|---|---|
| 部署 | 简单 | 复杂 |
| 扩展 | 整体扩展 | 按需扩展 |
| 开发 | 简单 | 需要协调 |
| 故障影响 | 可能全局 | 局部隔离 |
| 运维成本 | 低 | 高 |
不要为了微服务而微服务。小团队、早期项目,单体更合适。
服务拆分
按业务能力拆分
用户服务:注册、登录、权限
订单服务:下单、支付、退款
商品服务:商品管理、库存
通知服务:消息推送
拆分原则
| 原则 | 说明 |
|---|---|
| 高内聚 | 相关功能放一起 |
| 松耦合 | 服务间依赖少 |
| 独立部署 | 改一个不影响其他 |
| 数据隔离 | 每个服务有自己的数据库 |
服务发现
客户端发现
服务消费者直接查询注册中心:
class ServiceClient {
constructor(registryUrl) {
this.registry = registryUrl;
this.cache = new Map();
}
async getServiceUrl(serviceName) {
if (this.cache.has(serviceName)) {
return this.cache.get(serviceName);
}
const response = await fetch(`${this.registry}/services/${serviceName}`);
const instances = await response.json();
// 简单轮询
const instance = instances[Math.floor(Math.random() * instances.length)];
this.cache.set(serviceName, instance.url);
return instance.url;
}
}
服务端发现
通过网关代理:
客户端 → API 网关 → 服务实例
Nginx、Kong 都可以做。
通信模式
同步通信
REST 或 gRPC:
// 调用用户服务
const user = await fetch(`${userServiceUrl}/users/${userId}`).then(r => r.json());
简单直接,但有耦合。
异步通信
消息队列:
// 发布事件
await messageQueue.publish('user.created', {
userId: user.id,
email: user.email,
});
// 订阅事件
messageQueue.subscribe('user.created', async (event) => {
await sendWelcomeEmail(event.email);
});
解耦,但调试复杂。
| 模式 | 优点 | 缺点 |
|---|---|---|
| 同步 | 简单、实时 | 耦合、级联故障 |
| 异步 | 解耦、削峰 | 复杂、调试难 |
API 网关
网关是微服务的入口:
# Nginx 配置
location /api/users {
proxy_pass http://user-service;
}
location /api/orders {
proxy_pass http://order-service;
}
location /api/products {
proxy_pass http://product-service;
}
网关职责:
| 职责 | 说明 |
|---|---|
| 路由 | 请求转发 |
| 认证 | 统一鉴权 |
| 限流 | 保护后端 |
| 熔断 | 故障隔离 |
| 日志 | 请求追踪 |
数据一致性
分布式事务
不要用两阶段提交,太慢。用 Saga 模式:
// 订单创建 Saga
async function createOrderSaga(order) {
const steps = [
{ execute: () => createOrder(order), compensate: () => cancelOrder(order.id) },
{ execute: () => reserveInventory(order.items), compensate: () => releaseInventory(order.items) },
{ execute: () => processPayment(order.payment), compensate: () => refundPayment(order.payment.id) },
];
const executed = [];
for (const step of steps) {
try {
await step.execute();
executed.push(step);
} catch (error) {
// 补偿已执行的步骤
for (const s of executed.reverse()) {
await s.compensate();
}
throw error;
}
}
}
最终一致性
接受短暂不一致,通过事件同步:
// 发布库存变更事件
await inventoryService.decrement(productId, quantity);
await eventBus.publish('inventory.decremented', { productId, quantity });
// 订单服务监听
eventBus.subscribe('inventory.decremented', async (event) => {
await orderService.updateStatus(event.productId, 'inventory_checked');
});
容错设计
熔断器
class CircuitBreaker {
constructor(fn, options = {}) {
this.fn = fn;
this.failureThreshold = options.failureThreshold || 5;
this.resetTimeout = options.resetTimeout || 30000;
this.state = 'CLOSED';
this.failures = 0;
}
async call(...args) {
if (this.state === 'OPEN') {
throw new Error('Circuit is open');
}
try {
const result = await this.fn(...args);
this.failures = 0;
return result;
} catch (error) {
this.failures++;
if (this.failures >= this.failureThreshold) {
this.state = 'OPEN';
setTimeout(() => {
this.state = 'HALF_OPEN';
this.failures = 0;
}, this.resetTimeout);
}
throw error;
}
}
}
重试
async function withRetry(fn, options = {}) {
const maxRetries = options.maxRetries || 3;
const delay = options.delay || 1000;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
await sleep(delay * Math.pow(2, i));
}
}
}
降级
async function getProduct(productId) {
try {
return await productService.getProduct(productId);
} catch (error) {
// 降级:返回缓存或默认值
return cache.get(`product:${productId}`) || defaultProduct;
}
}
监控追踪
分布式追踪
每个请求有唯一 ID:
// 中间件生成 trace ID
app.use((req, res, next) => {
req.traceId = req.headers['x-trace-id'] || uuid();
res.setHeader('x-trace-id', req.traceId);
next();
});
// 调用下游服务时传递
const response = await fetch(url, {
headers: {
'x-trace-id': req.traceId,
},
});
集中式日志
traceId: abc-123 | service: user | action: getUser | userId: 456
traceId: abc-123 | service: order | action: getOrders | userId: 456
用 ELK 或 Loki 汇总日志。
总结
微服务不是银弹,增加了系统复杂度。
关键点:
- 服务拆分要合理
- 数据一致性是难点
- 容错设计不可少
- 监控追踪要跟上
小团队不建议上微服务,单体够用。