微服务全链路源码追踪
前面各篇分别拆解了 Gateway、Nacos、OpenFeign、Sentinel、Seata 的源码,这一篇把它们放回一条真实的业务请求里,以"下单"为场景做代码级追踪:请求如何穿过网关、找到实例、发出 RPC、过限流、开全局事务,最终落库,再把响应一层层传回去。
场景与链路总览
业务场景
下单场景:
客户端 POST /api/orders { productId, quantity }
├─ order-service:创建订单(核心服务)
├─ product-service:扣减库存(RPC 调用)
├─ inventory 数据表:库存扣减落库
└─ 全局事务:订单与库存要么都成功,要么都回滚完整链路
一次下单请求的旅程:
客户端
│ 1. HTTP POST
▼
Gateway(8080)
│ 2. 路由匹配 RoutePredicateHandlerMapping
│ 3. 鉴权过滤器 AuthGlobalFilter
│ 4. 限流 RequestRateLimiterFilter
│ 5. LoadBalancer 从 Nacos 选实例
│ 6. NettyRoutingFilter 转发
▼
order-service(8081)
│ 7. Controller 收参校验
│ 8. Service @Transactional 本地事务
│ 9. Sentinel CtSph.entry() 限流
│ 10. Seata GlobalTransactional 全局事务(TM)
│ 11. OpenFeign 调 product-service(RM 分支事务)
│ 12. MyBatis 执行 INSERT 订单落库
▼
product-service(8082)
│ 13. Sentinel 入口限流
│ 14. Seata 分支事务注册(RM)
│ 15. DataSourceProxy 拦截 SQL → 生成 undo_log
│ 16. MyBatis UPDATE 扣减库存
▼
响应原路返回 → 客户端一、网关层源码追踪
路由匹配
请求进入 Gateway 后,DispatcherHandler 把请求交给 RoutePredicateHandlerMapping,它从 RouteLocator 拿全部路由定义,逐一用 Predicate 匹配。
// RoutePredicateHandlerMapping.getHandlerInternal()
return this.routeLocator.getRoutes()
.filter(route -> route.getPredicate().test(exchange))
.next()
.map(route -> {
// 匹配成功 → 缓存路由并返回 GatewayFilterChain 处理器
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, route);
return new FilteringWebHandler(route.getFilters());
});路由匹配链:
Path=/api/orders/** → 命中 order-service 路由
Header=X-Version=v2 → 命中灰度路由(权重路由优先)鉴权过滤器
AuthGlobalFilter 实现 GlobalFilter,在转发前统一做 Token 校验,并把用户信息透传下游。
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
Claims claims = jwtUtil.parseToken(token.substring(7)); // 校验 JWT
ServerHttpRequest mutated = exchange.getRequest().mutate()
.header("X-User-Id", claims.get("userId").toString()) // 透传
.build();
return chain.filter(exchange.mutate().request(mutated).build());
}负载均衡选实例
ReactiveLoadBalancerClientFilter 读取 GATEWAY_REQUEST_URL_ATTR,发现 lb://order-service 协议后,交给 ReactorLoadBalancer 选择实例,选中后替换为 http://192.168.1.10:8081。
// LoadBalancerClientFilter.filter()
ServiceInstance instance = loadBalancer.choose(serviceId).block();
URI uri = exchange.getRequest().getURI();
// 把 lb://order-service 替换成实际地址
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, uri);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR,
new URI(uri.getScheme(), instance.getHost(), instance.getPort(), ...));选实例的数据来源(Nacos):
NacosNamingService.getAllInstances(serviceId)
→ 心跳维护的实例列表(含权重、健康状态)
→ 轮询 / 加权 / 灰度策略选出一个实例Netty 转发
NettyRoutingFilter 用 WebClient(Netty 连接池)发起真实转发,并把响应写回上游。
NettyRoutingFilter:
WebClient 请求 → 目标服务
← 响应 body 流式回传(不落内存缓冲)二、服务端追踪:order-service
Controller 到 Service
@RestController
public class OrderController {
@PostMapping("/api/orders")
public Result<Long> create(@RequestBody @Valid OrderCreateDTO dto) {
return orderService.createOrder(dto);
}
}本地事务与全局事务
订单创建涉及多个数据源操作和跨服务调用,事务分层如下:
事务分层:
@Transactional → Spring 本地事务(数据源内)
@GlobalTransactional → Seata 全局事务(跨服务/跨数据源)
├─ 事务拦截器生成 XID
├─ XID 通过 OpenFeign 请求头透传到 product-service
└─ 全局提交/回滚由 TC 协调@Service
public class OrderService {
@GlobalTransactional(name = "createOrderTx") // Seata 全局事务(TM)
public Long createOrder(OrderCreateDTO dto) {
// 1. Sentinel 限流入口
try (Entry entry = SphU.entry("createOrder")) {
// 2. 本地事务:订单 INSERT(MyBatis)
orderMapper.insert(buildOrder(dto));
// 3. RPC:扣减库存(XID 自动透传)
productClient.deductStock(dto.getProductId(), dto.getQuantity());
return orderId;
}
}
}Sentinel 限流点
Sentinel 拦截位置:
├─ 注解 @SentinelResource("createOrder")
├─ 或手动 SphU.entry("createOrder")
└─ ProcessorSlotChain:FlowSlot 限流 → DegradeSlot 熔断 → AuthoritySlot 黑白名单// Sentinel 核心入口
Entry entry = SphU.entry("createOrder", EntryType.IN);
// 内部:CtSph.entryWithType()
// → SlotChainBuilder 构建责任链
// → FlowSlot.checkFlow() 查规则判断是否放行数据库读写
MyBatis 执行链路:
Mapper 接口 → MapperProxy 动态代理
→ SqlSessionTemplate
→ SqlSession(SqlSessionInterceptor)
→ Executor(BaseExecutor)
→ StatementHandler
→ PreparedStatement 执行 INSERT/UPDATE-- 订单落库
INSERT INTO t_order (user_id, product_id, quantity, amount, status)
VALUES (#{userId}, #{productId}, #{quantity}, #{amount}, 'CREATED');三、跨服务调用追踪:Feign 到 product-service
OpenFeign 动态代理
order-service 里的 ProductClient 是 Feign 动态代理,调用发生在 InvocationHandler.invoke()。
@FeignClient(name = "product-service")
public interface ProductClient {
@PostMapping("/api/inventory/deduct")
Result<Void> deductStock(@RequestParam Long productId,
@RequestParam Integer quantity);
}Feign 调用链:
ProductClient.deductStock()(JDK 动态代理)
→ FeignInvocationHandler.invoke()
→ MethodHandler.invoke()(解析注解生成请求模板)
→ FeignBlockingLoadBalancerClient.execute()
→ LoadBalancer 从 Nacos 选 product-service 实例
→ Client.execute()(Apache HttpClient / OkHttp)
→ HTTP POST http://product-service:8082/api/inventory/deductXID 透传
Seata 通过 Feign 的 RequestInterceptor 把全局事务 XID 写进请求头,product-service 收到后取出绑定到本地事务上下文。
// Seata Feign 拦截器(内部实现)
public void apply(RequestTemplate template) {
String xid = RootContext.getXID();
if (xid != null) {
template.header(RootContext.KEY_XID, xid); // X-ID: 192.168.1.1:8091:112233
}
}四、下游服务追踪:product-service
分支事务注册
product-service 收到请求后,Seata RM 从请求头取 XID,进入 GlobalTransactionalInterceptor,开始分支事务。
RM 侧流程:
1. 解析请求头 X-ID → 绑定 RootContext
2. 数据源被 DataSourceProxy 代理
3. 执行 UPDATE 时:生成前镜像 + 后镜像 → 写 undo_log → 注册分支
4. 返回前提交本地事务(分支事务状态上报 TC)库存扣减与 undo_log
// DataSourceProxy 拦截的 SQL 执行
public PreparedStatement prepareStatement(String sql) {
// 1. 查询生成前镜像
// SELECT * FROM t_inventory WHERE product_id = ? FOR UPDATE
// 2. 执行 UPDATE(后镜像)
// 3. 计算变更 → 写 undo_log(分支事务记录)
// 4. 向 TC 注册分支事务
}undo_log 表结构(AT 模式):
id / branch_id / xid / context / rollback_info / log_status / log_created全局提交与回滚
TC 收到所有分支状态后决策:
├─ 全部成功 → 全局提交:通知各 RM 删除 undo_log
└─ 任一失败 → 全局回滚:RM 根据 undo_log 反向补偿 SQL
└─ 库存扣减失败 → UPDATE 回滚为扣减前数据五、链路追踪与上下文传递
TraceId 贯穿
全链路上下文:
├─ Gateway:生成 TraceId → 写入请求头 X-Trace-Id
├─ Feign:请求头自动透传(拦截器)
├─ 日志:MDC 放入 traceId,日志按链路聚合
└─ 可观测:SkyWalking 以 TraceId 关联 Span用户信息透传
用户身份传递:
Gateway 解析 JWT → 写入 X-User-Id 请求头
├─ 服务间:Feign 拦截器透传 X-User-Id
├─ 服务内:ThreadLocal 存取当前用户
└─ 用途:数据权限校验(只能查自己的订单)六、全链路源码追踪表
| 链路节点 | 关键类 / 方法 | 作用 |
|---|---|---|
| 路由匹配 | RoutePredicateHandlerMapping.getHandlerInternal() | 按 Predicate 匹配路由 |
| 网关过滤链 | FilteringWebHandler.handle() | 执行 GlobalFilter 与路由过滤器 |
| 网关鉴权 | AuthGlobalFilter.filter() | JWT 校验、透传用户头 |
| 网关限流 | RequestRateLimiterFilter.filter() | 令牌桶限流 |
| 网关选实例 | ReactiveLoadBalancerClientFilter / NacosServiceInstanceSupplier | 从 Nacos 选实例 |
| 网关转发 | NettyRoutingFilter.filter() | WebClient 转发 |
| 服务端入口 | DispatcherServlet → Controller | 参数解析与业务分发 |
| 本地事务 | TransactionInterceptor.invoke() | @Transactional 提交/回滚 |
| 全局事务 | GlobalTransactionalInterceptor | Seata 全局事务(TM) |
| 限流 | CtSph.entry() / FlowSlot | Sentinel 规则校验 |
| 远程调用 | FeignInvocationHandler.invoke() | 动态代理发起 RPC |
| 负载均衡 | FeignBlockingLoadBalancerClient.execute() | Feign 侧选实例 |
| 分支事务 | DataSourceProxy / undo_log | Seata RM 分支注册与回滚 |
| 数据库读写 | MapperProxy → Executor → StatementHandler | SQL 执行与结果映射 |
| 响应返回 | 各层 filter/拦截器 reverse 阶段 | 响应逐层回传 |
七、一次失败场景的追踪
场景:库存不足,product-service 返回业务失败
├─ product-service:全局事务分支标记失败
├─ TC:收到失败分支 → 发起全局回滚
├─ order-service:undo_log 无(INSERT 回滚)→ 删除订单数据
├─ product-service:根据 undo_log 恢复库存(若已扣减)
└─ 客户端:收到统一错误码 50001(库存不足)关键日志定位(按 TraceId 搜索):
[traceId=abc123] OrderService.createOrder begin
[traceId=abc123] GlobalTx begin, xid=192.168.1.1:8091:112233
[traceId=abc123] branch register success, branchId=223344
[traceId=abc123] global rollback, reason=inventory not enough总结
一条下单请求的完整旅程是 Spring Cloud 各组件协同的缩影:Gateway 负责入口的路由、鉴权、限流与转发,LoadBalancer 借助 Nacos 完成实例选择,OpenFeign 把 RPC 调用变成接口调用,Sentinel 在链路关键点把关,Seata 用全局事务保证跨服务一致性,最终由 MyBatis 落库。追踪时抓住三条主线——请求流转(HTTP)、上下文传递(XID / TraceId / 用户头)、事务边界(本地 + 全局),就能在任何生产问题中快速定位到具体类与方法。