为了改进我们服务的故障排除,我们实施了一个机制,即“请求ID"”,其中为每个请求分配一个唯一的ID,然后当服务在为特定请求提供服务时调用另一个ID时,它会发送请求ID作为HTTP标头,因此如果在被叫服务中发生错误,我们可以很容易地找出原始请求是什么。为了实现此功能,我们创建了两个过滤器:
ContainerRequestFilter
,它从请求标头中读取ID并将其设置在ContainerRequestContext
ClientRequestFilter
,将请求ID添加到Client
为此,我们在每个请求上注册ClientRequestFilter
,如下所示:
public class MyServiceClient {
private WebTarget target;
@Inject
public MyServiceClient(Client client, @Context ContainerRequestContext ctx) {
// This is called on every request, with a new context
this.target = client
.target("/target/endpoint")
.register(new RequestIdFilter(ctx)); // custom ClientRequestFilter
}
public void doSomething() {
this.target.path("resource")
.request()
.get();
}
}
这在大多数情况下都可以正常工作,但我们遇到了零星错误,这些错误似乎是由线程安全问题引起的。会发生的情况是,如果两个线程同时调用MyServiceClient::doSomething
,WebTarget
实例最终会调用ClientConfig::initRuntime
,它会在某个时刻收集所有已配置的Binder
并调用{{} 1}}在他们身上。现在,Binder::bind
显然不是线程安全的,因此有时请求会失败:
AbstractBinder::bind
根据Is java Jersey 2.1 client thread safe?,此问题可能是由其他人(可能是JacksonBinder,来自Dropwizard)注册的非线程安全java.lang.IllegalArgumentException: Recursive configuration call detected.
at org.glassfish.hk2.utilities.binding.AbstractBinder.bind(AbstractBinder.java:179)
at org.glassfish.jersey.model.internal.CommonConfig.configureBinders(CommonConfig.java:676)
at org.glassfish.jersey.model.internal.CommonConfig.configureMetaProviders(CommonConfig.java:641)
at org.glassfish.jersey.client.ClientConfig$State.configureMetaProviders(ClientConfig.java:372)
at org.glassfish.jersey.client.ClientConfig$State.initRuntime(ClientConfig.java:405)
at org.glassfish.jersey.client.ClientConfig$State.access$000(ClientConfig.java:90)
at org.glassfish.jersey.client.ClientConfig$State$3.get(ClientConfig.java:122)
at org.glassfish.jersey.client.ClientConfig$State$3.get(ClientConfig.java:119)
at org.glassfish.jersey.internal.util.collection.Values$LazyValueImpl.get(Values.java:340)
at org.glassfish.jersey.client.ClientConfig.getRuntime(ClientConfig.java:733)
at org.glassfish.jersey.client.ClientRequest.getConfiguration(ClientRequest.java:286)
at org.glassfish.jersey.client.JerseyInvocation.validateHttpMethodAndEntity(JerseyInvocation.java:135)
at org.glassfish.jersey.client.JerseyInvocation.<init>(JerseyInvocation.java:105)
at org.glassfish.jersey.client.JerseyInvocation.<init>(JerseyInvocation.java:101)
at org.glassfish.jersey.client.JerseyInvocation.<init>(JerseyInvocation.java:92)
at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:420)
at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:316)
引起的。你觉得怎么样?
如果方法有误,您是否知道实施此方法的更好方法&#34;请求ID&#34;功能?
谢谢!
编辑1:以下是Binder
RequestIdFilter
编辑2:在public class RequestIdFilter implements ClientRequestFilter {
private ContainerRequestContext context;
public RequestIdFilter(ContainerRequestContext context) {
this.context = context;
}
@Override
public void filter(ClientRequestContext clientCtx) throws IOException {
String requestId = (String) context.getProperty(X_REQUEST_ID); // This is set by a ContainerRequestFilter
clientCtx.getHeaders().putSingle(X_REQUEST_ID, requestId);
}
}
构造函数中创建WebTarget
时,通过将请求ID作为查询参数传递(暂时)修复了该问题。这似乎是线程安全的,并且避免在每个请求上注册新的过滤器。