跨线程管理事务

时间:2020-09-16 02:37:07

标签: database jakarta-ee transactions jta transaction-isolation

我一直在为我的应用程序(在kubernetes中运行)进行(就绪)运行状况检查的实验。我以前进行过准备状况检查,该检查将检查应用程序是否可以连接到数据库并进行伪查询。但是,这不能确保应用程序可以实际插入数据库(例如,插入时出现权限问题)。为了解决这个问题,我编写了执行以下操作的代码:

  1. 开始交易(userTransaction.begin())
  2. 运行插入(调用带有@Transactional(Transactional.TxType.REQUIRED)注释的方法)
  3. 在同一事务中执行(未提交)读取(未提交事务隔离级别)以验证插入是否有效
  4. 回滚在步骤1(userTransaction.rollback)中创建的交易

除了回滚之外,上面的代码将按预期执行(意味着插入已成功执行以及读取,即使回滚期间也不会引发异常)。但是,回滚将以静默方式失败(因为记录仍显示在数据库中)。经过一番挖掘后,我相信jta事务绑定到了一个线程https://docs.oracle.com/javaee/7/api/javax/transaction/UserTransaction.html),并且当我并行执行运行状况检查时,调用userTransaction的线程.begin()与调用userTransaction.rollback()的线程不同,如以下日志所示:

 2020-09-16 02:25:57,519 WARN  [com.arj.ats.arjuna] (pool-7-thread-1) ARJUNA012095: Abort of action id 0:ffffac110002:a243:5f617343:1 invoked while multiple threads active within it.
reading-comprehension       |  2020-09-16 02:25:57,519 WARN  [com.arj.ats.arjuna] (pool-7-thread-1) ARJUNA012381: Action id 0:ffffac110002:a243:5f617343:1 completed with multiple threads - thread pool-7-thread-1 was in progress with com.arjuna.ats.arjuna.coordinator.BasicAction.checkChildren(BasicAction.java:3407)
reading-comprehension       | com.arjuna.ats.arjuna.coordinator.BasicAction.Abort(BasicAction.java:1667)
reading-comprehension       | com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.cancel(TwoPhaseCoordinator.java:124)
reading-comprehension       | com.arjuna.ats.arjuna.AtomicAction.abort(AtomicAction.java:186)
reading-comprehension       | com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.rollbackAndDisassociate(TransactionImple.java:1369)
reading-comprehension       | com.arjuna.ats.internal.jta.transaction.arjunacore.BaseTransaction.rollback(BaseTransaction.java:143)
reading-comprehension       | io.quarkus.narayana.jta.runtime.NarayanaJtaProducers_ProducerMethod_userTransaction_c93608e32f9c017c8aafd0401efc2eb68d35aa4e_ClientProxy.rollback(NarayanaJtaProducers_ProducerMethod_userTransaction_c93608e32f9c017c8aafd0401efc2eb68d35aa4e_ClientProxy.zig:177)
reading-comprehension       | com.connor.reading.dao.AutoCloseableUserTransactionImpl.close(AutoCloseableUserTransactionImpl.java:27)
reading-comprehension       | com.connor.reading.health.HealthCheckerInsertAssessmentImpl.getValue(HealthCheckerInsertAssessmentImpl.java:32)
reading-comprehension       | com.connor.reading.health.HealthCheckerInsertAssessmentImpl_Subclass.getValue$$superaccessor3(HealthCheckerInsertAssessmentImpl_Subclass.zig:521)
reading-comprehension       | com.connor.reading.health.HealthCheckerInsertAssessmentImpl_Subclass$$function$$3.apply(HealthCheckerInsertAssessmentImpl_Subclass$$function$$3.zig:29)
reading-comprehension       | io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
reading-comprehension       | com.connor.reading.interceptor.DebugInterceptor.handleAroundInvoke(DebugInterceptor.java:32)
reading-comprehension       | com.connor.reading.interceptor.DebugInterceptor_Bean.intercept(DebugInterceptor_Bean.zig:385)
reading-comprehension       | io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
reading-comprehension       | io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
reading-comprehension       | io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
reading-comprehension       | com.connor.reading.health.HealthCheckerInsertAssessmentImpl_Subclass.getValue(HealthCheckerInsertAssessmentImpl_Subclass.zig:479)
reading-comprehension       | com.connor.reading.health.HealthCheckerInsertAssessmentImpl.getValue(HealthCheckerInsertAssessmentImpl.java:13)
reading-comprehension       | com.connor.reading.health.AbstractBaseHealthChecker.get(AbstractBaseHealthChecker.java:18)
reading-comprehension       | com.connor.reading.health.HealthCheckerInsertAssessmentImpl_Subclass.get$$superaccessor2(HealthCheckerInsertAssessmentImpl_Subclass.zig:414)
reading-comprehension       | com.connor.reading.health.HealthCheckerInsertAssessmentImpl_Subclass$$function$$2.apply(HealthCheckerInsertAssessmentImpl_Subclass$$function$$2.zig:29)
reading-comprehension       | io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
reading-comprehension       | com.connor.reading.interceptor.DebugInterceptor.handleAroundInvoke(DebugInterceptor.java:32)
reading-comprehension       | com.connor.reading.interceptor.DebugInterceptor_Bean.intercept(DebugInterceptor_Bean.zig:385)
reading-comprehension       | io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
reading-comprehension       | io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
reading-comprehension       | io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
reading-comprehension       | com.connor.reading.health.HealthCheckerInsertAssessmentImpl_Subclass.get(HealthCheckerInsertAssessmentImpl_Subclass.zig:372)
reading-comprehension       | com.connor.reading.health.AbstractBaseHealthChecker.get(AbstractBaseHealthChecker.java:5)
reading-comprehension       | io.smallrye.context.SmallRyeThreadContext$ContextualCallable.call(SmallRyeThreadContext.java:116)
reading-comprehension       | java.util.concurrent.FutureTask.run(FutureTask.java:264)
reading-comprehension       | java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
reading-comprehension       | java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
reading-comprehension       | java.lang.Thread.run(Thread.java:834)
reading-comprehension       | com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:517)
reading-comprehension       | com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:193)
reading-comprehension       | 
reading-comprehension       | 2020-09-16 02:25:57,520 WARN  [com.arj.ats.arjuna] (pool-7-thread-1) ARJUNA012381: Action id 0:ffffac110002:a243:5f617343:1 completed with multiple threads - thread executor-thread-1 was in progress with com.oracle.svm.core.posix.headers.Pthread.pthread_cond_timedwait(Pthread.java)
reading-comprehension       | com.oracle.svm.core.posix.thread.PosixParkEvent.condTimedWait(PosixJavaThreads.java:271)
reading-comprehension       | com.oracle.svm.core.thread.JavaThreads.park(JavaThreads.java:698)
reading-comprehension       | jdk.internal.misc.Unsafe.park(Unsafe.java:53)
reading-comprehension       | java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:234)
reading-comprehension       | java.util.concurrent.FutureTask.awaitDone(FutureTask.java:444)
reading-comprehension       | java.util.concurrent.FutureTask.get(FutureTask.java:203)
reading-comprehension       | java.util.concurrent.AbstractExecutorService.invokeAll(AbstractExecutorService.java:284)
reading-comprehension       | io.smallrye.context.SmallRyeManagedExecutor.invokeAll(SmallRyeManagedExecutor.java:118)
reading-comprehension       | com.connor.reading.service.HealthService.executeChecksInParallel(HealthService.java:102)
reading-comprehension       | com.connor.reading.service.HealthService_Subclass.executeChecksInParallel$$superaccessor10(HealthService_Subclass.zig:1528)
reading-comprehension       | com.connor.reading.service.HealthService_Subclass$$function$$10.apply(HealthService_Subclass$$function$$10.zig:43)
reading-comprehension       | io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
reading-comprehension       | com.connor.reading.interceptor.DebugInterceptor.handleAroundInvoke(DebugInterceptor.java:32)
reading-comprehension       | com.connor.reading.interceptor.DebugInterceptor_Bean.intercept(DebugInterceptor_Bean.zig:385)
reading-comprehension       | io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
reading-comprehension       | io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
reading-comprehension       | io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
reading-comprehension       | com.connor.reading.service.HealthService_Subclass.executeChecksInParallel(HealthService_Subclass.zig:1483)
reading-comprehension       | com.connor.reading.service.HealthService.runHealthChecksInParallel(HealthService.java:84)
reading-comprehension       | com.connor.reading.service.HealthService_Subclass.runHealthChecksInParallel$$superaccessor2(HealthService_Subclass.zig:614)
reading-comprehension       | com.connor.reading.service.HealthService_Subclass$$function$$2.apply(HealthService_Subclass$$function$$2.zig:35)
reading-comprehension       | io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
reading-comprehension       | com.connor.reading.interceptor.DebugInterceptor.handleAroundInvoke(DebugInterceptor.java:32)
reading-comprehension       | com.connor.reading.interceptor.DebugInterceptor_Bean.intercept(DebugInterceptor_Bean.zig:385)
reading-comprehension       | io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
reading-comprehension       | io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
reading-comprehension       | io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
reading-comprehension       | com.connor.reading.service.HealthService_Subclass.runHealthChecksInParallel(HealthService_Subclass.zig:571)
reading-comprehension       | com.connor.reading.service.HealthService.checkDownstreamDependencies(HealthService.java:38)
reading-comprehension       | com.connor.reading.service.HealthService_Subclass.checkDownstreamDependencies$$superaccessor3(HealthService_Subclass.zig:727)
reading-comprehension       | com.connor.reading.service.HealthService_Subclass$$function$$3.apply(HealthService_Subclass$$function$$3.zig:33)
reading-comprehension       | io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
reading-comprehension       | com.connor.reading.interceptor.DebugInterceptor.handleAroundInvoke(DebugInterceptor.java:32)
reading-comprehension       | com.connor.reading.interceptor.DebugInterceptor_Bean.intercept(DebugInterceptor_Bean.zig:385)
reading-comprehension       | io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
reading-comprehension       | io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
reading-comprehension       | io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
reading-comprehension       | com.connor.reading.service.HealthService_Subclass.checkDownstreamDependencies(HealthService_Subclass.zig:684)
reading-comprehension       | com.connor.reading.controller.HealthApiImpl.readinessCheck(HealthApiImpl.java:32)
reading-comprehension       | com.connor.reading.controller.HealthApiImpl_Subclass.readinessCheck$$superaccessor1(HealthApiImpl_Subclass.zig:223)
reading-comprehension       | com.connor.reading.controller.HealthApiImpl_Subclass$$function$$1.apply(HealthApiImpl_Subclass$$function$$1.zig:33)
reading-comprehension       | io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
reading-comprehension       | com.connor.reading.interceptor.DebugInterceptor.handleAroundInvoke(DebugInterceptor.java:32)
reading-comprehension       | com.connor.reading.interceptor.DebugInterceptor_Bean.intercept(DebugInterceptor_Bean.zig:385)
reading-comprehension       | io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
reading-comprehension       | io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
reading-comprehension       | io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
reading-comprehension       | com.connor.reading.controller.HealthApiImpl_Subclass.readinessCheck(HealthApiImpl_Subclass.zig:180)
reading-comprehension       | java.lang.reflect.Method.invoke(Method.java:566)
reading-comprehension       | org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:167)
reading-comprehension       | org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
reading-comprehension       | org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:638)
reading-comprehension       | org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:504)
reading-comprehension       | org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:454)
reading-comprehension       | org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
reading-comprehension       | org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:456)
reading-comprehension       | org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:417)
reading-comprehension       | org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:391)
reading-comprehension       | org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:68)
reading-comprehension       | org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:488)
reading-comprehension       | org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:259)
reading-comprehension       | org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:160)
reading-comprehension       | org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
reading-comprehension       | org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:163)
reading-comprehension       | org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:245)
reading-comprehension       | io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
reading-comprehension       | io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:132)
reading-comprehension       | io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.access$000(VertxRequestHandler.java:37)
reading-comprehension       | io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:94)
reading-comprehension       | org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
reading-comprehension       | org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2046)
reading-comprehension       | org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1578)
reading-comprehension       | org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
reading-comprehension       | org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
reading-comprehension       | org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
reading-comprehension       | java.lang.Thread.run(Thread.java:834)
reading-comprehension       | org.jboss.threads.JBossThread.run(JBossThread.java:479)
reading-comprehension       | com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:517)
reading-comprehension       | com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:193)
reading-comprehension       | 
reading-comprehension       | 2020-09-16 02:25:57,520 WARN  [com.arj.ats.arjuna] (pool-7-thread-1) ARJUNA012108: CheckedAction::check - atomic action 0:ffffac110002:a243:5f617343:1 aborting with 2 threads active!

为证实我的怀疑,我将线程名称添加到日志中并找到以下内容:

reading-comprehension       | 2020-09-16 02:25:57,516 INFO  [com.con.rea.dao.AutoCloseableUserTransactionImpl] (executor-thread-1) Successfully started injected transaction (likely for health check).  This was done on thread executor-thread-1
//logs omitted for breivy
reading-comprehension       | 2020-09-16 02:25:57,521 INFO  [com.con.rea.dao.AutoCloseableUserTransactionImpl] (pool-7-thread-1) Successfully rolled back transaction (likely for health check).  This was done on thread pool-7-thread-1

其他信息

存储库链接:https://gitlab.com/connorbutch/reading-comprehension/-/tree/9-list-all-assessments确保您正在进行9-list-all-assessments分支

代码流

(就绪)运行状况检查执行如下:

  1. 向{base_url} / health / readiness(即http:// localhost:8080 / health / readiness)发出(GET)请求
  2. 该请求由HealthApiImpl.readinessCheck()(https://gitlab.com/connorbutch/reading-comprehension/-/blob/9-list-all-assessments/reading-comprehension-server-quarkus-impl/src/main/java/com/connor/reading/controller/HealthApiImpl.java)处理
  3. HealthApiImpl调用HealthService.checkDownstreamDependencies(https://gitlab.com/connorbutch/reading-comprehension/-/blob/9-list-all-assessments/reading-comprehension-server-quarkus-impl/src/main/java/com/connor/reading/service/HealthService.java#L36
  4. HealthService最终(通过调用get)检查每个运行状况检查器上每个下游依赖项的运行状况(通过注入HealthChecker的每个具体实现)。 这是在线程池上完成的,以并行执行,我真的很想避免让它顺序运行
  5. 调用给出的特定运行状况检查程序是问题(https://gitlab.com/connorbutch/reading-comprehension/-/blob/9-list-all-assessments/reading-comprehension-server-quarkus-impl/src/main/java/com/connor/reading/health/HealthCheckerInsertAssessmentImpl.java)。呼唤开始交易
  6. 调用工厂类来获取一个可自动关闭的交易,该交易可包装我们的交易(在注入交易时创建),并在其close方法(https://gitlab.com/connorbutch/reading-comprehension/-/blob/9-list-all-assessments/reading-comprehension-server-quarkus-impl/src/main/java/com/connor/reading/dao/AutoCloseableUserTransactionImplFactory.java)中调用回滚
  7. 包装事务的Autocloseable实现被注入。它在构造函数中调用transaction.begin,并在调用close方法时回滚该事务(https://gitlab.com/connorbutch/reading-comprehension/-/blob/9-list-all-assessments/reading-comprehension-server-quarkus-impl/src/main/java/com/connor/reading/dao/AutoCloseableUserTransactionImpl.java

潜在解决方案

我已经想到了一些潜在的解决方案:

  1. 使用threadlocal进行用户事务。但是,这不起作用(至少是一个简短的poc)
  2. 使执行不使用执行程序。我真的很想避免这种解决方案。

能否请您提供最佳解决方案的建议?

0 个答案:

没有答案