关于 microservices-patterns
用于设计独立服务及其协作方式的架构技能,涵盖系统拆分、通信、数据所有权和弹性模式,也讨论何时单体架构可能更合适。
主要功能
- 01评估单体与微服务的取舍
- 02用限界上下文定义服务边界
- 03选择同步或异步通信
- 04设计基于 Saga 的分布式事务
- 05应用熔断器和舱壁模式
- 06使用 CQRS 和事件溯源
适用场景
- 01规划逐步拆分单体系统
- 02选择 REST、gRPC 或事件通信
- 03协调多服务业务事务
- 04分离读模型与写模型
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill microservices-patterns安装后在 Claude Code 中输入/microservices-patterns确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.claude-plugin/plugin.jsonreferences/decomposition-strategies.mdcommunication-patterns.md详细介绍
微服务模式
将系统拆分为独立服务,并确保它们可靠协作的实践。涵盖拆分策略、通信模式、数据所有权,以及防止分布式系统连锁故障并彻底失效的韧性模式。
适用场景
适用于:
- 决定是否拆分单体,以及从哪里开始
- 使用限界上下文和领域驱动设计来设计服务边界
- 选择同步(REST/gRPC)还是异步(事件)通信
- 为分布式事务实现 Saga 模式
- 设计 API 网关和面向前端的后端(BFF)层
- 应用熔断器、舱壁和重试模式
- 事件溯源和 CQRS 设计
- 服务发现和负载均衡策略
不适用于:
- 单体内部架构(使用
database-design-patterns、api-architect) - 无服务器函数设计和部署
- Kubernetes 基础设施和部署配置(使用
terraform-iac-expert) - 服务网格配置(Istio、Linkerd)——可以提及,但不是核心重点
- 单个服务的性能优化(使用
performance-profiling)
核心决策:单体与微服务及模块化单体
flowchart TD
Start[New project or architecture review] --> TeamSize{Team size?}
TeamSize -->|1-8 engineers| Small[Modular monolith first]
TeamSize -->|9-25| Medium{Domain complexity?}
TeamSize -->|25+| Large{Independent deploy needed?}
Small --> S1[Build well-factored modules with clear boundaries]
S1 --> S2{Growing pains?}
S2 -->|No| S1
S2 -->|Yes: deploy conflicts, team coupling| Extract[Extract services at natural seams]
Medium -->|Simple, few domains| Modular[Modular monolith]
Medium -->|Complex, many bounded contexts| MicroQ{Org structure?}
MicroQ -->|Teams align to domains| Micro[Microservices]
MicroQ -->|Teams are cross-functional| Modular
Large -->|Yes, teams blocked waiting for each other| Micro
Large -->|No, deploys are coordinated| Modular
Micro --> Check{Check: do services deploy independently?}
Check -->|No, they must release together| Problem[You have a distributed monolith]
Check -->|Yes| Proceed[Proceed with microservices]
何时值得采用微服务
微服务的优势:独立扩展、独立部署、技术多样性、故障隔离。代价:分布式系统复杂性、最终一致性、运维开销、网络延迟。
以下情况适合采用微服务:
- 系统的不同部分对扩展的要求差异极大
- 团队规模足够大,单一代码库会带来协调开销
- 领域足够稳定,服务边界不会频繁变化
- 团队具备管理分布式系统所需的运维成熟度(可观测性、部署自动化、值班)
拥有 4 名工程师、每天都在交付功能的初创公司,几乎肯定不应该构建微服务。拥有 200 名工程师、结账团队因等待目录团队发布而受阻的公司,则属于适合采用微服务的情况。
服务拆分
限界上下文
限界上下文是领域模型适用范围的明确边界。边界内的语言、概念和规则保持一致。在边界处,会进行明确的转换。
Order Service:
- "customer" = { id, shippingAddress, paymentMethod }
- "product" = { id, price, quantity }
Catalog Service:
- "product" = { id, name, description, images, attributes, category }
- "customer" — not a concept here at all
Recommendation Service:
- "customer" = { id, browsingHistory, purchaseHistory }
- "product" = { id, category, tags }
同一个词(“产品”)在不同上下文中含义不同。这是合理的——强行让所有服务共用单一模型会造成紧密耦合。
绞杀者无花果模式
安全拆分单体的方式:通过门面转发流量,逐步提取功能,绝不进行大爆炸式重写。
flowchart LR
Client --> Facade[API Gateway / Facade]
Facade --> Monolith[(Monolith)]
Facade --> NewService[New Service]
subgraph "Phase 1: Identify seam"
Monolith
end
subgraph "Phase 2: Route new traffic"
NewService
end
subgraph "Phase 3: Migrate & delete"
Monolith -->|Sunset| Deleted[Deleted]
end
步骤:
- 确定切入点——选择单体中输入/输出明确且内部依赖最少的模块
- 搭建新服务——独立实现相同功能
- 将新流量路由到新服务;旧流量仍流向单体
- 逐步迁移旧数据和流量
- 在充分确认后删除单体中的代码
切勿试图一次性提取整个单体。一次只处理一个切入点。
通信模式
同步与异步
flowchart TD
Decision[Choosing communication] --> Need{Does caller need an immediate response?}
Need -->|Yes, and can fail if downstream is down| Sync[Synchronous: REST or gRPC]
Need -->|No, or needs to tolerate downstream outages| Async[Asynchronous: events/messages]
Sync --> SyncQ{Protocol?}
SyncQ -->|CRUD operations, public APIs| REST[REST/HTTP]
SyncQ -->|Internal, high throughput, streaming| GRPC[gRPC]
Async --> AsyncQ{Pattern?}
AsyncQ -->|Fire and forget, fan-out| Events[Event bus: Kafka, NATS, SNS]
AsyncQ -->|Work queue, at-least-once delivery| Queue[Message queue: SQS, RabbitMQ]
AsyncQ -->|Multi-step transaction coordination| Saga
熔断器
防止响应缓慢或宕机的下游服务拖垮调用方。
States:
CLOSED (normal) → requests pass through
OPEN (tripped) → requests fail fast without calling downstream
HALF-OPEN (probe) → one request allowed through to test recovery
Transitions:
CLOSED → OPEN: failure threshold exceeded (e.g., 5 failures in 10 seconds)
OPEN → HALF-OPEN: after timeout (e.g., 30 seconds)
HALF-OPEN → CLOSED: probe request succeeds
HALF-OPEN → OPEN: probe request fails
// Example using opossum (Node.js circuit breaker library)
const CircuitBreaker = require('opossum');
const options = {
timeout: 3000, // If function takes longer than 3s, trigger failure
errorThresholdPercentage: 50, // Open circuit when 50% of requests fail
resetTimeout: 30000, // Try again after 30s
};
const breaker = new CircuitBreaker(callPaymentService, options);
breaker.on('open', () => console.log('Circuit open — payment service unreachable'));
breaker.on('halfOpen', () => console.log('Testing payment service recovery'));
breaker.on('close', () => console.log('Circuit closed — payment service recovered'));
// Fallback when circuit is open
breaker.fallback(() => ({ status: 'pending', message: 'Payment queued for retry' }));
舱壁
隔离故障:为每个下游服务配置独立的线程池/连接池,避免一个缓慢的服务耗尽所有资源。
// Naive: single shared pool — one slow dependency starves everything
const pool = new DatabasePool({ max: 50 });
// Bulkhead: separate pools per service
const pools = {
payments: new Pool({ max: 10 }), // Max 10 concurrent payment calls
catalog: new Pool({ max: 20 }), // Catalog can use more
notifications: new Pool({ max: 5 }), // Limit low-priority work
};
Saga 模式:分布式事务
Saga 用一系列本地事务取代分布式 ACID 事务(后者需要两阶段提交,开销较大)。每个本地事务都会发布事件或消息,以触发下一步。如果某一步失败,补偿事务会撤销之前的步骤。
编排式 Saga
中央协调器(saga 协调器)告知各服务要执行的操作,并在发生故障时发出补偿命令来处理。
sequenceDiagram
participant O as Order Saga Orchestrator
participant OS as Order Service
participant IS as Inventory Service
participant PS as Payment Service
participant NS as Notification Service
O->>OS: CreateOrder
OS-->>O: OrderCreated
O->>IS: ReserveInventory
IS-->>O: InventoryReserved
O->>PS: ProcessPayment
alt Payment succeeds
PS-->>O: PaymentProcessed
O->>NS: SendConfirmation
NS-->>O: NotificationSent
O->>OS: MarkOrderComplete
else Payment fails
PS-->>O: PaymentFailed
O->>IS: ReleaseInventory [compensating transaction]
O->>OS: CancelOrder [compensating transaction]
end
何时使用编排:工作流复杂、步骤众多且包含条件分支时。Saga 状态和故障处理都明确且集中。更易于观察(只需查看一个地方),但会产生一个掌握过多信息的中央协调器。
协作式 Saga
没有中央协调器。每个服务都会监听事件并决定要执行的操作,然后发出自己的事件。
sequenceDiagram
participant OS as Order Service
participant IS as Inventory Service
participant PS as Payment Service
participant NS as Notification Service
participant EB as Event Bus
OS->>EB: OrderCreated
EB->>IS: OrderCreated (consumed)
IS->>EB: InventoryReserved
EB->>PS: InventoryReserved (consumed)
alt Payment succeeds
PS->>EB: PaymentProcessed
EB->>NS: PaymentProcessed (consumed)
NS->>EB: NotificationSent
EB->>OS: NotificationSent (consumed)
OS->>OS: MarkOrderComplete
else Payment fails
PS->>EB: PaymentFailed
EB->>IS: PaymentFailed (consumed)
IS->>IS: ReleaseInventory [compensating]
IS->>EB: InventoryReleased
EB->>OS: InventoryReleased (consumed)
OS->>OS: CancelOrder [compensating]
end
何时使用协作:步骤较少的简单工作流。服务更加自主——没有任何服务了解整体流程。更难观察(必须跨服务追踪),但耦合度更低。
反模式:分布式单体
新手:将应用拆分为 8 个服务,但每次发布都必须同时部署全部 8 个服务,因为它们共享数据库架构,或者进行同步调用,一旦版本不匹配就会出错。
专家:必须一起部署的微服务并非真正的微服务——它们是分布式单体,同时承受两种架构的所有缺点,却不具备任何一种架构的优点。真正的微服务可以独立部署,通过向后兼容的 API 和事件架构容忍版本差异,并独占其数据。如果你无法用“可以”回答“我能否在不影响服务 B 的情况下部署服务 A?”,那就说明你还没有完成拆分。
识别方法:你的部署操作手册写着“按此顺序部署这些服务”。服务运行不同版本时,集成测试会失败。各团队需要跨服务协调发布日期。
反模式:同步调用链
新手:用户请求抵达 API Gateway → Order Service → 调用 Inventory Service → 后者调用 Warehouse Service → 后者再调用 Shipping Service。全部使用同步 HTTP。
专家:4 次同步调用组成的链会放大延迟并降低可用性。如果每个服务的可用性为 99.9%,那么 4 个服务组成的调用链可用性只有 99.6%——每月停机 3.5 小时。延迟会累积:4 个服务各需 50ms,最低延迟就是 200ms,此外还要加上网络开销。对于不需要阻塞用户操作的任务,应使用异步事件,并对每次同步调用应用断路器模式。如果调用链超过 2-3 跳,就应重新设计数据所有权——调用方可能缺少本应由自己拥有的数据。
识别方法:分布式追踪中的请求瀑布图显示,服务 A 正在等待服务 B,而服务 B 正在等待服务 C。p99 延迟远高于 p50(尾延迟级联)。
反模式:共享数据库
新手:微服务共享一个 PostgreSQL 数据库,以避免跨服务访问数据的复杂性。
专家:共享数据库会在存储层形成紧密耦合。任何架构变更都必须与所有访问该表的服务协调。一个服务的慢查询可能会锁定另一个服务需要的行。你无法根据不同的数据访问模式独立扩展服务。每个服务都必须拥有自己的数据存储,包括架构、索引等。跨服务的数据访问需通过所有者服务的 API 或事件进行。没错,这意味着你无法跨服务边界执行 JOIN。而这正是迫使数据所有权保持清晰的约束。
识别方法:服务 B 修改了共享表架构,导致服务 A 的测试失败。多个服务使用相同的数据库连接凭据。架构迁移需要多个服务同时停机。
CQRS 和事件溯源
CQRS(命令查询职责分离)
将写入模型(命令,负责强制执行不变量)与读取模型(查询,针对显示进行优化)分离。
Write side: Read side:
POST /orders → OrderService GET /orders/{id} → OrderQueryService
Validates business rules Materialized view, denormalized
Writes to order aggregate Updated from events
Emits OrderPlaced event No business logic, just data
当读取模式与写入模式截然不同时,CQRS 很有价值——例如,写入操作需要验证复杂的业务规则,而读取操作需要跨越多个聚合的非规范化视图。
事件溯源
不存储当前状态,而是存储产生该状态的一系列事件。通过重放事件来推导当前状态。
// Traditional: store current state
await db.update('orders', { id, status: 'SHIPPED', shippedAt: new Date() });
// Event sourcing: store what happened
await eventStore.append('order-' + id, {
type: 'OrderShipped',
payload: { orderId: id, carrier: 'FedEx', trackingNumber: '9400...' },
timestamp: new Date(),
version: 4, // optimistic concurrency control
});
// Derive current state by replaying
async function getOrderState(orderId) {
const events = await eventStore.getEvents('order-' + orderId);
return events.reduce(applyEvent, { status: null, items: [], history: [] });
}
事件溯源提供完整的审计日志、时间回溯能力(可重放到任意时间点),以及根据历史事件派生新读取模型的能力。权衡之处在于:查询更困难(必须使用投影),而且事件架构演进需要仔细进行版本管理。
API 网关和 BFF
API 网关的职责
- 身份验证/授权:验证 JWT,在转发前检查作用域
- 速率限制:按客户端、按端点进行限制
- 请求路由:根据路径/标头路由到适当的服务
- 协议转换:将外部 REST 转为内部 gRPC,反之亦然
- 响应聚合:扇出请求到多个服务并合并结果
- SSL 终止:对外处理 HTTPS,对内使用 HTTP
前端后端(BFF)
使用一个通用 API 网关会带来问题:移动客户端需要较小的负载,Web 客户端需要丰富的数据,而网关不得不为所有客户端做权衡。BFF 为每种客户端类型创建单独的网关。
Mobile App → BFF-Mobile → [User Service, Order Service]
(small payloads, battery-conscious)
Web App → BFF-Web → [User Service, Order Service, Recommendation Service]
(rich data, aggregated views)
Third-party API → Public API Gateway → (rate-limited, versioned, documented)
服务发现
客户端发现
客户端查询服务注册表(Consul、Eureka),并自行进行负载均衡。
// Client-side: ask registry, then call directly
const instances = await consul.health.service({ service: 'payment-service', passing: true });
const instance = loadBalance(instances);
const url = `http://${instance.Service.Address}:${instance.Service.Port}`;
await fetch(`${url}/api/charge`);
服务端发现
由负载均衡器(nginx、AWS ALB、Kubernetes Service)处理服务发现。客户端调用负载均衡器,由它将请求路由到健康实例。这对客户端来说更简单——在 Kubernetes 中使用此方式(Kubernetes Services 会为你处理)。
Saga 编排:状态机设计
自行构建还是使用框架
生产环境中的 Saga 应使用 Temporal.io 或 AWS Step Functions。 它们会为你处理持久执行、崩溃恢复和可见性。只有在以下情况才有理由自行构建 Saga 引擎:(a) 你需要低于 10ms 的步骤延迟,而 Temporal 的持久化开销无法满足;或 (b) 你的组织无法采用其他基础设施依赖。
如果用户要自行构建,请按以下设计为其提供指导:
状态模型
每次步骤转换后,Saga 都会持久化一条状态记录。这是恢复机制——如果协调器崩溃,它会读取最后一次持久化的状态并继续执行。
interface SagaState {
sagaId: string;
status: 'running' | 'compensating' | 'completed' | 'failed';
currentStepIndex: number;
completedSteps: string[]; // Step names that succeeded
compensatedSteps: string[]; // Step names that were compensated
context: Record<string, unknown>; // Accumulates results from each step
startedAt: string; // ISO timestamp
updatedAt: string;
failureReason?: string;
}
将其持久化到数据库(Postgres JSONB 列即可)——不要使用 Redis,也不要只保存在内存中。如果协调器崩溃,状态必须保留下来。
执行规则
-
正向执行:按顺序运行各步骤。每个步骤成功后,持久化状态,将
currentStepIndex++,并将步骤结果合并到context中。先持久化再推进的顺序很重要——如果进程在步骤执行完毕但尚未持久化时崩溃,恢复后该步骤会重新运行。步骤必须是幂等的。 -
失败触发补偿:步骤失败时,将
status设为'compensating',并按反向顺序从最后一个已完成步骤开始运行补偿操作。只补偿已包含在completedSteps中且尚未包含在compensatedSteps中的步骤。 -
补偿也要持久化:每次补偿成功后,将步骤名称添加到
compensatedSteps并持久化。如果协调器在补偿期间崩溃,它会从中断处继续补偿——绝不会对同一步骤重复补偿。 -
每个步骤都设置超时:每个步骤自行声明超时时间。支付扣款可能需要 30s;邮件通知则需要 5s。使用
Promise.race([step.command(context), timeout(step.timeoutMs)])包装步骤执行。超时后,将其视为失败并开始补偿。 -
恢复逻辑:协调器启动时,查询
status: 'running'或status: 'compensating'的 Saga。对于running,重新执行currentStepIndex处的步骤(步骤是幂等的,因此安全)。对于compensating,从第一个尚未补偿的步骤开始继续补偿(反向顺序)。
示例:订单 Saga
Steps (forward):
1. reserveInventory → compensate: releaseInventory
2. chargePayment → compensate: refundPayment
3. shipOrder → compensate: cancelShipment
4. sendConfirmation → compensate: sendCancellationEmail
If chargePayment fails:
→ compensate releaseInventory (only step 1 completed)
→ set status: 'failed', record failureReason
If shipOrder fails:
→ compensate refundPayment (step 2)
→ compensate releaseInventory (step 1)
→ reverse order matters: refund before releasing inventory
不要这样做
- 不要使用内存状态机 — 它会随进程一起终止,而你的 Saga 会卡在执行到一半的状态,无法恢复。
- 不要按正向顺序执行补偿 — 如果在释放库存后退款,其他客户可能已经抢走了这些库存。按逆序执行才能保持逻辑一致性。
- 不要无限重试 — 补偿操作也可能失败。补偿重试 3 次后,应发出警报并请求人工介入。Saga 卡在
compensating状态数小时,远不如明确报错。 - 不要跳过幂等性处理 — 整个崩溃恢复机制都依赖它。使用相同的 Saga ID 两次调用
reserveInventory,必须得到相同结果,而不是重复预留库存。
参考资料
references/communication-patterns.md— 适用于查阅:REST 与 gRPC 决策矩阵、异步消息代理(Kafka、RabbitMQ 与 SQS)、幂等性模式、Saga 编排与协调的权衡、CQRS 读模型模式references/decomposition-strategies.md— 适用于查阅:限界上下文识别、绞杀者无花果模式的实施步骤、领域驱动分解技术、团队拓扑对齐、数据库分解模式、数据迁移策略