简体   繁体   English

Spring数据存储库多线程性能

[英]Spring Data Repository Multi-Threading Performance

I am running into a performance issue with multiple threads accessing the same spring data repository object. 我遇到了多个线程访问同一spring数据存储库对象的性能问题。 Many of the threads are getting blocked while waiting on a lock for the repository object. 在等待锁定存储库对象时,许多线程被阻塞。 Most all of the threads are performing the same query on the repository object. 大多数线程都对存储库对象执行相同的查询。 When this thread block runs, the CPU is maxed out on all cores. 当该线程块运行时,所有内核上的CPU均已用尽。 Occasionally it will dip, which I think is from the blocked threads waiting on a lock for the repository object. 有时它会下降,我认为这是由于等待存储库对象锁的阻塞线程所致。 I have verified through profiling that multiple threads are waiting to call the same method in the repository object. 我已经通过分析验证了多个线程正在等待调用存储库对象中的相同方法。 I did see a boost in performance with changing the approach to use the method that returns a list. 通过更改使用返回列表的方法的方法,我确实看到了性能的提升。 But the locking is still a bottleneck. 但是锁定仍然是瓶颈。

UPDATE: After more research, I have come to the conclusion that the repository object is a singleton. 更新:经过更多研究,我得出的结论是,存储库对象是单例。 This one object is getting locked as each thread accesses it. 当每个线程访问该对象时,该对象将被锁定。 How do I prototype the repository object? 我如何原型存储库对象? (I would create a read only repository for this use case.) Would the configuration have to change? (我将为此用例创建一个只读存储库。)是否必须更改配置? Does Spring data already do this? Spring数据已经做到了吗?

MWE: MWE:

public interface EntityJpaRepository extends JpaRepository<Entity, Integer> {
    @Query(value = "select * from SomeTable where id = (?1);", nativeQuery = true)
    Entity findById(int id);

    //Method that returns a list of Entities
    @Query(value = "select * from SomeTable where id in (?1);", nativeQuery = true)
    List<Entity> findAllWithIds(List<Integer> ids);
}

@Component
@Scope("prototype")
public class AThread implements Runnable {
    @Autowired
    EntityJpaRepository myRepository;

    final int someId;         

    public AThread(int someId) {
      this.someId = someId;
    }

    @Override
    public void run() {
        //may call subMethod 1
        myRepository.findById(someId);
        //may call subMethod 1
        List<Integer> ids = someMethodWhichReturnsIDs();
        myRepository.findAllWithIds(ids);
        //may call subMethod 1
    }

    public void subMethod1(){
        //sometimes loop
        subMethod2();
        //may call 
    }
    public void subMethod2(){
        //more stuff
        List<Integer> ids = someMethodWhichReturnsIDs();
        //more stuff
    }
}

public static void main(String[] args){
ThreadPoolTaskExecutor taskExecutor = (ThreadPoolTaskExecutor) ctx.getBean("taskExecutor");

List<int> someInts;//assume this is full of ints.
for(int someId: someInts){
    taskExecutor.execute((Runnable)ctx.getBean("AThread", someId));
}
waitThreads(taskExecutor);

I will say I am getting a fair bit of performance out of what I currently have. 我要说的是,我现在的表现会有所提高。 I am also not sure if I have setup my configuration correctly to be hitting the database with multiple threads/connections. 我也不确定是否已正确设置我的配置,以便通过多个线程/连接访问数据库。 I don't think this is the issue, but I have provided the full configuration. 我不认为这是问题,但是我提供了完整的配置。 Any tips for performance are welcome. 欢迎任何性能提示。

@Configuration
@EnableJpaRepositories(basePackages= {"org.repository"})
@ContextConfiguration(locations={"classpath:META-INF/spring/app-context.xml"})
@ComponentScan(basePackages = "org.somepackage")
public class JpaConfiguration {

    @Value("#{mainDataSource}")
    private javax.sql.DataSource dataSource;

    @Bean
    public Map<String, Object> jpaProperties() {
        Map<String, Object> props = new HashMap<String, Object>();
        props.put("hibernate.dialect", MySQLDialect.class.getName());
        props.put("hibernate.format_sql", true);
        return props;
    }

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
        hibernateJpaVendorAdapter.setDatabase(Database.MYSQL);
        return hibernateJpaVendorAdapter;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new JpaTransactionManager( entityManagerFactory().getObject());
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
        lef.setDataSource(this.dataSource);
        lef.setJpaPropertyMap(this.jpaProperties());
        lef.setJpaVendorAdapter(this.jpaVendorAdapter());
        return lef;
    }

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
        pool.setCorePoolSize(100);
        pool.setMaxPoolSize(500);
        pool.setWaitForTasksToCompleteOnShutdown(true);
        pool.setKeepAliveSeconds(1800);
        return pool;
    }
}

Here is a stack trace of a thread when I pause the process/application while it is blocked. 这是线程被阻塞时我暂停进程/应用程序时的线程堆栈跟踪。 I also have sample output from the pro-filer. 我也有pro-filer的示例输出。 When it runs for a longer duration, the blocked time adds up. 如果运行时间较长,则阻塞时间加起来。 It is clearly getting blocked by another thread on the @Autowired repository object. 显然,它已被@Autowired存储库对象上的另一个线程阻塞。 I thought I have avoided this by using the prototype scope. 我以为我已经通过使用原型范围避免了这种情况。

taskExecutor-1  BLOCKED 4.68003 6320    34  2062    1   java.lang.Object@5842edfa   taskExecutor-9


java.lang.ClassLoader.loadClass(Unknown Source)
sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
java.lang.ClassLoader.loadClass(Unknown Source)
org.springframework.util.ClassUtils.isVisible(ClassUtils.java:1209)
org.springframework.util.ClassUtils.getAllInterfacesForClassAsSet(ClassUtils.java:1136)
org.springframework.util.ClassUtils.getAllInterfacesForClassAsSet(ClassUtils.java:1143)
org.springframework.util.ClassUtils.getAllInterfacesForClass(ClassUtils.java:1099)
org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:302)
com.sun.proxy.$Proxy41.createNativeQuery(Unknown Source)
org.springframework.data.jpa.repository.query.NativeJpaQuery.createJpaQuery(NativeJpaQuery.java:65)
org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.doCreateQuery(AbstractStringBasedJpaQuery.java:72)
org.springframework.data.jpa.repository.query.AbstractJpaQuery.createQuery(AbstractJpaQuery.java:165)
org.springframework.data.jpa.repository.query.JpaQueryExecution$SingleEntityExecution.doExecute(JpaQueryExecution.java:197)
org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:74)
org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:98)
org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:89)
org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:421)
org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:381)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.data.repository.core.support.RepositoryFactorySupport$DefaultMethodInvokingMethodInterceptor.invoke(RepositoryFactorySupport.java:512)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(    CrudMethodMetadataPostProcessor.java:122)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
com.sun.proxy.$Proxy81.findAllWithIds(Unknown Source)
sun.reflect.GeneratedMethodAccessor64.invoke(Unknown Source)
sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
java.lang.reflect.Method.invoke(Unknown Source)
org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
com.sun.proxy.$Proxy90.findAllWithIds(Unknown Source)
org.somepackage.AThread.subMethod2(AThread.java:696)
org.somepackage.AThread.subMethod1(AThread.java:346)
org.somepackage.AThread.run(AThread.java:132)
java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
java.lang.Thread.run(Unknown Source)

This looks very much like a problem with loading the class of your query repeatedly. 这看起来很像是反复加载查询类的问题。 Repeated loads of the same class can lead to heavy contention on the classloader lock. 重复加载相同的类可能会导致在类加载器锁上产生大量争用。 (example here: https://plumbr.eu/blog/locked-threads/classloading-and-locking ) (例如: https//plumbr.eu/blog/locked-threads/classloading-and-locking

The sharedEntityManager loads the class in line 302 - and this will run into the synchronized block on line 404 in the ClassLoader sharedEntityManager在302行中加载该类-它将在ClassLoader中的404行中运行到同步块中

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM