JDK SOAP解析阻塞问题分析及解决

在高并发场景下发现SOAP解析时超慢,耗时有时会达到几十秒,抓取线程栈分析发现大量的线程在等待:

   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000005c249caf0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.ArrayBlockingQueue.take(ArrayBlockingQueue.java:403)
	at com.sun.xml.internal.messaging.saaj.util.ParserPool.get(ParserPool.java:71)
	at com.sun.xml.internal.messaging.saaj.soap.EnvelopeFactory.createEnvelope(EnvelopeFactory.java:81)
	at com.sun.xml.internal.messaging.saaj.soap.ver1_1.SOAPPart1_1Impl.createEnvelopeFromSource(SOAPPart1_1Impl.java:69)
	at com.sun.xml.internal.messaging.saaj.soap.SOAPPartImpl.getEnvelope(SOAPPartImpl.java:128)
	at com.sun.xml.internal.messaging.saaj.soap.MessageImpl.getSOAPBody(MessageImpl.java:1351)

分析com.sun.xml.internal.messaging.saaj.soap.EnvelopeFactory代码发现:

     private static ContextClassloaderLocal parserPool = new ContextClassloaderLocal() {
        protected ParserPool initialValue() throws Exception {
            return new ParserPool(5);
        }
    };

此处的ParserPool中仅有5个实例,并且不支持配置。在高并发场景下,必然会导致阻塞于此。
修改方案就是想办法替换掉此处的EnvelopeFactory,从而能够扩大ParserPool,或者支持可配置。分析代码发现,SOAP解析支持配置MessageFactory和SOAPFactory,是否可以考虑用MessageFactory或者SOAPFactory实现替换掉JDK中的实现从而实现替换掉EnvelopeFactory。进一步分析线程栈和代码发现

  • com.sun.xml.internal.messaging.saaj.soap.ver1_1.SOAPPart1_1Impl类中调用了EnvelopeFactory
  • SOAPPart1_1Impl是在com.sun.xml.internal.messaging.saaj.soap.ver1_1.Message1_1Impl中new出来的
  • Message1_1Impl是通过com.sun.xml.internal.messaging.saaj.soap.ver1_1.SOAPMessageFactory1_1Impl 构造的
  • SOAPMessageFactory1_1Impl 可以通过MessageFactory指定新的实现

这样我们就可以通过实现新的MessageFactory类实现EnvelopeFactory的替换:

public class SOAPMessageFactoryImpl extends SOAPMessageFactory1_1Impl {

    public SOAPMessage createMessage(MimeHeaders headers, InputStream in) throws IOException, SOAPExceptionImpl {
        if (headers == null) {
            headers = new MimeHeaders();
        }

        if (getContentType(headers) == null) {
            headers.setHeader("Content-Type", "text/xml");
        }

        MessageImpl msg = new MessageImpl1_1(headers, in);
        msg.setLazyAttachments(this.lazyAttachments);
        return msg;
    }

}
public class MessageImpl1_1 extends Message1_1Impl {

    public MessageImpl1_1() {
    }

    public MessageImpl1_1(boolean isFastInfoset, boolean acceptFastInfoset) {
        super(isFastInfoset, acceptFastInfoset);
    }

    public MessageImpl1_1(SOAPMessage msg) {
        super(msg);
    }

    public MessageImpl1_1(MimeHeaders headers, InputStream in) throws IOException, SOAPExceptionImpl {
        super(headers, in);
    }

    public MessageImpl1_1(MimeHeaders headers, ContentType ct, int stat, InputStream in) throws SOAPExceptionImpl {
        super(headers, ct, stat, in);
    }

    @Override
    public SOAPPart getSOAPPart() {
        if (this.soapPartImpl == null) {
            this.soapPartImpl = new SOAPPartImpl1_1(this);
        }

        return this.soapPartImpl;
    }
}
public class SOAPPartImpl1_1 extends SOAPPart1_1Impl {

    public SOAPPartImpl1_1() {
    }

    public SOAPPartImpl1_1(MessageImpl message) {
        super(message);
    }

    @Override
    protected Envelope createEnvelopeFromSource() throws SOAPException {
        ...
        // all same to JDK implementation, except here: using EnvelopeFactory of own implementation
        EnvelopeImpl envelope = (EnvelopeImpl)EnvelopeFactory.createEnvelope(tmp, this);
        ....
        }
    }
}
public class EnvelopeFactory {
    protected static final Logger log = LoggerFactory.getLogger (EnvelopeFactory.class);

    private static final String SAX_PARSER_POOL_SIZE_PROP_NAME = "xxx.message.soap.saxParserPoolSize";
    private static final int DEFAULT_SAX_PARSER_POOL_SIZE = 5;

    private static final boolean lazyContentLength = SAAJUtil.getSystemBoolean("saaj.lazy.contentlength");

    private static ParserPool parserPool = new ParserPool(Integer.getInteger(
            SAX_PARSER_POOL_SIZE_PROP_NAME,
            DEFAULT_SAX_PARSER_POOL_SIZE));

    public EnvelopeFactory() {
    }

    public static Envelope createEnvelope(Source src, SOAPPartImpl soapPart) throws SOAPException {
        // same to JDK implementation
    }
}

最后在Java参数中把MessageFactory指定为自己的实现并配置池大小:

-Djavax.xml.soap.MessageFactory=xxx.message.soap.SOAPMessageFactoryImpl -Dxxx.message.soap.saxParserPoolSize=30

你可能感兴趣的:(Java)