不是所有模式都值得学
分布式系统有几十种设计模式,但真正在日常开发中反复出现的,就这六种。每个都带代码示例,不空谈理论。
模式一:Circuit Breaker(熔断器)
问题:下游服务挂了,你的服务一直在重试,耗尽线程池。
方案:像一个电路保险丝——失败超过阈值就"断开",快速失败。
class CircuitBreaker:
def __init__(self, fail_threshold=5, timeout=60):
self.fail_count = 0
self.state = 'CLOSED' # CLOSED / OPEN / HALF_OPEN
self.threshold = fail_threshold
self.timeout = timeout
self.last_fail_time = None
def call(self, func, *args):
if self.state == 'OPEN':
if time.time() - self.last_fail_time > self.timeout:
self.state = 'HALF_OPEN' # 试探性恢复
else:
raise Exception('Circuit breaker is OPEN')
try:
result = func(*args)
if self.state == 'HALF_OPEN':
self.state = 'CLOSED' # 恢复了
self.fail_count = 0
return result
except Exception:
self.fail_count += 1
self.last_fail_time = time.time()
if self.fail_count >= self.threshold:
self.state = 'OPEN'
raise
适用场景:任何调用外部服务的场景。几乎所有微服务框架都内置了(Hystrix、Resilience4j、Polly)。
模式二:Bulkhead(舱壁隔离)
问题:一个慢接口拖垮整个线程池。
方案:为不同下游服务分配独立的线程池。
# Java 示例
ThreadPoolExecutor userPool = new ThreadPoolExecutor(10, 10, ...);
ThreadPoolExecutor orderPool = new ThreadPoolExecutor(5, 5, ...);
ThreadPoolExecutor reportPool = new ThreadPoolExecutor(2, 2, ...);
// 即使 reportPool 满了,userPool 和 orderPool 也不受影响
模式三:CQRS(命令查询职责分离)
问题:同一套数据模型既要满足高并发写入,又要满足复杂的聚合查询。
方案:读写分离——写走主库,读走专为查询优化的读模型。
写路径(Command):
POST /orders → MySQL(范式化,快速写入)
读路径(Query):
GET /reports → Elasticsearch(反范式化,快速聚合)
同步:CDC (Change Data Capture) → Debezium → Kafka → ES
关键:接受读写之间的短暂延迟(最终一致性),换性能。
模式四:Saga(分布式事务的务实方案)
问题:一个操作跨多个服务,需要全部成功或全部回滚。
核心思想:每个服务有自己的本地事务 + 补偿操作。
创建订单 Saga:
1. 订单服务:创建订单(状态=CREATED) ← 补偿:标记为 CANCELLED
2. 库存服务:扣减库存 ← 补偿:回滚库存
3. 支付服务:扣款 ← 补偿:退款
4. 订单服务:更新状态(状态=CONFIRMED)
# 以编排(Orchestration)方式实现
class OrderSaga:
def execute(self, order):
steps = [
(self.create_order, self.cancel_order),
(self.reserve_stock, self.rollback_stock),
(self.charge_payment, self.refund_payment),
]
executed = []
for step, compensate in steps:
try:
step(order)
executed.append(compensate)
except Exception:
for c in reversed(executed):
c(order)
raise
模式五:Event Sourcing(事件溯源)
问题:需要完整的操作审计日志,而且任何时候都能回溯到历史状态。
方案:不存当前状态,存所有事件。当前状态 = events.reduce(apply)。
# 不存这个:
{ "account": "A", "balance": 100 }
# 存这些事件:
[
{ "type": "AccountCreated", "account": "A" },
{ "type": "Deposited", "amount": 200 },
{ "type": "Withdrawn", "amount": 50 },
{ "type": "Deposited", "amount": 30 },
{ "type": "Withdrawn", "amount": 80 }
]
# 余额 = 200 - 50 + 30 - 80 = 100
# 而且能看到完整的账户变动历史
模式六:Sidecar(边车模式)
问题:每个服务都要集成日志收集、监控埋点、服务发现——重复代码。
方案:把这些横切关注点放到独立的 sidecar 进程中。Service Mesh 就是这个模式的终极应用。
Pod:
├── app-container (你的业务代码)
└── envoy-sidecar (处理流量、TLS、限流、监控)
你的代码只需要发请求到 localhost:15000,剩下的 Envoy 全包了。
模式选择决策树
请求失败率高 → Circuit Breaker 资源隔离需求 → Bulkhead 读写性能矛盾 → CQRS 跨服务事务 → Saga 需要审计回溯 → Event Sourcing 横切关注点多 → Sidecar
记住:模式是用来解决问题的,不是用来套的。没见过的问题不要硬找模式来套,简单的方案永远是更好的方案。