likes
comments
collection
share

Spring微服务应用踩过的事务坑本文内容是如何让你了解Transactional如何通过管理数据库事务修复Spring

作者站长头像
站长
· 阅读数 33

Transactional介绍

了解@Transactional如何通过管理数据库事务修复Spring中的连接泄漏,增强应用程序的稳定性和性能。

在微服务中,精确的管理数据库交互对于维护应用程序的性能和可靠性至关重要。Spring微服务应用程序的持久化层中出现了一个关键问题,其中不当的异常处理导致性能测试期间出现意外故障和服务中断。本文深入探讨了该问题的具体细节,并强调了注释的关键作用@Transactional

Spring微服务应用程序严重依赖稳定高效的数据库交互,通常通过Java 持久性 API (JPA)进行管理。正确管理数据库连接(尤其是防止连接泄漏)对于确保这些交互不会对应用程序性能产生负面影响至关重要。

问题背景

在最近一轮性能测试中,我们一项重要的微服务出现了严重问题,该微服务用于发送客户端通信通知。并开始反复出现网关超时错误。问题根源来自于我们在存储库层的数据库操作。

对这些超时错误的调查显示,存储过程一直在失败。失败是由传递给过程的无效参数触发的,这会引发存储过程的业务异常。存储库层无法有效地处理此异常,以下是存储过程调用的源代码:

public long createInboxMessage(String notifCode,String acctId,String userId,String s3KeyName,
        List<Notif> notifList,String attributes,String notifTitle,
        String notifSubject,String notifPreviewText,String contentType,boolean doNotDelete,boolean isLetter,
        String groupId)throws EDeliveryException{
        try{
        StoredProcedureQuery query=entityManager.createStoredProcedureQuery("p_create_notification");
        DbUtility.setParameter(query,"v_notif_code",notifCode);
        DbUtility.setParameter(query,"v_user_uuid",userId);
        DbUtility.setNullParameter(query,"v_user_id",Integer.class);
        DbUtility.setParameter(query,"v_acct_id",acctId);
        DbUtility.setParameter(query,"v_message_url",s3KeyName);
        DbUtility.setParameter(query,"v_ecomm_attributes",attributes);
        DbUtility.setParameter(query,"v_notif_title",notifTitle);
        DbUtility.setParameter(query,"v_notif_subject",notifSubject);
        DbUtility.setParameter(query,"v_notif_preview_text",notifPreviewText);
        DbUtility.setParameter(query,"v_content_type",contentType);
        DbUtility.setParameter(query,"v_do_not_delete",doNotDelete);
        DbUtility.setParameter(query,"v_hard_copy_comm",isLetter);
        DbUtility.setParameter(query,"v_group_id",groupId);
        DbUtility.setOutParameter(query,"v_notif_id",BigInteger.class);

        query.execute();
        BigInteger notifId=(BigInteger)query.getOutputParameterValue("v_notif_id");
        return notifId.longValue();
        }catch(PersistenceException ex){
          logger.error("DbRepository::createInboxMessage - Error creating notification",ex);
          throw new EDeliveryException(ex.getMessage(),ex);
        }
   }

问题分析

如我们的场景所示,当存储过程遇到错误时,产生的异常会从存储库层向上传播到服务层,最后传播到控制器。这种传播是有问题的,导致我们的API响应非 200 HTTP状态代码(通常为 500 或 400)。在发生几次此类事件后,服务容器达到了无法再处理传入请求的程度,最终导致502网关超时错误。这种危急状态反映在我们的监控系统中,在日志中暴露出了此问题: 

 HikariPool-1-连接不可用,请求在 30000 毫秒后超时。

问题在于异常处理不当,因为异常在系统层中冒泡而没有得到妥善管理。这阻止了将数据库连接释放回连接池,从而导致可用连接耗尽。因此,在耗尽所有连接后,容器无法处理新请求,导致日志中报告错误和非 200 HTTP 错误。

解决方案

为了解决这个问题,我们可以优雅地处理异常,而不是进一步冒泡,让 JPA 和 Spring 上下文释放与池的连接。另一种选择是@Transactional对方法使用注释。下面是带有注释的相同方法:

@Transactional
public long createInboxMessage(String notifCode, String acctId, String userId, String s3KeyName,
        List<Notif> notifList, String attributes, String notifTitle,
        String notifSubject, String notifPreviewText, String contentType, boolean doNotDelete, boolean isLetter,
        String groupId) throws EDeliveryException {
        ………
        }

下面方法的实现演示了一种异常处理方法,通过在方法本身内捕获和记录异常来防止异常进一步的传播:

public long createInboxMessage(String notifCode, String acctId, String userId, String s3KeyName,
        List<Notif> notifList, String attributes, String notifTitle,
        String notifSubject, String notifPreviewText, String contentType, boolean doNotDelete, boolean isLetter,
        String loanGroupId) {
        try {
        .......
        query.execute();
        BigInteger notifId = (BigInteger) query.getOutputParameterValue("v_notif_id");
        return notifId.longValue();
        } catch (PersistenceException ex) {
        logger.error("DbRepository::createInboxMessage - Error creating notification", ex);
        }
        return -1;
    }

使用@Transactional

Spring 框架中的注释@Transactional管理事务边界。它在注释方法启动时启动事务,并在方法完成时提交或回滚事务。当发生异常时,@Transactional确保事务回滚,这有助于适当地将数据库连接释放回连接池。

没有使用@Transactional

如果调用存储过程的存储库方法未使用进行注释 ,@Transactional则 Spring不会管理该方法的事务边界。如果存储过程引发异常,则必须手动实现事务处理。如果管理不当,这可能导致数据库连接无法关闭且无法返回到池中,从而导致连接泄漏。

最佳实践

  • @Transactional当方法的操作应在事务范围内执行时,始终使用该方法。这对于涉及可修改数据库状态的存储过程的操作尤其重要。
  • 确保方法内的异常处理包括正确的事务回滚和关闭任何数据库连接,主要是在不使用时@Transactional

问题总结

有效的事务管理对于使用 JPA 维护 Spring 微服务应用程序的健康和性能至关重要。通过使用注释@Transactional,我们可以防止连接泄漏并确保数据库交互不会降低应用程序的性能或稳定性。遵守这些准则可以提高我们的 Spring 微服务的可靠性和效率,为消费应用程序或最终用户提供稳定且响应迅速的服务。

转载自:https://juejin.cn/post/7385904495384231972
评论
请登录