2018.11.18
前言
項目背景:有一個基於Spring Boot的調度器,負責調度並向Yarn提交Spark作業。在測試時發現,有個Spark作業一直報__app__.jar找不到的錯誤。
ERROR yarn.ApplicationMaster: User class threw exception: org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [applicationContext.xml]; nested exception is java.lang.IllegalStateException: Unable to load schema mappings from location [META-INF/spring.schemas]
org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [applicationContext.xml]; nested exception is java.lang.IllegalStateException: Unable to load schema mappings from location [META-INF/spring.schemas]
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:414)
...
at org.apache.spark.deploy.yarn.ApplicationMaster$$anon$2.run(ApplicationMaster.scala:542)
Caused by: java.lang.IllegalStateException: Unable to load schema mappings from location [META-INF/spring.schemas]
at org.springframework.beans.factory.xml.PluggableSchemaResolver.getSchemaMappings(PluggableSchemaResolver.java:154)
...
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:391)
... 27 more
Caused by: java.io.FileNotFoundException: /path/application_1540964852368_1198/container_1540964852368_1198_01_000001/__app__.jar (No such file or directory)
at java.util.zip.ZipFile.open(Native Method)
...
at org.springframework.beans.factory.xml.PluggableSchemaResolver.getSchemaMappings(PluggableSchemaResolver.java:145)
... 48 more
看到root caused by之後,我一度認爲是集羣的問題,比如Yarn某個節點不穩定,導致jar包在分發時失敗,進而運行失敗。然而在經過多次重啓後,除了這個作業其它作業都能正常運行,這種說法是站不住腳的。
方法
在向上翻看log時,發現一個跟Spring Boot有關的Warning:
WARN internal.EntityManagerFactoryRegistry: HHH000436: Entity manager factory name (default) is already registered. If entity manager will be clustered or passivated, specify a unique value for property 'hibernate.ejb.entitymanager_factory_name'
從Warning可以看出,報錯的作業至少試圖註冊兩個同名的EntityManagerFactory1。項目使用了Hibernate作爲ORM,Hibernate中,每個EntityManagerFactory有一個對應的Persistence Unit,Persistence Unit(PU)要求擁有唯一的名字2。再看這個Warning就可以得知,當前JPA配置裏的EntityManagerFactory已經註冊,並且有另一個EntityManagerFactory也試圖註冊在同一個PU名下3。
錯誤的嘗試
在沒有了解PU和EMF關係之前,我根據Warning的直接理解,嘗試着在JPA的配置裏對EntityManagerFactory的Bean進行唯一命名(比如用當前時間生成Bean的名字)。這種想法期望的結果是:驗證是不是因爲多次註冊同一個EntityManagerFactory而導致報錯,如果唯一命名之後不再報錯,再去定位多次註冊的原因是什麼。
個人認爲出發點是好的,希望能逐步地逼近真相。但是由於缺乏對PU和EMF的瞭解,所以結果就導致報錯一直在。實際上,應該爲EMF的PU設置唯一命名4:
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
dataSource.setUrl("<URL>");
dataSource.setUsername("<USER>");
dataSource.setPassword("<PWD>");
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPersistenceUnitName("<UNIQUE_PU_NAME>");
}
正確的解法
由於只有一個作業會報錯,應該直接檢查作業相關的代碼。後來發現原因是:在某段代碼裏,多次地創建ClassPathXmlApplicationContext
並用ClassPathXmlApplicationContext#getBean
方法去獲取操作數據庫的Bean,結果導致JPA配置被多次加載,EMF也就被多次註冊。