【问题标题】:Java-EE database connection pool runs out of maxJava-EE 数据库连接池用完最大值
【发布时间】:2017-08-08 05:35:46
【问题描述】:

我有一个默认的standalone.xml 配置,其中在数据库连接池中最多有20 个连接同时处于活动状态。有充分的理由,我猜。我们运行一个 Oracle 数据库。

存在合理数量的数据库流量,因为存在第三方 API 流量,例如我正在开发的企业应用程序中的 SOAP 和 HTTP 调用。

我们经常这样做:

@PersistenceContext(unitName = "some-pu")
private EntityManager em;

public void someBusinessMethod() {
    someEntity = em.findSomeEntity();
    soap.callEndPoint(someEntity.getSomeProperty()); // may take up to 1 minute
    em.update(someEntity);
    cdiEvent.fire(finishedBusinessEvent);
}

但是,在这种情况下,在获取实体时获取数据库连接,并在更新后释放(实际上是在整个事务完成时)。关于事务,一切都是容器管理的,没有额外的注释。我知道您不应该“保持”数据库连接超过必要的时间,这正是我要解决的问题。一方面,我不知道如何以编程方式释放连接,我也不认为这是一个好主意,因为您仍然希望能够回滚整个事务。

所以?如何解决这个问题?我尝试了多种选择:

选项 1,使用 ManagedExecutorService:

@Resource
private ManagedExecutorService mes;

public void someBusinessMethod() {
    someEntity = em.findSomeEntity();

    this.mes.submit(() -> {
        soap.callEndPoint(someEntity.getSomeProperty()); // may take up to 1 minute
        em.update(someEntity);
        cdiEvent.fire(finishedBusinessEvent);
    });
}

选项 2,使用 @Asynchronous:

@Inject
private AsyncBean asyncBean;

public void someBusinessMethod() {
    someEntity = em.findSomeEntity();
    this.asyncBean.process(someEntity);
}

public class AsyncBean {

    @Asynchronous
    public void process() {
        soap.callEndPoint(someEntity.getSomeProperty()); // may take up to 1 minute
        em.update(someEntity);
        cdiEvent.fire(finishedBusinessEvent);
    }

}

这实际上解决了数据库连接池问题,例如soap.callEndPoint 发生后立即释放连接。但感觉不是很稳定(无法确定这里的问题)。当然,一旦你进入异步处理,事务就完成了,所以只要在soap调用过程中出现问题,就没有任何回滚。

结束... 我即将将长时间运行的 IO 任务(soap 和 http 调用)移动到通过队列卸载的应用程序的单独部分,并再次通过队列将结果反馈到应用程序中。在这种情况下,一切都是通过事务完成的,并且没有连接被阻止。但这是很多开销,因此在这样做之前,我想听听您的意见/如何解决这个问题的最佳实践!

【问题讨论】:

  • 有多少并发 Web 请求在正常和高峰期执行?
  • 大约 50 到 100,峰值约为 200。Wildfly 配置为在从池中获取连接失败之前等待大约 30 秒。没有重试。
  • 处理同一实体的并发更改的策略是什么?

标签: database asynchronous jakarta-ee transactions


【解决方案1】:

您的队列解决方案是可行的,但如果您只在调用之前执行读取操作,则可能没有必要,您可以使用 DAO 模式将事务拆分为 2 个事务(就像您对队列所做的那样)。

例子:

@Stateless
private DaoBean dao;

@TransactionAttribute(TransactionAttributeType.NEVER)
public void someBusinessMethod() {
    Entity e = dao.getEntity(); // creates and discards TX
    e = soap.callEndPoint(e.getSomeProperty());
    dao.update(e); // creates TX 2 and commits
}

此解决方案有一些注意事项。

  • 当事务已经处于活动状态时,不能调用上述业务方法,因为它会否定 DAO 的目的(一个 TX 因 NOT_SUPPORTED 而暂停)。
  • 您必须处理或忽略在肥皂调用期间实体上可能发生的变化 (@Version ...)。
  • 实体将在业务方法中分离,因此您必须在soap调用中预先加载所需的所有内容。

我无法告诉您这是否适合您,因为这取决于商务电话之前所做的工作。虽然仍然很复杂,但它比排队更容易。

【讨论】:

  • 我确实立即输入了一笔交易,所以我怀疑您有目的的解决方案是否真的有效:-(。dao.getEntity() 上的@Transactional(Transactional.TxType.REQUIRES_NEW) 是否可以帮助将其放入自己的交易中并丢弃一次它完成了从数据库中获取它(脏读)?因此在进行soap调用时没有保持连接。不过,我需要为可能的冲突实现@Version..
【解决方案2】:

您使用选项 2 走在了正确的轨道上,它只需要更多的分解,以使事务管理以一种非常短的方式进行。

由于您有一个可能长时间运行的 Web 服务调用,您肯定需要在两个单独的事务中执行数据库更新:

  1. 短查找操作
  2. 长时间的网络服务调用
  3. 短更新操作

这可以通过引入第三个 EJB 来实现,如下所示:

入口点

@Stateless
public class MyService {

    @Inject
    private AsyncService asyncService;

    @PersistenceContext
    private EntityManager em;

    /*
     * Short lived method call returns promptly
     * (unless you need a fancy multi join query)
     * It will execute in a short REQUIRED transaction by default
     */
    public void someBusinessMethod(long entityId) {
        SomeEntity someEntity = em.find(SomeEntity.class, entityId);
        asyncService.process(someEntity);
    }

}

处理网络服务调用

@Stateless
public class AsyncService {

    @Inject
    private BusinessCompletionService businessCompletionService;

    @Inject
    private SomeSoapService soap;

    /*
     * Long lived method call with no transaction.
     *
     * Asynchronous methods are effectively run as REQUIRES_NEW
     * unless it is disabled.
     * This should avoid transaction timeout problems.
     */
    @Asynchronous
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void process(SomeEntity someEntity) {
        soap.callEndPoint(someEntity.getSomeProperty()); // may take up to 1 minute
        businessCompletionService.handleBusinessProcessCompletion(someEntity);
    }
}

完成

@Stateless
public class BusinessCompletionService {

    @PersistenceContext
    private EntityManager em;

    @Inject
    @Any
    private Event<BusinessFinished> businessFinishedEvent;

    /*
     * Short lived method call returns promptly.
     * It defaults to REQUIRED, but will in effect get a new transaction
     * for this scenario.
     */
    public void handleBusinessProcessCompletion(SomeEntity someEntity) {
        someEntity.setSomething(SOMETHING);
        someEntity = em.merge(someEntity);
        // you may have to deal with optimistic locking exceptions...
        businessFinishedEvent.fire(new BusinessFinished(someEntity));
    }

}

我怀疑您可能仍需要对连接池进行一些调整以有效应对峰值负载。监控应该可以解决这个问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-02
    • 2011-12-31
    • 1970-01-01
    • 2019-02-24
    • 2020-04-01
    • 2014-10-19
    • 2020-12-29
    • 2014-12-20
    相关资源
    最近更新 更多