Axis调用webService问题

·  阅读 621

背景

之前有 一篇文章 专门介绍webservice,只能算是webservice入门,本文则是涉及到调用的客户端,也是在实际使用过程中出现了问题,通过自己的分析以及相关资料的查询才了解到具体原因,记录下来,避免自己以后踩坑。

线上问题复现

在线上应用中,有一个服务端使用了webservice,client也就只能使用wsdl协议进行调用。具体调用过程如下:

  1. 创建Service单例。
CLIENT;

private Service serviceInstance;

ServiceEnum(){
    serviceInstance = new Service(getClientEngineConfig());
}

public Service getServiceInstance() {
    return serviceInstance;
}

private static EngineConfiguration getClientEngineConfig() {
    SimpleProvider engineProvider = new SimpleProvider();
    engineProvider.deployTransport(HTTPTransport.DEFAULT_TRANSPORT_NAME, new RequestLoggerHandler());
    return engineProvider;
}

这里面的RequestLoggerHandler是自定义的HttpSender,继承了CommonsHTTPSender,用来发送Http请求的。

  1. 通过call.invoke方法调用服务端服务。
public static String getService(String endpoint, String wsName, String xmlBody, WsdlMonitorLogInfoDTO wsdlMonitorLogInfoDTO) {
        String rspxml = "";
        Call call = (Call) ServiceEnum.CLIENT.getServiceInstance().createCall();
        call.setProperty(MessageContext.HTTP_TRANSPORT_VERSION, HTTPConstants.HEADER_PROTOCOL_11);
        call.setTargetEndpointAddress(endpoint);
        call.setOperationName(wsName);
        call.addParameter("userName", XMLType.XSD_DATE, ParameterMode.IN);
        call.setReturnType(XMLType.XSD_STRING);
        call.setTimeout(15000);
        //log.info("cmsc.gateway=>统一认证:call.invoke url=[{}],OperationName=[{}]",endpoint,wsName);
        rspxml = (String)call.invoke(new Object[]{xmlBody});
}

本质就是创建一个call对象,设置call对象属性,调用call对象的invoke方法。以上代码在日常使用的时候没有任何问题,但是在调用量增大的时候,有极少接口会调用出错。具体报错类似如下:

2021-08-24 22:00:00.620| INFO|fce3ee0082a67242|5830914be8852ad6|false|1|http-nio-9090-exec-128|c.c.c.u.g.c.e.w.RequestLoggerHandler    |the RequestLoggerHandler endPoint [https://login.10086.cn:32734/services/AssertionQryUID?wsdl], respMesssage [<?xml version="1.0" encoding="utf-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body><soapenv:Fault><faultcode xmlns:ns1="http://xml.apache.org/axis/">ns1:Client</faultcode><faultstring>No such operation 'getAssertInfoByArtifact'</faultstring><detail><ns2:hostname xmlns:ns2="http://xml.apache.org/axis/">ac-webservice-7df55d9dfb-9vbq9</ns2:hostname></detail></soapenv:Fault></soapenv:Body></soapenv:Envelope>]
2021-08-24 22:00:00.621|ERROR|fce3ee0082a67242|5830914be8852ad6|false|1|http-nio-9090-exec-128|c.c.c.u.g.c.api.wsdlCall.WsdlService    |[d424df19f29c4166902a93e9198535ed_CA02001],请求统一认证异常:No such operation 'getAssertInfoByArtifact'

这里的报错提示是AssertionQryUID这个方法的webserviceName应该是AssertionQryUID,但是日志里面提示传入的是getAssertInfoByArtifact,因此服务端报错了。请求参数如下所示:

2021-08-24 22:00:00.501| INFO|fce3ee0082a67242|5830914be8852ad6|false|1|http-nio-9090-exec-128|c.c.c.u.g.c.e.w.RequestLoggerHandler    |the RequestLoggerHandler endPoint [https://login.10086.cn:32734/services/AssertionQryUID?wsdl], reqMesssage [<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body>
  <getAssertInfoByArtifact soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
   <userName xsi:type="xsd:string">0qmwPBkdgvy7WZTW5RMSKde93O3Z86s9iAH3+pE2vnRZcd8eFCbAAK3Y4zuInazzmnL4cJvMmxaCzq9t+lOQHb62uWSXc9nfS+0RAPhPHLV1dRnnviCnP3ngLJabhfGqqrS6E4rPHFAv+8QrTcKHeunPmEZH/AUqsUn3GU0V/bnFdqRHaOJ2ADfjcaCpn34s8AVlB+T5d5t7SLmtF+SNk46xpPLgxW3W0WcTPKKfGFuBGgBtM5MC7D3w7thHP+nO87azJyU202WHbV11waL/qvj1OGPfwtjHnNF/VB5/Km7LUNe4noeoqvWSCHglnSL1oIGOHb5vznt+DSceprItHYcUb8WIDZB/fxM7CUfRia+N5vY4SNvMkHbLy4/jQTKL4bvdxrDt00WPG1Xd795AXmHRuuVhToj5mq/KI2ZH3AOe0I3oYG6L5AAZsWZ7rXJtegfj8RgHctmEE8DDN4LJlE39YVXaCfoUdM4UPM5W0OhszQ7U7y6wwX+o4K41faTSEHbx7gTFhcNpBpLb7eGzCSAiVqsePwtbQOVIFnjZSjmMqekOF25pMY4oLYbHNhcOH4vShUPAUIyLFLQdgxO3L0OnJ50I4PRE2J4EeQLX277hdsQ5+n18dG8Pq/lqPOOyyJvPxiun7EnDZfC24hsbhSSBjJ1u8d+NnWJLHSzFmfh1OvDIVnIVoeizVHdEBdzm8Y5HEdjUqj9/evER95kLK/QLuxE4eAWYnYFNd6qX9nj0N9RvONHE4ckD9Y/BV1U3</userName>
  </getAssertInfoByArtifact>
 </soapenv:Body>
</soapenv:Envelope>]
  1. 更有意思的是在调整了Http请求类的超时时间等参数之后,接口的识别率提高的很快。
AxisProperties.setProperty("axis.http.client.maximum.total.connections","150");
AxisProperties.setProperty("axis.http.client.maximum.connections.per.host","50");
AxisProperties.setProperty("axis.http.client.connection.pool.timeout","20000");
AxisProperties.setProperty("axis.http.client.connection.default.connection.timeout","10000");
AxisProperties.setProperty("axis.http.client.connection.default.so.timeout","5000");

问题分析

这个问题在调用量低的时候很难复现,在调用量增大之后变得严重,看起来非常像线程安全的问题。查看整个调用过程,虽然getService方法是一个静态方法,但是Call这个对象每次调用都会创建,是一个局部变量,应该不会有线程安全问题。除非Call对象里面有一些共享变量之类的导致线程安全问题。看下Call方法的内部。

image.png

果然Call类中Service是一个共享的变量,也就是所有的请求使用的是相同的Service,那么如果Service是非线程安全的,整个调用过程自然也就是非线程安全的。而Http请求参数的调整使得线程安全问题更严重了,整个逻辑上的分析是合理的,那么Service究竟是不是线程安全的呢?答案是否定。

image.png

Are Axis2 generated stubs thread-safe

也就是说axis1以及axis2都是线程非安全的。

解决办法

  1. stackoverflow上提到了一种解决办法,那就是池化技术。既能解决线程安全问题,同时也提高了性能(需要调用的时候从池子里面获取对象,调用完成之后还回去)。个人不太喜欢这种方式,觉得对资源的消耗还是有点多,因为每个Service都会创建HttpClient,并且在里面还会用到连接池。
  2. 引入ThreadLocal解决线程安全问题,网上找了一下:threadLocal解决线程安全问题吗?结论是ThreadLocal不能解决共享变量的线程安全问题。切记切记。
  3. 既然webservice本质上依然是通过Http调用远程服务,那么我完全可以不用框架,自己用Http工具类调用服务,这样不就没有任何线程安全的问题了,而且可以对调用过程进行优化,最终决定采用这种方式实现。

小结

看似一个简单client端调用,在使用过程中由于没有注意到线程安全的问题,就会出错。反思一下就是在自己不了解类库的情况下,盲目的使用单例才是引起线程安全问题的根本原因,单例虽好,但在调用过程中一定确认是线程安全的才能使用,对于非线程安全的一定不能使用单例。

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改