查询中国移动fj.10086.cn最新优惠信息
背景
之前有 一篇文章 专门介绍webservice,只能算是webservice入门,本文则是涉及到调用的客户端,也是在实际使用过程中出现了问题,通过自己的分析以及相关资料的查询才了解到具体原因,记录下来,避免自己以后踩坑。
线上问题复现
在线上应用中,有一个服务端使用了webservice,client也就只能使用wsdl协议进行调用。具体调用过程如下:
创建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请求的。
通过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 [
这里的报错提示是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 [
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方法的内部。
果然Call类中Service是一个共享的变量,也就是所有的请求使用的是相同的Service,那么如果Service是非线程安全的,整个调用过程自然也就是非线程安全的。而Http请求参数的调整使得线程安全问题更严重了,整个逻辑上的分析是合理的,那么Service究竟是不是线程安全的呢?答案是否定。
Are Axis2 generated stubs thread-safe
也就是说axis1以及axis2都是线程非安全的。
解决办法stackoverflow上提到了一种解决办法,那就是池化技术。既能解决线程安全问题,同时也提高了性能(需要调用的时候从池子里面获取对象,调用完成之后还回去)。个人不太喜欢这种方式,觉得对资源的消耗还是有点多,因为每个Service都会创建HttpClient,并且在里面还会用到连接池。引入ThreadLocal解决线程安全问题,网上找了一下:threadLocal解决线程安全问题吗?结论是ThreadLocal不能解决共享变量的线程安全问题。切记切记。既然webservice本质上依然是通过Http调用远程服务,那么我完全可以不用框架,自己用Http工具类调用服务,这样不就没有任何线程安全的问题了,而且可以对调用过程进行优化,最终决定采用这种方式实现。小结
看似一个简单client端调用,在使用过程中由于没有注意到线程安全的问题,就会出错。反思一下就是在自己不了解类库的情况下,盲目地使用单例才是引起线程安全问题的根本原因,单例虽好,但在调用过程中一定确认是线程安全的才能使用,对于非线程安全的一定不能使用单例。
作者:hujun8610
链接:https://juejin.cn/post/7000172743761068063
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
如发现本站有涉嫌抄袭侵权/违法违规等内容,请<举报!一经查实,本站将立刻删除。