这个是发生在上周周末的真实案例,因为cxf client 端线程安全导致的错误,总结出来希望其他使用cxf的兄弟注意。
首先描述一下背景,简单的说就是使用cxf作为web service的客户端,运行在weblogic上,连接外部的服务器。为了测试需要,开发了一个简单的模拟器模拟服务器端,准备在release之前跑稳定性测试。
结果出问题了,在排除掉一些干扰和诸如网络环境,设置等之后问题依旧,由于系统负责,包括ws的模拟器也是出了一个之前没有试过的方法,因此费了不少时间来查找问题。过程很枯燥,应该很多人经历过,在一个大的系统中找到一个小错误的出处,可以说是一门学问,技术耐心和运气都是需要的.....跳出这个过程,由于问题表现在web service的网络连接在这个异常上,在服务器端模拟器的日志中有大量的这种异常信息:
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->
2009
-
07
-
24
19
:
23
:
22
,
898
DEBUG ( : ) (tomcat
-
exec
-
56
) [Http11NioProcessor]
-
Error parsing HTTP request header
java.io.EOFException: Unexpected EOF read on the socket
at org.apache.coyote.http11.InternalNioInputBuffer.readSocket(InternalNioInputBuffer.java:
589
)
at org.apache.coyote.http11.InternalNioInputBuffer.parseRequestLine(InternalNioInputBuffer.java:
425
)
at org.apache.coyote.http11.Http11NioProcessor.process(Http11NioProcessor.java:
825
)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:
719
)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:
2080
)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:
885
)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:
907
)
at java.lang.Thread.run(Thread.java:
619
)
2009
-
07
-
24
19
:
23
:
22
,
898
DEBUG ( : ) (tomcat
-
exec
-
56
) [Http11NioProcessor]
-
Error parsing HTTP request header
java.io.EOFException: Unexpected EOF read on the socket
at org.apache.coyote.http11.InternalNioInputBuffer.readSocket(InternalNioInputBuffer.java:
589
)
at org.apache.coyote.http11.InternalNioInputBuffer.parseRequestLine(InternalNioInputBuffer.java:
425
)
at org.apache.coyote.http11.Http11NioProcessor.process(Http11NioProcessor.java:
825
)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:
719
)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:
2080
)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:
885
)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:
907
)
at java.lang.Thread.run(Thread.java:
619
)
而服务器端模拟器这次是我们第一次使用tomcat和coyote,因此怀疑是tomcat的问题,在再三追查代码无果的情况下,决定换一个服务器端模拟器来确认问题所在:到底是cxf的客户端的问题,还是服务器端模拟器。一个简单的模拟器写出来了,一个跳过所有业务逻辑直接调用cxf客户端实现代码的测试小程序写出来了,测试之后发现,问题依旧。于是将目光集中到cxf的客户端上。
在测试中发现这样一个规律,在上述服务器端的异常发生前,在客户端中总是会有规律的出现下面这个异常:
看来问题是出现在这里了。
进一步的测试发现,低压力下比如2-3个工作线程,基本不会有任何问题,因此可以解释为什么功能测试时不出现问题。工作线程加到10个,基本上还算问题,只有极其偶然的会出现一次两次这个异常。进一步加大工作线程,由于受到测试机器的性能限制,10个工作线程和100个工作线程的tps基本相同,都大体在300TPS左右(客户端在笔记本上跑的,cpu已经90%+了)。测试的结果是上面的异常开始变的有规律,恩,非常搞笑的规律,大概每10000次左右的请求就发生一次上述异常,非常的稳定而执着的重现。服了,这么有规律而重现性极好的错误,还真是难得一见........
分析一下问题,在tps基本保持不变的情况下,客户端线程从10增加到100问题就变得明显。因此问题的焦点直指线程安全这个老大难问题,重新审视我们使用cxf的代码,发现有个地方
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->
/**
* the server service
*/
MessagingChannelServiceImplService mcsis
=
null
;
/**
* the sever service port
*/
MessagingChannelService mcs
=
null
;
mcsis
=
new
MessagingChannelServiceImplService(serviceWsdlUrl);
mcs
=
mcsis.getMessagingChannelServiceImplPort();
MessagingChannelService是cxf自动生成的,这个是@WebService,其他的业务代码都是调用它上面的业务方法来实现。由于serviceWsdlUrl不变,因此我们重用了一些东西,避免每次都初始化一次。看来问题出现在这里,试着将代码修改为threadlocal,让每个线程都初始化一次然后保存给自己使用。
修改后的客户端代码在之后的测试中,非常稳定,没有再出现上面的异常,问题算是解决了。
我对cxf不是很熟悉,找了一下也没有找到到底是那里造成的线程不安全,google了一下找到几个地方,但是似乎还不能完全说明问题。先列出来慢慢研究:
1) Are JAX-WS client proxies thread safe?
装载自这里:http://cxf.apache.org/faq.html#FAQ-AreJAXWSclientproxiesthreadsafe%253F
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->
Official JAX-WS answer: No. According to the JAX-WS spec, the client proxies are NOT thread safe. To write portable code, you should treat them as non-thread safe and synchronize access or use a pool of instances or similar.
CXF answer: CXF proxies are thread safe for MANY use cases. The exceptions are:
* Use of ((BindingProvider)proxy).getRequestContext() - per JAX-WS spec, the request context is PER INSTANCE. Thus, anything set there will affect requests on other threads. With CXF, you can do:
((BindingProvider)proxy).getRequestContext().put("thread.local.request.context", "true");
((BindingProvider)proxy).getRequestContext().put("thread.local.request.context", "true");
and future calls to getRequestContext() will use a thread local request context. That allows the request context to be threadsafe. (Note: the response context is always thread local in CXF)
* Settings on the conduit - if you use code or configuration to directly manipulate the conduit (like to set TLS settings or similar), those are not thread safe. The conduit is per-instance and thus those settings would be shared.
* Session support - if you turn on sessions support (see jaxws spec), the session cookie is stored in the conduit. Thus, it would fall into the above rules on conduit settings and thus be shared across threads.
For the conduit issues, you COULD install a new ConduitSelector that uses a thread local or similar. That's a bit complex though.
For most "simple" use cases, you can use CXF proxies on multiple threads. The above outlines the workarounds for the others.
2) cxf的wiki中谈到Client API中的Proxy-based API
wiki 地址: http://cwiki.apache.org/CXF20DOC/jax-rs.html
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->Limitations
Proxy methods can not have @Context method parameters and subresource methods returning Objects can not be invoked - perhaps it is actually not too bad at all - please inject contexts as field or bean properties and have subresource methods returning typed classes : interfaces, abstract classes or concrete implementations.
Proxies are currently not thread-safe.
3) thread safe issue caused by XMLOutputFactoryImpl
找到的一个cxf的bug,https://issues.apache.org/jira/browse/CXF-2229
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--> Description
Currently CXF calls StaxUtils.getXMLOutputFactory() to get the cached instance of XMLOutputFactoryImpl. But XMLOutputFactoryImpl.createXMLStreamWriter is not thread
-
safe. See below.
javax.xml.stream.XMLStreamWriter createXMLStreamWriter(javax.xml.transform.stream.StreamResult sr, String encoding)
throws
javax.xml.stream.XMLStreamException {
try
{
if
(fReuseInstance
&&
fStreamWriter
!=
null
&&
fStreamWriter.canReuse()
&&
!
fPropertyChanged){
fStreamWriter.reset();
fStreamWriter.setOutput(sr, encoding);
if
(DEBUG)System.out.println(
"
reusing instance, object id :
"
+
fStreamWriter);
return
fStreamWriter;
}
return
fStreamWriter
=
new
XMLStreamWriterImpl(sr, encoding,
new
PropertyManager(fPropertyManager));
--
this
is not thread safe, since the
new
instance is assigned to the field fStreamWriter first, then it is possible that different threads get the same XMLStreamWriterImpl when they call
this
method at the same time.
}
catch
(java.io.IOException io){
throw
new
XMLStreamException(io);
}
}
The solution might be, StaxUtils.getXMLOutputFactory() method creates a
new
instance of XMLOutputFactory every time, don
'
t cache it.
分享到:
相关推荐
cxf+spring+client+springmvc
cxf client
赠送jar包:cxf-rt-rs-client-3.0.1.jar; 赠送原API文档:cxf-rt-rs-client-3.0.1-javadoc.jar; 赠送源代码:cxf-rt-rs-client-3.0.1-sources.jar; 赠送Maven依赖信息文件:cxf-rt-rs-client-3.0.1.pom; 包含...
springboot整合CXF发布webservice和客户端调用 项目中每个类都有详细的注释,保证都能看懂,不失为一个学习springboot整合cxf来学习webservice发布调用的好例子
NULL 博文链接:https://flame-zhou.iteye.com/blog/2206232
这是一个cxf安全验证的完整实例,直接可以部署到应用服务器中运行。
使用CXF开发Web Service,包含服务器端和客户端
CXF实现SSL安全验证,实现https的WebService
Apache Cxf 安全认证,includes some source code to test
CXF 自定义拦截器实现的 webservice安全机制实例工程, 带jar包 ,代码注释详细 内有说明文档, 由于资源过大 ,所以客户端jar包 删除掉了, 只需要把服务端的jar 拷贝到 客户端即可 , 小弟刚刚研究完 CXF 安全 。...
CXF实例源代码 客户端调用 web service 入门教程
wsdl2java源码springboot-apachecxf-client 本项目演示了如何在Springboot中实现apachecxf客户端,以及如何为客户端调用生成wsdltojava。 Springboot-apachecxf-jaxws 示例 此应用程序展示了如何使用 apachecxf ...
CXF动态调用webservice.demo测试用例,亲测通过,拿走不谢
在websphere8.5 下部署含有CXFwebservice的war包无法正常启动,而相应的war包在tomcat上是可以正常启动的,通过后台的日志分析大致可以定位为相关的cxf类无法找到,其实这些类在项目的lib目录下都是存在的,莫名其妙...
C#动态调用CXF WEBSERVICE框架共通类。
apache-cxf-2.7.7以及cxf客户端所需要的jar包,命令生成webservice客户端
cxf spring server client demo.rar soap webservice
我在工程里配的路径是http://localhost:8080/cxf/ws.请注意后缀的路径ws是必须的.因为我在web.xml里配的它的路径是<url-pattern>/ws/*</url-pattern>.这个是根据需要可以改变的.有什么不明白的,可以发邮件到我的邮箱...
JAVA 用 Apache CXF 调用 .NET 服务端 WebService 代码 整个JAVA工程的压缩,导入到myeclipse可直接运行,如果只想浏览下,请访问:http://blog.csdn.net/wqmain/article/details/8216331
ssh框架整合cxf(webservice),ssh案例(增、删、改、查),发布webservice,客户端调用,该工程自带jar包,mysql...⑤在ssh2cxf项目里com.ccrfid.client包下运行PersonnelClient.java类调用webservice(增删改查)