我们有一堆微服务,它们使用 JAXB 在 XML 有效负载和 Java 对象之间进行编组/取消编组。 在我们的负载测试中,我们注意到性能显着下降,在放置大量日志条目后,我们发现 JAXB 处理(主要是解组部分)有时需要 1 到 3 mills(理应如此),但有时需要几秒钟(甚至 10 到 20 秒)来处理基本相同的有效负载(只有几个元素值不同,使它们独一无二。有效负载不小,但也不大;大约 50KB。
当没有负载时,处理总是很快。我唯一的一个怀疑(没有任何关于 JAXB 内部工作原理的研究)是有一些
syncronized
调用,并且大量线程并行执行相同的操作,这只会阻碍整个事情。
这就是我们解组的方式:
public MyJavaObject toMyJavaObject(String payload) {
try (StringReader reader = new StringReader(payload)) {
final StreamSource source = new StreamSource(reader);
return (MyJavaObject) ((JAXBElement) myMarshaller.unmarshal(source)).getValue();
}
}
在哪里创建
myMarshaller
bean,如下所示:
@Value("classpath:my/path/MyJavaObject.xsd")
private Resource myJavaObjectSchema;
@Bean
@Qualifier("myMarshaller")
public Jaxb2Marshaller myMarshaller() {
final Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setSchemas(myJavaObjectSchema);
marshaller.setClassesToBeBound(MyJavaObject.class, JAXBElement.class);
marshaller.setCheckForXmlRootElement(false);
return marshaller;
}
所以我的问题是是什么导致了如此大的性能影响,或者我们如何找到问题所在。 2 磨机和 20 秒之间的差异是巨大的,当有效载荷结构相同时,为什么会出现这样的差异。有什么需要注意的地方或者有办法解决吗?此外,如果最坏的情况发生,是否有更好的 JAXB 替代方案。我们还使用 JAXB 进行模式验证,这是连接我们系统以符合行业标准的重要对象。
我们的 JAXB 类是 spring-oxm-5.3.27.jar 的一部分,后者将使用
javax.xml.bind:jaxb-api:2.3.1
** 更新 ** 我将解析器更改为不再是 Spring bean,而是为每条未编组的消息创建一个新的编组器实例,并且性能下降得更多:
parse 'Time taken to parse my request: *, client reference id: *' AS duration, clientReferenceId
| filter @message like 'Time taken to parse my request:'
| stats avg(duration)
Showing 1 of 9,831 records matched
12650.4609
parse 'Time taken to parse my request: *, client reference id: *' AS duration, clientReferenceId
| filter @message like 'Time taken to parse my request:'
| sort duration asc
duration clientReferenceId
1 16 OHaRPFh7Z2USRpfveFfB7NE1OEdU
2 16 3Xo1n6nZsrBtfROsRlh3N2rZHKK4
parse 'Time taken to parse my request: *, client reference id: *' AS duration, clientReferenceId
| filter @message like 'Time taken to parse my request:'
| sort duration desc
duration clientReferenceId
1 124585 Oj8L3Vt9dzDuWBb4wXv7yaUiv
2 120226 c972mxd544fjXqXIDp3DdPLP9
我们做了类似的观察:我们在启用模式验证的情况下向 JAXB 抛出的线程越多,它的性能就越差。在我们的例子中,锁争用在探查器(如 VisualVM)的线程视图中清晰可见,我们能够将其追踪到每次编组/解组时调用的
synchronized
方法 com.sun.org.apache.xerces.internal.impl.dv.SchemaDVFactory.getInstance()
。我们将此作为 bug 报告给 OpenJDK。
请注意,Xerces2 库(JDK 的 XML 解析器/验证器实现源自于此)已于 2007 年解决了此问题(!)。您可以尝试使用它(只需添加依赖项,ServiceLoader 机制会自动拾取它) - 在我们的例子中,它 100% 解决了问题。