Skip to content
第 218 / 250 章架构⏱ 12 分钟阅读

第 218 章:微服务设计原则

学习目标

  • 掌握服务拆分方法
  • 学会接口设计约定
  • 了解服务间依赖管理
  • 掌握容错设计模式

一、Martin Fowler 微服务特征

1.1 9 大特征

  1. Componentization via Services
  2. Organized around Business Capability(组织围绕业务能力)
  3. Products not Projects(产品而非项目)
  4. Smart Endpoints and Dumb Pipes(智能端点 + 哑管道)
  5. Decentralized Governance(去中心化治理)
  6. Decentralized Data Management(去中心化数据)
  7. Infrastructure Automation(基础设施自动化)
  8. Design for Failure(故障容错设计)
  9. Evolutionary Design(演进式设计)

二、服务拆分策略

2.1 业务能力法(Business Capability)

每个服务对应一个业务能力。

电商业务能力
├── 客户管理(注册/资料/积分)
├── 商品管理(SPU/SKU/类目)
├── 订单管理(下单/取消/退款)
├── 支付管理(交易/退费)
├── 库存管理(进出库/锁定)
├── 物流管理(配送/签收)
└── 营销管理(优惠券/活动)

2.2 演进式拆分

不要从大单体一步拆为微服务 → 走绞杀者模式

策略:

  1. 大单体 + 集成层(数据同步 + 接口代理)
  2. 新功能作为独立服务
  3. 老模块逐步重写为服务
  4. 完全切量后下掉旧单体

2.3 反例

❌ 按技术层拆分:

├── controller-service
├── service-service
└── dao-service

✅ 按业务能力拆分

2.4 服务数量

服务数阶段
1单体
2-10微服务初期
10-50成熟微服务
50+平台化(需中台)

服务越多,运维复杂度指数级上升。

三、接口设计

3.1 RESTful 规范

GET    /users                  # 查询列表
GET    /users/{id}             # 查询单个
POST   /users                  # 新建
PUT    /users/{id}             # 全量更新
PATCH  /users/{id}             # 部分更新
DELETE /users/{id}             # 删除

子资源:

GET    /users/{id}/orders      # 查询用户的订单
POST   /users/{id}/orders      # 用户下单

3.2 版本管理

URL 路径版本:

/api/v1/users
/api/v2/users

Header 版本:

Accept: application/vnd.myapp.v2+json

语义化版本: v1, v2.major.minor

3.3 兼容性原则

变更兼容性
新增字段✅ 兼容
新增端点✅ 兼容
字段语义变化❌ 不兼容(部署 v2)
删除字段❌ 不兼容
修改字段名❌ 不兼容

3.4 接口幂等

java
// 通过幂等键保证不重复
@PostMapping("/orders")
public Result<Order> create(
    @RequestHeader("Idempotency-Key") String idempotencyKey,
    @RequestBody OrderRequest req
) {
    // 同一 Key 多次调用返回同一结果
    return idempotentService.execute(idempotencyKey, () -> orderService.create(req));
}

四、数据隔离

4.1 每个服务独享数据库

❌ 多服务共享数据库
├── order-db (user / order / payment 表)
└── 多个服务直接访问同一库

✅ 服务与库一一对应
├── order-service → order-db
├── user-service → user-db
└── payment-service → payment-db

4.2 跨服务数据访问

方式一:通过 API

java
// 订单服务需要用户信息 → 调用用户服务
@FeignClient("user-service")
public interface UserClient {
    @GetMapping("/users/{id}")
    UserDTO getUser(@PathVariable Long id);
}

方式二:数据同步(本地副本)

通过 MQ 异步同步:

java
// 用户服务发布用户变更事件
@EventListener
public void on(UserUpdatedEvent event) {
    kafkaTemplate.send("user-events", event);
}

// 订单服务订阅并存储本地副本
@KafkaListener(topics = "user-events")
public void handle(UserUpdatedEvent event) {
    userCacheRepository.save(event.userId(), event.name(), event.phone());
}

4.3 分布式事务

策略:

  • 可补偿操作:Saga
  • 最终一致:消息队列
  • 强一致场景:TCC(侵入性强)

五、服务依赖治理

5.1 循环依赖

❌ 循环依赖 = 死锁风险

解决:

  1. 提取共同依赖到 C
  2. 事件驱动(单向)
  3. 接口下沉

5.2 依赖分析

工具:ArchUnit,Structure101

5.3 防腐层(ACL)

外部系统变更不能影响本服务。

java
// 防腐层:隔离老系统
@Component
public class LegacyOrderAdapter implements OrderPort {

    @Autowired
    private LegacySoapClient legacyClient;  // 老 SOAP 服务

    @Override
    public OrderDTO queryById(String orderId) {
        LegacyOrder legacy = legacyClient.queryOrder(orderId);
        // 转换为内部模型
        return new OrderDTO(
            legacy.getOrderNo(),
            legacy.getTotalAmount(),
            OrderStatus.fromCode(legacy.getStatus())
        );
    }
}

六、容错设计

6.1 Design for Failure

每个服务必须考虑下游不可用

java
@Service
public class OrderService {

    @CircuitBreaker(name = "inventory", fallbackMethod = "fallback")
    @Retry(maxAttempts = 3)
    @TimeLimiter(name = "inventory")
    public CompletableFuture<Boolean> checkStock(String skuId) {
        return CompletableFuture.supplyAsync(() ->
            inventoryClient.checkStock(skuId)
        );
    }

    public CompletableFuture<Boolean> fallback(String skuId, Throwable t) {
        log.warn("库存查询失败,默认有库存", t);
        return CompletableFuture.completedFuture(true);
    }
}

6.2 隔离策略

策略说明
线程池隔离不同服务独立线程池
信号量隔离共享线程,仅限信号量
进程隔离不同服务独立进程
舱壁模式借鉴船舶水密隔舱

6.3 限流保护

java
// Sentinel 限流
@SentinelResource(value = "createOrder", blockHandler = "rateLimitHandler")
public Order createOrder(OrderRequest req) {
    return orderService.create(req);
}

public Order rateLimitHandler(OrderRequest req, BlockException ex) {
    throw new BizException("系统繁忙,请稍后再试");
}

6.4 超时控制

yaml
# OpenFeign 超时
feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 10000

# Resilience4j 超时
resilience4j:
  timelimiter:
    configs:
      default:
        timeoutDuration: 3s

原则: 总是设置超时,默认 3 秒

6.5 重试

维度建议
重试次数2-3 次
重试间隔指数退避 1s, 2s, 4s
重试条件仅对幂等 + 临时故障 重试
终止条件超过最大次数
java
@Retry(name = "inventory")
public InventoryResponse query(String skuId) {
    return inventoryClient.query(skuId);
}

七、安全

7.1 网关统一鉴权

java
@Component
public class AuthFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest req = exchange.getRequest();
        String token = req.getHeaders().getFirst("Authorization");

        if (!authService.validate(token)) {
            return Mono.error(new UnauthorizedException());
        }

        return chain.filter(exchange);
    }
}

7.2 服务间传递用户上下文

java
// Feign 拦截器
@Component
public class UserContextFeignInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        String userId = UserContextHolder.getUserId();
        template.header("X-User-Id", userId);
    }
}

接收方:

java
public class OrderController {

    @GetMapping
    public List<Order> list(@RequestHeader("X-User-Id") String userId) {
        return orderService.listByUser(userId);
    }
}

7.3 零信任架构

  • 不信任内网
  • 每个调用都要鉴权
  • 最小权限
  • 加密传输(mTLS)

八、可观测性

8.1 三大支柱

8.2 健康检查

java
@RestController
public class HealthController {

    @GetMapping("/actuator/health")
    public ResponseEntity<Health> health() {
        return ResponseEntity.ok(
            Health.up()
                .withDetail("db", checkDb())
                .withDetail("redis", checkRedis())
                .build()
        );
    }
}

K8s liveness / readiness:

yaml
livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5

8.3 集中式日志

应用 A logs → Filebeat → Elasticsearch
应用 B logs → Filebeat → Elasticsearch
应用 C logs → Filebeat → Elasticsearch

                          Kibana

每条日志必含: traceId, userId, level, service

8.4 统一监控

yaml
# Prometheus 拉所有服务的 /actuator/prometheus
scrape_configs:
  - job_name: 'orders'
    static_configs:
      - targets: ['order-service:8080']

九、契约测试

9.1 消费者驱动(CDC)

消费方定义契约,提供方实现契约。

java
// 消费方定义期望
@SpringBootTest
@AutoConfigureStubRunner(
    ids = "com.example:inventory-service:1.0.0:stubs"
)
public class OrderContractTest {

    @Autowired
    private InventoryClient inventoryClient;

    @Test
    public void shouldReturnTrueWhenStockSufficient() {
        // 调用 stub 的 inventoryClient
        boolean available = inventoryClient.checkStock("SKU-001");
        assertThat(available).isTrue();
    }
}

9.2 Pact / Spring Cloud Contract

工具自动验证双方契约一致。

十、本章小结

主题要点
拆分按业务能力,渐进式
接口RESTful + 版本 + 幂等
数据每服务一库
容错超时 + 重试 + 熔断 + 限流
安全网关 + mTLS + 用户透传
观测指标 + 日志 + 追踪
测试单元 + 集成 + 契约 + E2E

动手练习

  1. 为订单服务定义完整 RESTful 接口(包含版本控制)
  2. 给订单 + 库存服务之间加 OpenFeign + Sentinel + Resilience4j
  3. 实现一个统一的鉴权网关过滤器
  4. 用 Spring Cloud Contract 写一个消费者驱动的契约测试

推荐阅读


下一章:第 219 章:API 设计规范

本站基于 VitePress 构建 · 由 Codebook 团队维护