微服务测试与可观测性
问题
微服务架构下如何做测试?如何实现全链路的可观测性?
答案
微服务测试金字塔
| 测试类型 | 说明 | 工具 |
|---|---|---|
| 单元测试 | 测试单个类/方法 | JUnit 5、Mockito |
| 集成测试 | 测试服务与 DB/Redis 等的集成 | Testcontainers、@SpringBootTest |
| 契约测试 | 验证服务间 API 契约一致性 | Spring Cloud Contract、Pact |
| E2E 测试 | 端到端全链路测试 | Selenium、Playwright |
契约测试
服务 A 调用服务 B 的 API,契约测试确保 B 的 API 变更不会破坏 A。
Spring Cloud Contract DSL
Contract.make {
description("扣减库存成功")
request {
method POST()
url "/api/inventory/deduct"
headers { contentType(applicationJson()) }
body([productId: "P001", quantity: 1])
}
response {
status 200
body([success: true, remainStock: 99])
}
}
集成测试(Testcontainers)
使用 Testcontainers 启动真实数据库
@SpringBootTest
@Testcontainers
class OrderServiceIntegrationTest {
@Container
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
.withDatabaseName("order_db");
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", mysql::getJdbcUrl);
registry.add("spring.datasource.username", mysql::getUsername);
registry.add("spring.datasource.password", mysql::getPassword);
registry.add("spring.redis.host", redis::getHost);
registry.add("spring.redis.port", () -> redis.getMappedPort(6379));
}
@Autowired
private OrderService orderService;
@Test
void shouldCreateOrder() {
OrderDTO dto = new OrderDTO("user1", "product1", 2);
Order order = orderService.create(dto);
assertThat(order.getId()).isNotNull();
assertThat(order.getStatus()).isEqualTo("CREATED");
}
}
可观测性三大支柱
| 支柱 | 说明 | 工具 |
|---|---|---|
| 日志(Logs) | 离散事件记录 | ELK、Loki |
| 指标(Metrics) | 聚合数值(QPS、延迟、错误率) | Prometheus + Grafana |
| 链路追踪(Traces) | 请求在多个服务间的调用链路 | SkyWalking、Zipkin |
链路追踪
每个请求带一个全局 traceId,每次服务调用产生一个 spanId。通过链路追踪可以看到完整的调用链和每个节点的耗时。
日志中自动携带 TraceId
// Micrometer Tracing(Spring Boot 3.x)自动注入
// 日志输出:2024-01-15 10:30:00 [traceId=abc123, spanId=def456] INFO OrderService - 创建订单
// logback-spring.xml 配置
// <pattern>%d [traceId=%X{traceId}, spanId=%X{spanId}] %-5level %logger - %msg%n</pattern>
监控告警
| 指标 | 含义 | 告警阈值(参考) |
|---|---|---|
| QPS | 每秒请求数 | 超过容量的 80% |
| P99 延迟 | 99% 请求在此时间内完成 | > 3 秒 |
| 错误率 | 5xx 响应占比 | > 1% |
| CPU 使用率 | 容器 CPU 使用 | > 80% |
| 内存使用率 | 容器内存使用 | > 85% |
| GC 频率 | Full GC 次数 | > 1 次/分钟 |
常见面试问题
Q1: 微服务架构下如何排查线上问题?
答案:
- 看日志:通过 traceId 在 ELK/Loki 中搜索完整调用链路的日志
- 看链路:在 SkyWalking 中查看调用拓扑和各节点耗时
- 看指标:在 Grafana 中查看 QPS、延迟、错误率等指标变化
- 看告警:根据告警信息快速定位哪个服务出问题
排查流程:告警触发 → Grafana 看指标 → SkyWalking 看链路 → ELK 看日志 → 定位根因。
Q2: 如何保证微服务上线质量?
答案:
- 单元测试:覆盖核心业务逻辑(> 80% 覆盖率)
- 集成测试:Testcontainers 验证 DB/Redis 等集成
- 契约测试:保证服务间 API 兼容
- 灰度发布:先小比例流量验证
- 监控告警:发布后重点关注错误率和延迟
Q3: traceId 如何在服务间传递?
答案:
- 第一个服务(通常是网关)生成 traceId
- 通过 HTTP Header(如
X-B3-TraceId)在服务间传递 - 每个服务的日志框架(如 Logback MDC)自动记录 traceId
- 异步场景(MQ)通过消息属性传递 traceId
Spring Boot 3.x 使用 Micrometer Tracing 自动完成这些工作。