Java 异步EJB应用程序中Hibernate的奇怪行为。比赛条件?

Java 异步EJB应用程序中Hibernate的奇怪行为。比赛条件?,java,hibernate,jakarta-ee,jpa,concurrency,Java,Hibernate,Jakarta Ee,Jpa,Concurrency,问题的简要说明: 我正在开发JavaEE应用程序来处理来自金融市场的消息。应用程序部署在应用程序服务器上:Wildfly-8.2.0.Final 应用程序中的消息流如下图所示: MDB1 \ StrategyManager(@Singleton) -> StrategyRunner(@Singleton) -> SomeStrategy(@Singleton) / MDB2 异步调用的EJB-SomeStrategy(@Singleton)正在对定义

问题的简要说明:

我正在开发JavaEE应用程序来处理来自金融市场的消息。应用程序部署在应用程序服务器上:Wildfly-8.2.0.Final

应用程序中的消息流如下图所示:

MDB1
     \
      StrategyManager(@Singleton) -> StrategyRunner(@Singleton) -> SomeStrategy(@Singleton)
     /
MDB2
异步调用的EJB-SomeStrategy(@Singleton)正在对定义为JPA实体的JPA模型执行读取更新操作: StrategyEntity- StrategyManagerStrategyRunner是辅助EJB,即数据库操作与SomeStrategy(@Singleton)中包含的主要业务逻辑分离

StrategyEntity在SomeStrategySingletonEJB执行读取|更新操作之前刷新,并在读取|更新操作之后保存

@Slf4j
@Singleton
@Local
@Startup
public class StrategyRunner {

    @EJB
    private SomeStrategySingletonEJB someStrategySingletonEJB;

    @EJB
    private StrategyDao strategyDao;


    public void runStrategy(StrategyEntity strategyEntity, OrderBookAggregated orderBookAggregated) {
        strategyDao.refresh(strategyEntity);
        log.info("StrategyEntity after refresh: {}", startegyEntity);
        if (!strategyEntity.isRunning()) return;

        someStrategySingletonEJB.updateOnOrderBook(strategyEntity);
        log.info("StrategyEntity before save: {}", startegyEntity);
        strategyDao.save(strategyEntity);

    }

    public void runStrategy(StrategyEntity strategyEntity, OrderQueryReport orderQueryReport) {
        strategyDao.refresh(strategyEntity);
        log.info("StrategyEntity after refresh: {}", startegyEntity);
        if (!strategyEntity.isRunning()) return;
        someStrategySingletonEJB.updateOnExecutionReport(strategyEntity, orderQueryReport);
        log.info("StrategyEntity before save: {}", startsingletoneegyEntity);
        strategyDao.save(strategyEntity);
    }

}
方法runStrategy()是从MDB并发调用的。Singleton的默认锁类型是写锁,因此方法应该 不要同时运行。StrategyEntity是从上层数据库(StrategyManager)中检索的,请参见详细说明。 问题是:有时(非常落后-平均2000条消息中有一条)“保存前策略实体”不同 “刷新后的战略实体”它看起来像某种比赛状态。如果我替换strategyDao(访问RDBMS) 通过将简单缓存实现为EJB@Singleton,问题就消失了。我推断出这个问题 将数据库层替换为缓存后,作为应用程序的Hibernate操作可以在繁重的工作负载下完美工作。你有什么想法吗

休眠属性:

<persistence-unit name="AlgorithmEnginePU">
    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
    <jta-data-source>java:jboss/AlgorithmEngineDS</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <!--<class>com.main.model.configuration.ExchangeInformation</class>-->
    <properties>

        <!-- Properties for Hibernate -->
        <property name="hibernate.default_schema" value="algorithm_engine"/>
        <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
        <property name="hibernate.show_sql" value="false"/>
        <property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform"/>
        <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
    </properties>
</persistence-unit>
第二个MDB接收其他类型的消息,并将它们排队,以便在注入的单例上处理它们 遵循默认写入锁定:

@Slf4j
@MessageDriven(name = "MessageReceiver", activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "jms/tradeAgentReply"),
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"),
        @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge")})
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class TradeAgentClientMDB implements MessageListener {

    @EJB
    private StrategyManager strategyManager;

    @Override
    public void  onMessage(Message message) {
        (...)

        try {

            TradeQueryReport tradeQueryReport = (TradeQueryReport) serializableMessage;

           if (tradeQueryReport instanceof OrderQueryReport ) {
                OrderQueryReport orderQueryReport = (OrderQueryReport) tradeQueryReport;
                strategyManager.updateOrder(orderQueryReport);
            }

        } catch (JMSException e) {
            e.printStackTrace();
        }

    }
}
以下是EJB(StrategyRunner@EJB在简要描述部分中描述):


代码本身看起来很好,我找不到任何明显的缺陷。我记得不久前我自己也遇到过类似的情况,日志输出与数据库中的内容不匹配。以下是我的情况:

  • 日志语句。这并没有立即记录下来;而是收集数据并将日志事件添加到队列中。这意味着日志事件具有对对象的硬引用
  • 插入/更新数据库
  • 交易结束
  • 第二个线程从缓存中获取对象
  • 对象被修改
  • 另一个线程开始处理日志事件
  • 日志事件已转换为字符串
由于在最后一步之前修改了对象,因此日志输出是意外的:事务结束后的更改“泄漏”到日志中

解决方案:确保记录器只获取不可变数据。一个简单的解决方法是

log.info("StrategyEntity after refresh: {}", startegyEntity.toString());

在您的
OrderBookReceiver
中,您从未使用过
orderBookEntity
。相反,有一个新变量
orderBookAggregated
。打字错误?问题:你怎么知道
StrategyRunner
是单身汉?也要确保锁是真正的单体锁-只是为了安全。要确保日志条目不是来自不同的线程,请将线程ID添加到日志文件中。最后,你是同步登录的吗?是的,这是一个输入错误。我选对了,谢谢!我不知道我是否正确理解你的问题。。。StrategyRunner是一个单例,因为它是用EJB@Singleton注释的。锁也是单例的,默认锁类型更改为READ(我还附上了它的代码-请参阅StrategyOrderBookLock)。出于测试目的,系统中只有一个strategyEntity,因此来自MDB的每条消息都会修改同一个实体。日志条目来自不同的线程,因为单例调用由来自Wildfly线程池的不同线程提供服务。在Spring中,配置可以相互覆盖。如果EJB可以做到这一点,那么您应该检查bean是否定义了两次,可能是不同的作用域。非常感谢您提供的线索,但主要问题不是日志与数据库中实体的状态不同。主要的问题是,有时(很少)应用程序从数据库中读取旧状态,其逻辑流从此点开始损坏。作为一种解决方法(对ORM的某些错误进行推理),我用简单的单例缓存替换了数据库。在这个变通应用程序工作正常之后(所有实体都存储在单例缓存中,而不是存储在数据库中)。目前我正在考虑用EclipseLink替换Hibernate(作为Wildfly中的默认ORM),并测试这个问题是否是由ORM引起的。
@Slf4j
@Singleton
@Local
@Startup
public class StrategyManager {

    @Inject
    private StrategyDao strategyDao;

    @EJB
    private StrategyRunner strategyRunner;

    @Asynchronous
    public void updateOrderBook(OrderBookAggregated orderBookAggregated) {

        StrategyEntity strategy =  strategyDao.findStrategyByExchange(orderBookAggregated.getAccount().getExchangeEntity());

        if(strategyEntity == null) return;

        strategyRunner.runStrategy(strategyEntity, orderBookAggregated);
    }

    @Asynchronous
    public void updateOrder(OrderQueryReport orderQueryReport) {

        StrategyEntity strategy = strategyDao.findStrategyByOrder(orderQueryReport.getClientOrderId());

        if(strategy == null) return;

        strategyRunner.runStrategy(strategy, orderQueryReport);
    }

    (...)
}

@Singleton
@Local
@Startup
public class StrategyOrderBookLock {
    private java.util.concurrent.locks.Lock updateOrderBookLock = new ReentrantLock();


    @Lock(LockType.READ)
    public boolean tryLock() {
        return updateOrderBookLock.tryLock();
    }

    @Lock(LockType.READ)
    public void unlock() {
        updateOrderBookLock.unlock();
    }
}
log.info("StrategyEntity after refresh: {}", startegyEntity.toString());