Spring 中的多个请求共享一个 ID
One ID shared between many requests in Spring
我想问一些类似 conversationId
的问题,它可以在一次 API 通话中在多个 requests/responses 之间共享。例如,我们有 /test
个端点,这个端点调用另一个服务,然后另一个 return 响应基于获得的结果。它看起来像:
Conversation:
1) Sending test request.
2) API calls /info endpoint.
3) Returns /info response.
4) API calls /example endpoint.
5) Returns /example response.
6) Returns /test response.
当我们同时调用 /test
API 4-5 次时,日志可能会很乱,我们不知道如何组合各个调用。
我想用任何随机请求范围 ID 标记它,例如高值数字。它可能看起来像:
1) Sending test request of ID 12345.
2) Sending test request of ID 98765.
3) API calls /info endpoint of ID 12345.
4) Returns /info response of ID 12345.
5) API calls /info endpoint of ID 98765.
6) API calls /example endpoint of ID 12345.
7) Returns /info response of ID 98765.
and so on
是否有任何 Spring 机制以请求方式在 ClientHttpRequestInterceptor
和 GenericFilterBean
之间共享数据而不重新创建那些 bean?就像一个生命周期从 doFilter
开始并在那里结束的自定义作用域 bean。
1.使用侦探
(限制 - Spring Cloud Sleuth 是 Spring 基于引导的)
Spring sleuth 可以提供跟踪 ID 和跨度 ID。使用跟踪 ID,您可以组合单个调用。
下面的例子可能对某些人有用。
添加spring侦探
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
你的/test
@GetMapping("/test")
public String test() {
LOG.info("Sending test request.");
LOG.info("API calls /info endpoint.");
// Calling /info service or endpoint
LOG.info("Returns /info response.");
LOG.info("API calls /example endpoint.");
// Calling /example service or endpoint
LOG.info("Returns /example response.");
LOG.info("Returns /test response");
return "test";
}
同时调用/test API 5次,服务器调用日志如下所示
2022-04-28 00:40:11.108 INFO [test,be0b352e1f8664d1,be0b352e1f8664d1] 10766 --- [nio-9091-exec-5] c.k.d.d.a.AccountServiceApplication : Sending test request.
2022-04-28 00:40:11.109 INFO [test,29c2e863354fca33,29c2e863354fca33] 10766 --- [nio-9091-exec-1] c.k.d.d.a.AccountServiceApplication : Sending test request.
2022-04-28 00:40:11.115 INFO [test,29c2e863354fca33,29c2e863354fca33] 10766 --- [nio-9091-exec-1] c.k.d.d.a.AccountServiceApplication : API calls /info endpoint.
2022-04-28 00:40:11.115 INFO [test,29c2e863354fca33,29c2e863354fca33] 10766 --- [nio-9091-exec-1] c.k.d.d.a.AccountServiceApplication : Returns /info response.
2022-04-28 00:40:11.115 INFO [test,29c2e863354fca33,29c2e863354fca33] 10766 --- [nio-9091-exec-1] c.k.d.d.a.AccountServiceApplication : API calls /example endpoint.
2022-04-28 00:40:11.115 INFO [test,29c2e863354fca33,29c2e863354fca33] 10766 --- [nio-9091-exec-1] c.k.d.d.a.AccountServiceApplication : Returns /example response.
2022-04-28 00:40:11.115 INFO [test,29c2e863354fca33,29c2e863354fca33] 10766 --- [nio-9091-exec-1] c.k.d.d.a.AccountServiceApplication : Returns /test response
2022-04-28 00:40:11.115 INFO [test,be0b352e1f8664d1,be0b352e1f8664d1] 10766 --- [nio-9091-exec-5] c.k.d.d.a.AccountServiceApplication : API calls /info endpoint.
2022-04-28 00:40:11.116 INFO [test,be0b352e1f8664d1,be0b352e1f8664d1] 10766 --- [nio-9091-exec-5] c.k.d.d.a.AccountServiceApplication : Returns /info response.
2022-04-28 00:40:11.116 INFO [test,be0b352e1f8664d1,be0b352e1f8664d1] 10766 --- [nio-9091-exec-5] c.k.d.d.a.AccountServiceApplication : API calls /example endpoint.
2022-04-28 00:40:11.116 INFO [test,be0b352e1f8664d1,be0b352e1f8664d1] 10766 --- [nio-9091-exec-5] c.k.d.d.a.AccountServiceApplication : Returns /example response.
2022-04-28 00:40:11.116 INFO [test,be0b352e1f8664d1,be0b352e1f8664d1] 10766 --- [nio-9091-exec-5] c.k.d.d.a.AccountServiceApplication : Returns /test response
2022-04-28 00:40:11.109 INFO [test,b26d689f73b974b7,b26d689f73b974b7] 10766 --- [nio-9091-exec-4] c.k.d.d.a.AccountServiceApplication : Sending test request.
2022-04-28 00:40:11.116 INFO [test,b26d689f73b974b7,b26d689f73b974b7] 10766 --- [nio-9091-exec-4] c.k.d.d.a.AccountServiceApplication : API calls /info endpoint.
2022-04-28 00:40:11.116 INFO [test,b26d689f73b974b7,b26d689f73b974b7] 10766 --- [nio-9091-exec-4] c.k.d.d.a.AccountServiceApplication : Returns /info response.
2022-04-28 00:40:11.116 INFO [test,b26d689f73b974b7,b26d689f73b974b7] 10766 --- [nio-9091-exec-4] c.k.d.d.a.AccountServiceApplication : API calls /example endpoint.
2022-04-28 00:40:11.116 INFO [test,b26d689f73b974b7,b26d689f73b974b7] 10766 --- [nio-9091-exec-4] c.k.d.d.a.AccountServiceApplication : Returns /example response.
2022-04-28 00:40:11.116 INFO [test,b26d689f73b974b7,b26d689f73b974b7] 10766 --- [nio-9091-exec-4] c.k.d.d.a.AccountServiceApplication : Returns /test response
2022-04-28 00:40:11.109 INFO [test,4f28a79ecc2ce6da,4f28a79ecc2ce6da] 10766 --- [nio-9091-exec-2] c.k.d.d.a.AccountServiceApplication : Sending test request.
2022-04-28 00:40:11.109 INFO [test,ee7bfa89b05b69ad,ee7bfa89b05b69ad] 10766 --- [nio-9091-exec-3] c.k.d.d.a.AccountServiceApplication : Sending test request.
etc....
请注意,应用程序名称位于开头的括号内。这些括号由 Sleuth 添加。它们代表应用程序名称、跟踪 ID 和跨度 ID。
此外,您可以通过编程方式获取当前轨迹。
@Autowired
private Tracer tracer;
@GetMapping("/test")
public String account() {
LOG.info("Sending test request, " + "trace ID=" + tracer.currentSpan().context().traceId().toString());
LOG.info("API calls /info endpoint, " + "trace ID=" + tracer.currentSpan().context().traceId().toString());
// Calling /info service or endpoint
LOG.info("Returns /info response, " + "trace ID=" + tracer.currentSpan().context().traceId().toString());
LOG.info("API calls /example endpoint, " + "trace ID="
+ tracer.currentSpan().context().traceId().toString());
// Calling /example service or endpoint
LOG.info("Returns /example response, " + "trace ID=" + tracer.currentSpan().context().traceId().toString());
LOG.info("Returns /test response, " + "trace ID=" + tracer.currentSpan().context().traceId().toString());
return "test";
}
日志
....Sending test request, trace ID=51322ee614962951
.... API calls /info endpoint, trace ID=51322ee614962951
.... Sending test request, trace ID=4bc5017e2beceaf8
.... Sending test request, trace ID=75b638bda78a7bb5
.... Returns /info response, trace ID=51322ee614962951
.... API calls /info endpoint, trace ID=4bc5017e2beceaf8
.... API calls /example endpoint, trace ID=51322ee614962951
.... Sending test request, trace ID=f20cde67b4376777
etc....
2。您可以使用线程上下文映射
@GetMapping("/test")
public String test() {
ThreadContext.put("id", UUID.randomUUID().toString());
LOG.info("Sending test request, " + ThreadContext.get("id"));
LOG.info("API calls /info endpoint, " + ThreadContext.get("id"));
// Calling /info service or endpoint
LOG.info("Returns /info response, " + ThreadContext.get("id"));
LOG.info("API calls /example endpoint, " + ThreadContext.get("id"));
// Calling /example service or endpoint
LOG.info("Returns /example response, " + ThreadContext.get("id"));
LOG.info("Returns /test response, " + ThreadContext.get("id"));
return "test";
}
你可以使用MDC along with OncePerRequestFilter这样的东西来满足这种要求。:
@Component
public class MDCFilter implements OncePerRequestFilter{
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
MDC.put("request_ID", someNumberGeneratorUtility());
try {
filterChain.doFilter(request, response);
} finally {
MDC.remove("request_ID");
}
}
}
我已经创建了 RequestContext
bean 作为我的请求数据的持有者,例如 conversationId
:
@Component
@RequestScope
public class RequestContext {
private final String conversationId;
public RequestContext() {
conversationId = UUID.randomUUID().toString();
}
public String getConversationId() {
return conversationId;
}
}
然后,将其注入我的过滤器:
@Component
public class LoggingFilter extends GenericFilterBean {
private final RequestContext requestContext;
public RestServerLoggingFilter(RequestContext requestContext) {
this.requestContext = requestContext;
}
...
}
但最重要的是配置和顺序:
@Configuration
public class RestServerConfiguration {
private static final int LOGGING_FILTER_ORDER = -104;
@Bean
public FilterRegistrationBean<RestServerLoggingFilter> initFilter(LoggingFilter filter) {
FilterRegistrationBean<RestServerLoggingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(filter);
registrationBean.setDispatcherTypes(
EnumSet.complementOf(EnumSet.of(DispatcherType.ERROR))
);
registrationBean.setOrder(LOGGING_FILTER_ORDER);
return registrationBean;
}
}
过滤器堆叠到 ApplicationFilterChain
class 下的列表中。当我们浏览这个堆栈时,我们有一个 OrderedRequestContextFilter
bean,其顺序 -105
初始化 RequestScope
在 initContextHolders
定义它的属性。了解这一点非常重要。然后您可以将请求的范围 bean 注入任何过滤器,即链中 OrderedRequestContextFilter
之后。
我想问一些类似 conversationId
的问题,它可以在一次 API 通话中在多个 requests/responses 之间共享。例如,我们有 /test
个端点,这个端点调用另一个服务,然后另一个 return 响应基于获得的结果。它看起来像:
Conversation:
1) Sending test request.
2) API calls /info endpoint.
3) Returns /info response.
4) API calls /example endpoint.
5) Returns /example response.
6) Returns /test response.
当我们同时调用 /test
API 4-5 次时,日志可能会很乱,我们不知道如何组合各个调用。
我想用任何随机请求范围 ID 标记它,例如高值数字。它可能看起来像:
1) Sending test request of ID 12345.
2) Sending test request of ID 98765.
3) API calls /info endpoint of ID 12345.
4) Returns /info response of ID 12345.
5) API calls /info endpoint of ID 98765.
6) API calls /example endpoint of ID 12345.
7) Returns /info response of ID 98765.
and so on
是否有任何 Spring 机制以请求方式在 ClientHttpRequestInterceptor
和 GenericFilterBean
之间共享数据而不重新创建那些 bean?就像一个生命周期从 doFilter
开始并在那里结束的自定义作用域 bean。
1.使用侦探 (限制 - Spring Cloud Sleuth 是 Spring 基于引导的)
Spring sleuth 可以提供跟踪 ID 和跨度 ID。使用跟踪 ID,您可以组合单个调用。
下面的例子可能对某些人有用。
添加spring侦探
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
你的/test
@GetMapping("/test")
public String test() {
LOG.info("Sending test request.");
LOG.info("API calls /info endpoint.");
// Calling /info service or endpoint
LOG.info("Returns /info response.");
LOG.info("API calls /example endpoint.");
// Calling /example service or endpoint
LOG.info("Returns /example response.");
LOG.info("Returns /test response");
return "test";
}
同时调用/test API 5次,服务器调用日志如下所示
2022-04-28 00:40:11.108 INFO [test,be0b352e1f8664d1,be0b352e1f8664d1] 10766 --- [nio-9091-exec-5] c.k.d.d.a.AccountServiceApplication : Sending test request.
2022-04-28 00:40:11.109 INFO [test,29c2e863354fca33,29c2e863354fca33] 10766 --- [nio-9091-exec-1] c.k.d.d.a.AccountServiceApplication : Sending test request.
2022-04-28 00:40:11.115 INFO [test,29c2e863354fca33,29c2e863354fca33] 10766 --- [nio-9091-exec-1] c.k.d.d.a.AccountServiceApplication : API calls /info endpoint.
2022-04-28 00:40:11.115 INFO [test,29c2e863354fca33,29c2e863354fca33] 10766 --- [nio-9091-exec-1] c.k.d.d.a.AccountServiceApplication : Returns /info response.
2022-04-28 00:40:11.115 INFO [test,29c2e863354fca33,29c2e863354fca33] 10766 --- [nio-9091-exec-1] c.k.d.d.a.AccountServiceApplication : API calls /example endpoint.
2022-04-28 00:40:11.115 INFO [test,29c2e863354fca33,29c2e863354fca33] 10766 --- [nio-9091-exec-1] c.k.d.d.a.AccountServiceApplication : Returns /example response.
2022-04-28 00:40:11.115 INFO [test,29c2e863354fca33,29c2e863354fca33] 10766 --- [nio-9091-exec-1] c.k.d.d.a.AccountServiceApplication : Returns /test response
2022-04-28 00:40:11.115 INFO [test,be0b352e1f8664d1,be0b352e1f8664d1] 10766 --- [nio-9091-exec-5] c.k.d.d.a.AccountServiceApplication : API calls /info endpoint.
2022-04-28 00:40:11.116 INFO [test,be0b352e1f8664d1,be0b352e1f8664d1] 10766 --- [nio-9091-exec-5] c.k.d.d.a.AccountServiceApplication : Returns /info response.
2022-04-28 00:40:11.116 INFO [test,be0b352e1f8664d1,be0b352e1f8664d1] 10766 --- [nio-9091-exec-5] c.k.d.d.a.AccountServiceApplication : API calls /example endpoint.
2022-04-28 00:40:11.116 INFO [test,be0b352e1f8664d1,be0b352e1f8664d1] 10766 --- [nio-9091-exec-5] c.k.d.d.a.AccountServiceApplication : Returns /example response.
2022-04-28 00:40:11.116 INFO [test,be0b352e1f8664d1,be0b352e1f8664d1] 10766 --- [nio-9091-exec-5] c.k.d.d.a.AccountServiceApplication : Returns /test response
2022-04-28 00:40:11.109 INFO [test,b26d689f73b974b7,b26d689f73b974b7] 10766 --- [nio-9091-exec-4] c.k.d.d.a.AccountServiceApplication : Sending test request.
2022-04-28 00:40:11.116 INFO [test,b26d689f73b974b7,b26d689f73b974b7] 10766 --- [nio-9091-exec-4] c.k.d.d.a.AccountServiceApplication : API calls /info endpoint.
2022-04-28 00:40:11.116 INFO [test,b26d689f73b974b7,b26d689f73b974b7] 10766 --- [nio-9091-exec-4] c.k.d.d.a.AccountServiceApplication : Returns /info response.
2022-04-28 00:40:11.116 INFO [test,b26d689f73b974b7,b26d689f73b974b7] 10766 --- [nio-9091-exec-4] c.k.d.d.a.AccountServiceApplication : API calls /example endpoint.
2022-04-28 00:40:11.116 INFO [test,b26d689f73b974b7,b26d689f73b974b7] 10766 --- [nio-9091-exec-4] c.k.d.d.a.AccountServiceApplication : Returns /example response.
2022-04-28 00:40:11.116 INFO [test,b26d689f73b974b7,b26d689f73b974b7] 10766 --- [nio-9091-exec-4] c.k.d.d.a.AccountServiceApplication : Returns /test response
2022-04-28 00:40:11.109 INFO [test,4f28a79ecc2ce6da,4f28a79ecc2ce6da] 10766 --- [nio-9091-exec-2] c.k.d.d.a.AccountServiceApplication : Sending test request.
2022-04-28 00:40:11.109 INFO [test,ee7bfa89b05b69ad,ee7bfa89b05b69ad] 10766 --- [nio-9091-exec-3] c.k.d.d.a.AccountServiceApplication : Sending test request.
etc....
请注意,应用程序名称位于开头的括号内。这些括号由 Sleuth 添加。它们代表应用程序名称、跟踪 ID 和跨度 ID。
此外,您可以通过编程方式获取当前轨迹。
@Autowired
private Tracer tracer;
@GetMapping("/test")
public String account() {
LOG.info("Sending test request, " + "trace ID=" + tracer.currentSpan().context().traceId().toString());
LOG.info("API calls /info endpoint, " + "trace ID=" + tracer.currentSpan().context().traceId().toString());
// Calling /info service or endpoint
LOG.info("Returns /info response, " + "trace ID=" + tracer.currentSpan().context().traceId().toString());
LOG.info("API calls /example endpoint, " + "trace ID="
+ tracer.currentSpan().context().traceId().toString());
// Calling /example service or endpoint
LOG.info("Returns /example response, " + "trace ID=" + tracer.currentSpan().context().traceId().toString());
LOG.info("Returns /test response, " + "trace ID=" + tracer.currentSpan().context().traceId().toString());
return "test";
}
日志
....Sending test request, trace ID=51322ee614962951
.... API calls /info endpoint, trace ID=51322ee614962951
.... Sending test request, trace ID=4bc5017e2beceaf8
.... Sending test request, trace ID=75b638bda78a7bb5
.... Returns /info response, trace ID=51322ee614962951
.... API calls /info endpoint, trace ID=4bc5017e2beceaf8
.... API calls /example endpoint, trace ID=51322ee614962951
.... Sending test request, trace ID=f20cde67b4376777
etc....
2。您可以使用线程上下文映射
@GetMapping("/test")
public String test() {
ThreadContext.put("id", UUID.randomUUID().toString());
LOG.info("Sending test request, " + ThreadContext.get("id"));
LOG.info("API calls /info endpoint, " + ThreadContext.get("id"));
// Calling /info service or endpoint
LOG.info("Returns /info response, " + ThreadContext.get("id"));
LOG.info("API calls /example endpoint, " + ThreadContext.get("id"));
// Calling /example service or endpoint
LOG.info("Returns /example response, " + ThreadContext.get("id"));
LOG.info("Returns /test response, " + ThreadContext.get("id"));
return "test";
}
你可以使用MDC along with OncePerRequestFilter这样的东西来满足这种要求。:
@Component
public class MDCFilter implements OncePerRequestFilter{
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
MDC.put("request_ID", someNumberGeneratorUtility());
try {
filterChain.doFilter(request, response);
} finally {
MDC.remove("request_ID");
}
}
}
我已经创建了 RequestContext
bean 作为我的请求数据的持有者,例如 conversationId
:
@Component
@RequestScope
public class RequestContext {
private final String conversationId;
public RequestContext() {
conversationId = UUID.randomUUID().toString();
}
public String getConversationId() {
return conversationId;
}
}
然后,将其注入我的过滤器:
@Component
public class LoggingFilter extends GenericFilterBean {
private final RequestContext requestContext;
public RestServerLoggingFilter(RequestContext requestContext) {
this.requestContext = requestContext;
}
...
}
但最重要的是配置和顺序:
@Configuration
public class RestServerConfiguration {
private static final int LOGGING_FILTER_ORDER = -104;
@Bean
public FilterRegistrationBean<RestServerLoggingFilter> initFilter(LoggingFilter filter) {
FilterRegistrationBean<RestServerLoggingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(filter);
registrationBean.setDispatcherTypes(
EnumSet.complementOf(EnumSet.of(DispatcherType.ERROR))
);
registrationBean.setOrder(LOGGING_FILTER_ORDER);
return registrationBean;
}
}
过滤器堆叠到 ApplicationFilterChain
class 下的列表中。当我们浏览这个堆栈时,我们有一个 OrderedRequestContextFilter
bean,其顺序 -105
初始化 RequestScope
在 initContextHolders
定义它的属性。了解这一点非常重要。然后您可以将请求的范围 bean 注入任何过滤器,即链中 OrderedRequestContextFilter
之后。