最近, Spring 微服务应用程序的存储库层出现了一个关键问题,异常处理不当导致性能测试期间出现意外故障和服务中断。
本文深入探讨了该问题的具体情况,并强调了注释的关键作用@Transactional,它解决了该问题。
Spring 微服务应用程序严重依赖稳定高效的数据库交互,通常通过Java Persistence API (JPA)进行管理。正确管理数据库连接,特别是防止连接泄漏,对于确保这些交互不会对应用程序性能产生负面影响至关重要。
问题背景
在最近一轮的性能测试中,我们的一个基本微服务中出现了一个关键问题,该微服务 被指定 用于发送客户端通信。该服务开始出现重复的网关超时错误。根本问题根源在于我们在存储库层的数据库操作。
对这些超时错误的调查表明存储过程始终失败。失败 是 由传递给过程的无效参数触发的,这引发了存储过程的业务异常。存储库层没有有效地处理这个异常;它冒泡了。下面是存储过程调用的源代码:
public long createInboxMessage(String notifCode, String acctId, String userId, String s3KeyName, |
问题分析
正如我们的场景所示,当存储过程遇到错误时,产生的异常将从存储库层向上传播到服务层,最后传播到控制器。这种传播是有问题的,导致我们的 API 以非 200 HTTP 状态代码进行响应 - 通常为 500 或 400。在发生几次此类事件后,服务容器达到了无法再处理传入请求的程度,最终导致 502 网关超时错误。这种严重状态反映在我们的监控系统中,Kibana 日志表明了该问题:
“HikariPool-1 - 连接不可用,请求在 30000 毫秒后超时。”
问题在于异常处理不当,因为异常在系统层中不断涌现,而没有得到适当的管理。这会阻止将数据库连接释放回连接池,从而导致可用连接耗尽。因此,在耗尽所有连接后,容器无法处理新请求,导致Kibana 日志中报告错误和非 200 HTTP 错误。
解决
为了解决这个问题,我们可以优雅地处理异常:
- 让 JPA 和 Spring 上下文释放池的连接。
- 另一种选择是@Transactional对方法使用注释。
下面是带有注释的相同方法:
@Transactional |
下面方法的实现演示了一种异常处理方法,通过在方法本身中捕获和记录异常,防止异常进一步向堆栈上层传播:
public long createInboxMessage(String notifCode, String acctId, String userId, String s3KeyName, |
使用@Transactional
@TransactionalSpring 框架中的注释管理事务边界。它在带注释的方法启动时开始事务,并在方法完成时提交或回滚事务。当发生异常时,@Transactional确保事务回滚,这有助于适当地将数据库连接释放回连接池。
如果没有@Transactional
如果调用存储过程的存储库方法 没有使用@Transactional 注释 ,Spring 不会管理该方法的事务边界。 如果存储过程抛出异常,则必须手动实现事务处理。如果管理不当,可能会导致数据库连接未关闭且未返回到池中,从而导致连接泄漏。
最佳实践
- 当方法的操作应在事务范围内执行时,应始终使用 @Transactional。这对于涉及存储过程(可修改数据库状态)的操作尤为重要。
- 确保方法中的异常处理包括适当的事务回滚和关闭任何数据库连接,主要是在不使用 @Transactional 时。
结论
有效的事务管理对于维护使用 JPA 的 Spring 微服务应用程序的健康和性能至关重要。通过使用 @Transactional 注解,我们可以防止连接泄漏,并确保数据库交互不会降低应用程序的性能或稳定性。遵守这些准则可以提高 Spring 微服务的可靠性和效率,为消费应用程序或终端用户提供稳定、响应迅速的服务。