第 218 章:微服务设计原则
学习目标
- 掌握服务拆分方法
- 学会接口设计约定
- 了解服务间依赖管理
- 掌握容错设计模式
一、Martin Fowler 微服务特征
1.1 9 大特征
- Componentization via Services
- Organized around Business Capability(组织围绕业务能力)
- Products not Projects(产品而非项目)
- Smart Endpoints and Dumb Pipes(智能端点 + 哑管道)
- Decentralized Governance(去中心化治理)
- Decentralized Data Management(去中心化数据)
- Infrastructure Automation(基础设施自动化)
- Design for Failure(故障容错设计)
- Evolutionary Design(演进式设计)
二、服务拆分策略
2.1 业务能力法(Business Capability)
每个服务对应一个业务能力。
电商业务能力
├── 客户管理(注册/资料/积分)
├── 商品管理(SPU/SKU/类目)
├── 订单管理(下单/取消/退款)
├── 支付管理(交易/退费)
├── 库存管理(进出库/锁定)
├── 物流管理(配送/签收)
└── 营销管理(优惠券/活动)2.2 演进式拆分
不要从大单体一步拆为微服务 → 走绞杀者模式。
策略:
- 大单体 + 集成层(数据同步 + 接口代理)
- 新功能作为独立服务
- 老模块逐步重写为服务
- 完全切量后下掉旧单体
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/usersHeader 版本:
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-db4.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 循环依赖
❌ 循环依赖 = 死锁风险
解决:
- 提取共同依赖到 C
- 事件驱动(单向)
- 接口下沉
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: 58.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 |
动手练习
- 为订单服务定义完整 RESTful 接口(包含版本控制)
- 给订单 + 库存服务之间加 OpenFeign + Sentinel + Resilience4j
- 实现一个统一的鉴权网关过滤器
- 用 Spring Cloud Contract 写一个消费者驱动的契约测试
推荐阅读
下一章:第 219 章:API 设计规范