【问题标题】:Spring Boot with CXF Client Race Condition/Connection Timeout具有 CXF 客户端竞争条件/连接超时的 Spring Boot
【发布时间】:2018-08-13 08:49:44
【问题描述】:

我在我的 Spring Boot 应用程序中配置了一个 CXF 客户端,如下所示:

    @Bean
    public ConsumerSupportService consumerSupportService() {
        JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
        jaxWsProxyFactoryBean.setServiceClass(ConsumerSupportService.class);
        jaxWsProxyFactoryBean.setAddress("https://www.someservice.com/service?wsdl");
        jaxWsProxyFactoryBean.setBindingId(SOAPBinding.SOAP12HTTP_BINDING);
        WSAddressingFeature wsAddressingFeature = new WSAddressingFeature();
        wsAddressingFeature.setAddressingRequired(true);
        jaxWsProxyFactoryBean.getFeatures().add(wsAddressingFeature);

        ConsumerSupportService service =  (ConsumerSupportService) jaxWsProxyFactoryBean.create();

        Client client = ClientProxy.getClient(service);
        AddressingProperties addressingProperties = new AddressingProperties();
        AttributedURIType to = new AttributedURIType();
        to.setValue(applicationProperties.getWex().getServices().getConsumersupport().getTo());
        addressingProperties.setTo(to);
        AttributedURIType action = new AttributedURIType();
        action.setValue("http://serviceaction/SearchConsumer");
        addressingProperties.setAction(action);
        client.getRequestContext().put("javax.xml.ws.addressing.context", addressingProperties);

        setClientTimeout(client);

        return service;
    }

    private void setClientTimeout(Client client) {
        HTTPConduit conduit = (HTTPConduit) client.getConduit();
        HTTPClientPolicy policy = new HTTPClientPolicy();
        policy.setConnectionTimeout(applicationProperties.getWex().getServices().getClient().getConnectionTimeout());
        policy.setReceiveTimeout(applicationProperties.getWex().getServices().getClient().getReceiveTimeout());
        conduit.setClient(policy);
    }

同一个服务 bean 被同一个应用程序序列中的两个不同线程访问。如果我连续 10 次执行此特定序列,我将至少 3 次从服务调用中获得连接超时。我看到的是:

Caused by: java.io.IOException: Timed out waiting for response to operation {http://theservice.com}SearchConsumer.
    at org.apache.cxf.endpoint.ClientImpl.waitResponse(ClientImpl.java:685) ~[cxf-core-3.2.0.jar:3.2.0]
    at org.apache.cxf.endpoint.ClientImpl.processResult(ClientImpl.java:608) ~[cxf-core-3.2.0.jar:3.2.0]

如果我更改顺序以使其中一个线程不调用此服务,那么错误就会消失。所以,这里似乎发生了某种竞争条件。如果我查看该服务的代理管理器中的日志,我可以看到两个服务调用都非常快速地返回响应,但是第二个服务调用似乎卡在代码中的某个地方并且从未真正放开连接直到达到超时值。我一直在尝试追查此问题的原因很长一段时间,但没有成功。

关于 CXF 客户端代理是否是线程安全的,我读过一些不同的意见,但我的印象是它们是。如果事实并非如此,我应该为每次调用创建一个新的客户端代理,还是使用代理池?

【问题讨论】:

    标签: spring-boot cxf jax-ws


    【解决方案1】:

    原来是代理不是线程安全的问题。我最终要做的是利用一种类似于在这篇文章底部发布的解决方案:Is this JAX-WS client call thread safe? - 我为代理创建了一个池,并使用它以线程安全的方式从多个线程访问代理。这似乎效果很好。

    public class JaxWSServiceProxyPool<T> extends GenericObjectPool<T> {
        JaxWSServiceProxyPool(Supplier<T> factory, GenericObjectPoolConfig poolConfig) {
            super(new BasePooledObjectFactory<T>() {
                @Override
                public T create() throws Exception {
                    return factory.get();
                }
    
                @Override
                public PooledObject<T> wrap(T t) {
                    return new DefaultPooledObject<>(t);
                }
            }, poolConfig != null ? poolConfig : new GenericObjectPoolConfig());
        }
    }
    

    然后我创建了一个简单的“注册表”类来保存对各种池的引用。

    @Component
    public class JaxWSServiceProxyPoolRegistry {
        private static final Map<Class, JaxWSServiceProxyPool> registry = new HashMap<>();
    
        public synchronized <T> void register(Class<T> serviceTypeClass, Supplier<T> factory, GenericObjectPoolConfig poolConfig) {
            Assert.notNull(serviceTypeClass);
            Assert.notNull(factory);
            if (!registry.containsKey(serviceTypeClass)) {
                registry.put(serviceTypeClass, new JaxWSServiceProxyPool<>(factory, poolConfig));
            }
        }
    
        public <T> void register(Class<T> serviceTypeClass, Supplier<T> factory) {
            register(serviceTypeClass, factory, null);
        }
    
        @SuppressWarnings("unchecked")
        public <T> JaxWSServiceProxyPool<T> getServiceProxyPool(Class<T> serviceTypeClass) {
            Assert.notNull(serviceTypeClass);
            return registry.get(serviceTypeClass);
        }
    }
    

    为了使用它,我做到了:

    JaxWSServiceProxyPoolRegistry jaxWSServiceProxyPoolRegistry = new JaxWSServiceProxyPoolRegistry();
    jaxWSServiceProxyPoolRegistry.register(ConsumerSupportService.class,
                this::buildConsumerSupportServiceClient,
                getConsumerSupportServicePoolConfig());
    

    其中 buildConsumerSupportServiceClient 使用 JaxWsProxyFactoryBean 来构建客户端。

    要从池中检索一个实例,我会注入我的注册表类,然后执行以下操作:

    JaxWSServiceProxyPool<ConsumerSupportService> consumerSupportServiceJaxWSServiceProxyPool = jaxWSServiceProxyPoolRegistry.getServiceProxyPool(ConsumerSupportService.class);
    

    然后根据需要从池中借/归还对象。

    到目前为止,这似乎运作良好。我已经针对它执行了一些相当重的负载测试并且它被阻止了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-01
      • 2020-08-01
      • 2023-01-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多