Spring Cloud Sleuth如何添加拼接span-id的自定义传播字段Request-Id
自定义Request-Id传播字段实现方案
这个需求本质是对分布式链路追踪的传播上下文做自定义扩展,核心需要在传播协议、入口拦截、出口透传三个核心环节做统一改造即可实现:
前置前提
- 你当前使用的链路追踪组件(OpenTelemetry、SkyWalking、Jaeger等原生或二次封装版本)已经开放自定义上下文传播的扩展能力
- 所有接入服务统一使用同一套传播协议:HTTP头、gRPC Metadata、MQ消息头等场景下使用相同的字段名约定,避免大小写、拼写差异导致的透传失败
核心实现步骤
1. 统一传播字段定义
先全局约定字段名:HTTP场景推荐使用X-Request-Id,RPC、MQ等场景对应元数据字段名用request_id,降低跨协议适配成本。
2. 入站流量拦截逻辑
在所有服务的流量入口层(网关过滤器、Web拦截器、RPC服务端拦截器)做统一处理:
- 若入站请求没有携带
request_id:判定为链路起点,直接取当前span的span-id作为本服务的request_id值 - 若入站请求已经携带
request_id:取上游透传的request_id+.+ 当前span的span-id,拼接后作为本服务的request_id值
注意:拼接前需要对上游传入的request_id做合法性校验,过滤特殊字符、限制最大长度,避免恶意构造的字段值导致服务异常
3. 出站流量透传逻辑
在所有服务的流量出口层(HTTP客户端拦截器、RPC客户端拦截器、MQ生产者拦截器),把本服务计算完成的request_id值注入到出站请求的元数据中,传递给下游服务。
4. 本地上下文存储
把计算好的request_id存入线程本地存储(ThreadLocal、协程上下文等),方便业务日志打印、问题排查时直接取用。
代码示例(Spring Boot + OpenTelemetry场景)
// 入站请求过滤器 @Component public class RequestIdFilter implements Filter { private static final String REQUEST_ID_HEADER = "X-Request-Id"; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; // 读取上游传递的Request-Id String upstreamRequestId = httpRequest.getHeader(REQUEST_ID_HEADER); // 获取当前Span的SpanId String currentSpanId = Span.current().getSpanContext().getSpanId(); // 拼接生成本地Request-Id String localRequestId = upstreamRequestId == null ? currentSpanId : upstreamRequestId + "." + currentSpanId; // 存入线程上下文供业务使用 RequestContextHolder.set("request_id", localRequestId); chain.doFilter(request, response); } } // 出站请求拦截器 @Component public class RestTemplateInterceptor implements ClientHttpRequestInterceptor { private static final String REQUEST_ID_HEADER = "X-Request-Id"; @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 从上下文取出Request-Id注入到出站请求头 String localRequestId = RequestContextHolder.get("request_id"); if (localRequestId != null) { request.getHeaders().add(REQUEST_ID_HEADER, localRequestId); } return execution.execute(request, body); } }
边界场景兼容
- 异步调用场景:需要把
request_id透传到异步线程/协程上下文中,避免上下文丢失 - 链路中断场景:如果存在未接入统一规范的中间服务,链路续接时如果没有拿到上游的
request_id,直接从当前span重新生成即可 - 性能影响:拼接逻辑非常轻量,基本不会带来额外性能开销,无需做额外优化
内容的提问来源于stack exchange,提问作者hex_fed
相关产品推荐
相关产品推荐

