为什么需要微服务

当单体应用逐渐膨胀到没有人能完整理解全貌时,微服务的价值就体现出来了。它能将复杂度分散到多个独立部署的服务中,每个团队负责自己的领域。

单体真正的痛点通常不是性能,而是工程效率:

  • 构建与发布耦合:改一行营销代码,也要重新部署整个交易系统,回归测试范围无限膨胀
  • 技术栈锁定:一个 10 年老单体,连升级框架版本都是高风险项目
  • 故障域不隔离:一个边缘功能的内存泄漏,把整个应用拖垮
  • 团队协作冲突:几十人改同一个仓库,合并冲突、发布窗口排队成了日常

但微服务也带来了分布式系统固有的复杂性:网络不可靠、数据一致性、服务发现、链路追踪,这些都需要在架构设计时提前考虑。

一个务实的判断标准:当你有多个独立的功能团队、且发布频率被单体的发布流程拖累时,才是拆分的合理时机。团队规模不到十人、业务边界还很模糊时,一个边界清晰的模块化单体(Modular Monolith)往往是更好的选择。

服务拆分原则

拆分粒度是微服务最常被讨论的话题。过粗等于没拆,过细则运维成本飙升。推荐遵循以下几个原则:

  • 按业务领域拆分:参考 DDD 的限界上下文,每个服务对应一个业务领域
  • 数据独立:每个服务拥有自己的数据库,不直接访问其他服务的库
  • 高内聚低耦合:频繁调用的逻辑应放在同一个服务内

用事件风暴识别边界

实践中识别限界上下文的常用方法是事件风暴:把业务专家和工程师拉到一起,把领域事件(订单已创建、库存已扣减、支付已完成)贴到墙上,再找出发出命令的角色和聚合。事件聚集密集、且与外界交互少的区域,就是一个天然的上下文边界。

拆分的反模式

  • 按技术层拆(一个服务管所有数据库操作、一个管所有消息):把调用链拉长,团队无法端到端负责业务
  • 分布式单体:服务拆了,但每次发布仍需要多个服务按顺序一起上,这是最糟糕的状态——既有单体的耦合,又有分布式的运维成本
  • 共享数据库:两个服务读写同一个库,看似省事,实际任何 schema 变更都会互相伤害,等于没拆

服务间通信

同步通信常用 HTTP 或 gRPC,异步通信常用消息队列。实际项目中通常是混合使用。

选择通信方式的决策依据:

  • 需要实时拿到结果(如查询用户信息):同步调用,gRPC 比 JSON over HTTP 更高效且有强类型契约
  • 只需通知“某事已发生”(如订单已创建):发布事件,生产者不关心谁来消费
  • 需要长流程编排(如退款流程):事件驱动 + Saga,避免同步调用链把所有服务绑成一根绳
syntax = "proto3";

service UserService {
    rpc GetUser (GetUserRequest) returns (UserResponse);
}

message GetUserRequest {
    int64 user_id = 1;
}

message UserResponse {
    int64 id = 1;
    string name = 2;
    string email = 3;
}

同步调用的防护

跨服务调用必须假设下游会挂。三件套缺一不可:

  • 超时:没有超时的远程调用等于埋雷,故障时会耗尽上游线程池
  • 重试 + 幂等:只重试幂等操作,且配合指数退避和次数上限,避免重试风暴
  • 熔断 + 降级:连续失败时快速失败(fail fast),保护上游不被拖垮。Sentinel / resilience4j / Polly 都提供了现成实现
// resilience4j 示例:熔断 + 降级
@CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
@Retry(name = "userService")
public User getUser(long userId) {
    return userServiceClient.getUser(userId);
}

// 降级逻辑:返回缓存或默认值,而不是把错误抛给用户
private User getUserFallback(long userId, Throwable t) {
    return userCache.get(userId);
}

事件契约管理

异步通信的契约(事件结构)演进比 API 更难约束,因为消费者是隐式的。实践建议:事件 schema 注册到统一的 Schema Registry;只增字段不删不改(兼容性规则);用契约测试捕获不兼容变更。

数据一致性

跨服务的数据一致性是微服务最难的问题之一。Saga 模式是目前比较主流的解决方案,通过补偿事务来保证最终一致性。

不要试图在分布式系统中追求强一致性,接受最终一致性并设计好补偿机制,才是更务实的做法。

以电商下单为例,Saga 把“创建订单 + 扣库存 + 扣款”拆成本地事务序列,每一步失败就执行前面步骤的补偿:

正向:创建订单 → 扣减库存 → 扣款
补偿:订单标记失败 ← 回滚库存 ← 退款

任一步失败,反向执行已完成步骤的补偿操作

两种编排方式各有适用场景:

  • 编排式(Orchestration):一个中央协调器(如 Temporal、Seata)驱动流程,逻辑集中易理解,适合复杂长流程
  • 协同式(Choreography):各服务监听事件自行决定下一步,无中心依赖更松耦合,但流程分散、排查困难,适合简单链路

幂等是一切补偿的前提

补偿可能重试多次,每个参与方都必须幂等。常用手段是业务操作带上唯一请求 ID(requestId / 事务 ID),处理前先查去重表:

-- 每个 Saga 步骤执行前检查
INSERT INTO dedup (request_id, step) VALUES ('req-123', 'deduct-stock');
-- 唯一键冲突则说明已执行过,直接返回成功

分布式事务什么时候才需要

大多数场景下,本地事务 + 事务性发件箱(Transactional Outbox)+ 消息队列就足够:业务数据和事件在同一个本地事务里落库,由后台任务把事件投递到 MQ,保证“业务成功则事件必达”。2PC/XA 这种强一致方案锁资源、吞吐低,仅在资金核心链路等极少数场景使用。

可观测性:微服务的眼睛

服务拆散后,“一个请求慢在哪”的答案分散在多个服务里,可观测性不再是加分项而是必需品:

  • 统一链路追踪:traceID 从网关注入、跨服务透传(W3C Trace Context 标准),Jaeger / SkyWalking / OpenTelemetry 任选其一落地
  • 结构化日志:日志带上 traceID 输出 JSON,一个 grep 拉出请求全链路
  • RED 指标监控:每个服务暴露 Rate(QPS)、Errors(错误率)、Duration(耗时),配告警
  • 服务依赖图:定期 review 依赖关系,警惕单向依赖退化成循环依赖

发布层面,配置灰度发布和流量录制回放能力,让新版本先在 1% 流量上验证,是微服务环境下控制爆炸半径的最后防线。

总结

微服务是一种架构风格,不是目标。在单体还能运转时不必急于拆分,但在业务规模和团队规模达到临界点时,及时演进才能保持工程效率。拆分前想清楚三件事:业务边界是否清晰、团队是否具备分布式系统运维能力、可观测性基建是否就绪。记住那句老话:微服务解决的是组织沟通问题,其次才是技术问题——康威定律永远生效。