微服务时代

2015 年,Martin Fowler 发表了一篇文章《Microservices》,系统性地总结了这一架构风格的核心特征。这篇文章迅速在技术圈传播,成为后续几年无数公司「拆微服务」的理论依据。

但讽刺的是,很多人读完文章后,只记住了「微服务」三个字,忘记了文章副标题——「企业级应用开发者应该在认真考虑利弊后,再决定是否采用」。

这篇文章的正文只有 3000 单词,但「背景和动机」就占了 1000 单词。Martin Fowler 用大量篇幅描述了微服务诞生的背景、对 SOA 失败的反思、以及它适用的场景。但大多数人只看了摘要和核心特征。

微服务不是银弹。它是 SOA 失败的教训 + 云计算的普及 + 敏捷开发的兴起,共同推动的产物。

微服务诞生的背景

SOA 的失败教训

上一节我们讲了 SOA 的失败案例。SOA 失败的核心原因:

  • ESB 中心化:所有流量都经过 ESB,成为新的瓶颈
  • 服务粒度太粗:拆成了几个大服务,和单体没本质区别
  • 过度设计:为了抽象而抽象,导致系统复杂度爆炸

微服务在设计上刻意避开了这些坑:去中心化、细粒度服务、轻量级通信。

云计算的普及

2010 年代,AWS、Azure、阿里云等云平台成熟。容器技术(Docker)和容器编排(Kubernetes)的出现,让「独立部署」变得简单。

云之前的部署:
1. 申请物理服务器(2 周)
2. 安装操作系统(1 天)
3. 配置中间件(1 天)
4. 部署应用(1 天)
总耗时:约 2 周

云 + 容器之后的部署:
1. 编写 Dockerfile(1 小时)
2. push 镜像(10 分钟)
3. kubectl apply(1 分钟)
总耗时:约 1 小时

部署成本从「天」变成「分钟」,让独立部署每个服务变得可行。

敏捷开发的兴起

DevOps 运动让「独立团队负责独立服务」成为可能。每个团队可以自主决定技术栈、发布节奏,不需要跨团队协调。

微服务的核心特征

独立部署

每个微服务是独立的部署单元,可以单独构建、单独测试、单独发布。一个服务的变更不需要影响其他服务。

单体部署:
┌──────────────────────────────────────────────┐
│                   单体应用                     │
│  [用户模块] [商品模块] [订单模块] [支付模块]      │
│                                              │
│  一次构建 → 一次测试 → 一次发布               │
│  任何一个模块改动了,都要发布整个应用            │
└──────────────────────────────────────────────┘

微服务部署:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 用户服务  │ │ 商品服务  │ │ 订单服务  │ │ 支付服务  │
│  独立构建 │  独立构建 │  独立构建 │  独立构建 │
│  独立测试 │  独立测试 │  独立测试 │  独立测试 │
│  独立发布 │  独立发布 │  独立发布 │  独立发布 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘

独立数据库

每个微服务管理自己的数据库,服务之间不共享数据存储。这被称为「Database per Service」模式。

// 用户服务:MySQL
@Service
public class UserService {
    @Autowired private UserRepository userRepository; // MySQL
}

// 订单服务:PostgreSQL
@Service
public class OrderService {
    @Autowired private OrderRepository orderRepository; // PostgreSQL
}

// 商品服务:MongoDB
@Service
public class ProductService {
    @Autowired private ProductDocumentRepository productRepository; // MongoDB
}

这样做的好处是:每个服务可以选择最适合自己的数据库,数据模型可以独立演进。代价是跨服务的数据一致性需要额外的机制来保证。

API 通信

微服务之间通过轻量级协议通信,REST 和 gRPC 是最常见的选择。

// REST 调用示例
@Service
public class OrderService {

    @Autowired
    private RestTemplate restTemplate;

    public OrderDetail getOrderWithUser(Long orderId) {
        // 从用户服务获取用户信息
        // 走 HTTP REST API,而非进程内调用
        User user = restTemplate.getForObject(
            "http://user-service/api/users/{userId}",
            User.class,
            order.getUserId()
        );

        return new OrderDetail(order, user);
    }
}

// gRPC 调用示例(更高性能)
@Service
public class OrderService {
    @Autowired private UserServiceGrpcClient userClient;

    public OrderDetail getOrderWithUser(Long orderId) {
        // gRPC 基于 HTTP/2,支持双向流,性能比 REST 更好
        User user = userClient.getUser(order.getUserId());
        return new OrderDetail(order, user);
    }
}

去中心化数据管理

微服务强调「每个服务管理自己的数据」,不共享数据库。服务之间通过 API 传递数据,而不是直接查询对方的数据库。

错误做法(微服务中不应该出现):
┌─────────────┐      ┌─────────────┐
│  用户服务    │      │  订单服务    │
│   MySQL     │←────→│ PostgreSQL  │
└──────┬──────┘      └─────────────┘

直接查询对方数据库(耦合)

正确做法:
┌─────────────┐      ┌─────────────┐
│  用户服务    │      │  订单服务    │
│   MySQL     │      │ PostgreSQL  │
└─────────────┘      └──────┬──────┘
       ↑                     ↓
       │     API 调用        │
       └────────────────────┘

康威定律

Mel Conway 在 1967 年提出:「设计系统的组织,其产生的设计等价于组织内部的沟通结构。」这就是著名的康威定律(Conway's Law)。

┌─────────────────────────────────────────────────────────┐
│                  组织结构决定系统结构                    │
│                                                          │
│  组织:                                                   │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐                  │
│  │ 用户团队 │  │ 商品团队 │  │ 订单团队 │                  │
│  └────┬────┘  └────┬────┘  └────┬────┘                  │
│       │            │            │                        │
│       ↓            ↓            ↓                        │
│  系统:                                                   │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐                  │
│  │ 用户服务 │  │ 商品服务 │  │ 订单服务 │                  │
│  └─────────┘  └─────────┘  └─────────┘                  │
│                                                          │
│  如果用户团队和商品团队经常开会讨论问题,                   │
│  说明它们应该合并成一个团队,对应的服务也应该合并           │
└─────────────────────────────────────────────────────────┘

微服务架构强调「团队自治」:每个微服务由一个团队独立负责,团队可以自主选择技术栈、决定发布节奏。这就是康威定律的应用:如果要拆分微服务,先调整组织结构

微服务的优势与代价

优势

独立扩展:每个服务可以独立扩容,不需要复制整个应用。

扩展场景:商品服务是瓶颈

单体架构:
需要复制整个应用,包括不需要扩容的用户、订单模块
成本:4x 资源

微服务架构:
只需要扩容商品服务
成本:1.5x 资源

独立部署:服务之间解耦,可以独立发布。某个服务的变更不需要全量测试,不需要协调其他团队。

技术多样性:不同服务可以用不同语言、不同框架、不同数据库。机器学习服务可以用 Python,实时通讯服务可以用 Go,高并发服务可以用 Rust。

故障隔离:一个服务的问题不会级联扩散到其他服务。整个系统的稳定性更高。

代价

运维复杂度:每个服务需要独立的监控、告警、部署、回滚。10 个服务意味着 10 倍的运维工作。

分布式系统复杂性:服务间通信、网络延迟、超时处理、熔断降级、分布式事务——这些都是单体架构中不存在的问题。

数据一致性:跨服务的数据一致性是微服务最复杂的部分。本地事务变成分布式事务,需要引入 Saga、TCC 等补偿机制。

测试复杂性:端到端测试需要启动多个服务,测试环境的搭建和维护成本高。

微服务 vs 单体的复杂度对比:

| 维度 | 单体 | 微服务 |
| --- | --- | --- |
| 部署 | 1 个应用 | 10~100 个服务 |
| 监控 | 1 套指标 | 10~100 套指标 |
| 日志 | 1 个日志文件 | 10~100 个日志聚合 |
| 数据库 | 1 个实例 | 10~100 个数据库 |
| 事务 | 本地事务 | 分布式事务 |
| 测试 | 单元测试 → 集成测试 | 单元 → 集成 → 端到端 |
| 故障排查 | 单进程调试 | 全链路追踪 |

微服务适合的场景

适合的场景

  • 大规模互联网应用:用户量超过 100 万,日活超过 10 万
  • 快速迭代需求:需要频繁发布,每天甚至每小时一次
  • 多团队协作:团队规模超过 20 人,需要并行开发
  • 高并发需求:需要独立扩容特定模块
  • 技术多样性需求:不同业务需要不同技术栈

不适合的场景

  • 小��队(5 人以下):运维成本超过收益
  • 业务边界不清晰:拆分错了边界,后期合并代价更大
  • 资金和人力有限:基础设施投入太高
  • 简单 CRUD 应用:业务逻辑简单,单体完全够用
判断标准

什么时候应该考虑微服务?

问题答案建议
团队规模> 20 人可以考虑微服务
团队规模<= 10 人优先单体或模块化
代码量> 50 万行可以考虑微服务
代码量<= 10 万行优先单体
发布频率需求每天多次微服务优势明显
发布频率需求每月一次单体完全够用
业务复杂度高(多领域)微服务边界划分更容易
业务复杂度低(简单流程)单体更简单

微服务 12 要素(The Twelve-Factor App)

Heroku 在 2011 年提出了 12 要素方法论,成为微服务开发的最佳实践指南。

核心要素

要素说明示例
基准代码一份代码库,多个部署Git 仓库,每个环境一个分支
依赖显式声明和隔离依赖pom.xmlrequirements.txt
配置环境变量存储配置DATABASE_URLAPI_KEY
后端服务把后端服务当作附件资源数据库、消息队列、缓存
构建/发布/运行严格分离构建和运行阶段CI/CD pipeline
进程以无状态进程的方式运行不在内存中存储状态
端口绑定服务通过端口绑定导出Docker 容器暴露端口
并发通过进程模型扩展多实例并行
快速启动/优雅停止进程可快速启动和优雅停止K8s rolling update
开发/生产环境一致保持开发/生产环境相同使用 Docker 保持一致
日志把日志当作事件流输出到 stdout,收集到 ELK
管理进程把后台管理任务当作一次性进程数据库迁移、修复脚本

为什么要素很重要

12 要素方法论解决的核心问题是:「如何让应用在云环境中稳定运行」。它的每一条规定都是经验总结:

  • 无状态:云环境中实例随时可能被销毁重建,状态必须存储到外部
  • 端口绑定:云环境中没有固定的服务器端口,服务必须暴露端口
  • 日志流:云环境中日志分散在多个实例,需要集中收集
违反 12 要素的常见问题:

# 问题 1:配置写在代码里
public class Config {
    private static final String DB_URL = "jdbc:mysql://localhost:3306";
    // 问题:换环境需要改代码

# 问题 2:使用本地文件系统存储
File file = new File("/data/uploads/" + filename);
    // 问题:云环境中实例没有持久化存储

# 问题 3:使用本地内存缓存
Map<String, Object> cache = new HashMap<>();
    // 问题:重启后缓存丢失,多实例之间缓存不同步

配套基础设施

微服务需要配套的基础设施,否则运维成本会高到无法承受。

服务发现

服务实例的 IP 和端口是动态的(容器重启后可能变化),需要服务发现机制来定位服务。

# Kubernetes 服务发现示例
apiVersion: v1
kind: Service
metadata:
  name: user-service
spec:
  selector:
    app: user-service
  ports:
    - port: 8080
      targetPort: 8080
# 服务发现:通过服务名访问
# user-service:8080 → 自动解析到对应的 Pod IP

配置中心

每个服务的配置需要统一管理,修改配置不需要重新部署服务。

# Apollo 配置中心示例
app:
  id: user-service
  config:
    # 数据库配置
    - key: datasource.url
      value: ${DATABASE_URL}
    - key: datasource.pool-size
      value: 20
    # Redis 配置
    - key: redis.timeout
      value: 3000

链路追踪

分布式系统中,一次请求可能跨越多个服务,需要链路追踪来定位问题。

// OpenTelemetry 链路追踪示例
@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{userId}")
    public User getUser(@PathVariable Long userId) {
        // 自动生成 traceId 和 spanId
        // 所有日志和 HTTP 请求会自动关联
        return userService.getUser(userId);
    }
}

日志聚合

服务日志分散在多个实例,需要集中收集和分析。

# Filebeat + Elasticsearch 日志收集
filebeat.inputs:
  - type: container
    paths:
      - /var/log/containers/*.log
    processors:
      - add_kubernetes_metadata:

output.elasticsearch:
  hosts: ["elasticsearch:9200"]

CI/CD

每个服务需要独立的构建和部署流水线。

# GitHub Actions CI/CD 示例
name: Deploy User Service

on:
  push:
    branches: [main]
    paths: [user-service/**]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Build Docker Image
        run: docker build -t user-service:${{ github.sha }} ./user-service
      - name: Push to Registry
        run: docker push registry.example.com/user-service:${{ github.sha }}
      - name: Deploy to K8s
        run: |
          kubectl set image deployment/user-service \
            user-service=registry.example.com/user-service:${{ github.sha }}

总结

微服务是互联网时代的产物,它继承了 SOA 的服务化理念,吸收了云计算的部署能力,结合了敏捷开发的团队自治思想。

微服务的核心价值:

  • 独立部署:让服务可以快速迭代、独立发布
  • 独立扩展:让每个模块可以按需扩容
  • 团队自治:让团队可以自主决策、自主负责

但微服务也有明确的代价:

  • 运维复杂度:需要完善的基础设施支撑
  • 分布式复杂性:网络、事务、一致性都有额外成本
  • 学习曲线:团队需要掌握 DevOps、容器、K8s 等技能

微服务不是银弹,它是特定场景下的最优解。如果你的团队规模小、业务简单、资金有限,单体架构可能是更好的选择。下一节,我们来看微服务治理的进阶形态:服务网格。

思考题

问题 1:某团队有 15 个人,正在开发一个电商系统,代码量 30 万行。他们应该立即拆成微服务吗?

参考答案

不一定是立即拆。这支团队的规模处于「单体有压力、微服务有收益」的边界线上。建议的路径是:

  1. 先做模块化:在代码层面做清晰的模块划分(用户模块、商品模块、订单模块),明确模块间的接口。虽然物理上还是单体,但逻辑上已经解耦。

  2. 建立基础设施:微服务的前提是 DevOps 能力。先把 CI/CD、监控、日志、服务发现建立好,这些能力不是微服务的结果,而是微服务的前提。

  3. 试点拆分:选择一个风险低、依赖少的模块(如用户模块)做试点,验证微服务拆分的能力和流程。

  4. 渐进式拆分:根据试点经验,逐步拆分其他模块。6 个月到 1 年完成拆分是合理的节奏。

问题 2:微服务中,订单服务创建订单时需要查询用户信息。用户服务变慢导致订单创建超时,应该怎么处理?

参考答案

这是一个典型的级联失败问题,有几种处理方式:

  1. 熔断降级:当用户服务连续失败超过阈值时,打开熔断器,直接返回降级结果(如「获取用户信息失败,但订单已创建」)。Resilience4j、Sentinel 等工具支持熔断器。

  2. 超时设置:为用户服务调用设置合理的超时时间(如 500ms),超时不等待,直接继续处理。

  3. 异步化:订单创建后,异步获取用户信息并补充到订单详情中。用户在查看订单详情时可能稍晚看到用户名,但订单创建本身不受影响。

  4. 本地缓存:把用户信息缓存到订单服务,减少对用户服务的依赖。缓存策略需要考虑数据一致性。

问题 3:微服务 12 要素中,「进程无状态」和「后端服务作为附件资源」是什么关系?为什么这样设计?

参考答案

这两个要素是相互关联的,共同解决云环境中的两个问题:

「后端服务作为附件资源」的核心是:数据库、消息队列、缓存等后台服务不应该「内置」在应用中,而应该通过配置注入。环境变化时,只需要改配置,不需要改代码。

「进程无状态」的核心是:应用不依赖本地内存或本地文件系统存储状态。云环境中实例随时可能被销毁重建,如果状态存在内存里,重启就丢失了。

它们的关系是:无状态意味着状态必须存到外部(后端服务)。如果把状态存到 Redis 而不是本地内存,那么容器重启后状态不丢失;如果把状态存到本地文件,那么容器销毁后状态丢失。

这样做的好处是:应用可以随时水平扩展,任意实例都可以处理任意请求,因为它们共享同一个后端状态存储。