You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在模式匹配中实现针对特定异常的3次重试逻辑?

Scala实现外部API调用的重试逻辑方案

问题描述

我有一个调用外部API的函数,当前通过模式匹配处理determinationService.getTax方法的返回结果。当捕获到TimeoutException等特定异常时,希望实现最多3次的重试逻辑,现有代码及单元测试代码如下:

现有业务代码:

determinationService.getTax(getRequest, runID) match {
            case Success(getResponse) =>
                LOGGER.info("Following response received from determination service:")
                LOGGER.info(getTaxResponse.toString)
                    ...
            case Failure(exception) =>
                LOGGER.error(
                    "AppName:{} RunID:{} Grpc error when calling Determination service: {}",
                    Array(LoggingConstant.APP_NAME, runID, exception): _*
                )
                //do retry here
}

单元测试代码:

val determinationServiceMock = mock[DeterminationService]
        doReturn(Failure(new TimeOutException())).when(determinationServiceMock).getTax(any(), any())

实现方案

方案一:手动实现重试逻辑

无需引入第三方依赖,封装通用重试函数,完全控制重试逻辑:

import scala.util.{Failure, Success, Try}

// 通用重试函数:指定最大重试次数、需重试的异常类型,接收待执行的Try操作
def retry[T](maxAttempts: Int, retryOn: Class[_ <: Throwable]*)(fn: => Try[T]): Try[T] = {
  def attempt(currentAttempt: Int): Try[T] = {
    fn match {
      case success @ Success(_) => success
      case Failure(e) if currentAttempt < maxAttempts && retryOn.exists(_.isInstance(e)) =>
        LOGGER.warn(
          s"AppName:{} RunID:{} Retrying attempt ${currentAttempt + 1} of $maxAttempts due to exception: {}",
          Array(LoggingConstant.APP_NAME, runID, e): _*
        )
        attempt(currentAttempt + 1)
      case failure => failure
    }
  }
  attempt(1)
}

修改原有业务代码,调用重试函数:

retry(maxAttempts = 3, classOf[TimeoutException])(determinationService.getTax(getRequest, runID)) match {
  case Success(getResponse) =>
    LOGGER.info("Following response received from determination service:")
    LOGGER.info(getResponse.toString)
    // 后续业务处理逻辑
  case Failure(exception) =>
    LOGGER.error(
      "AppName:{} RunID:{} Final failure when calling Determination service after 3 retries: {}",
      Array(LoggingConstant.APP_NAME, runID, exception): _*
    )
    // 最终失败处理逻辑(比如返回错误响应、抛出异常等)
}

方案二:使用第三方重试库(scala-retry)

如果需要更灵活的重试策略(比如指数退避、固定间隔等待),可以使用scala-retry库简化开发:

  1. 添加依赖(以sbt为例):
libraryDependencies += "com.github.cb372" %% "scala-retry" % "0.5.0"
  1. 实现重试逻辑:
import com.github.cb372.retry.RetryPolicy
import com.github.cb372.retry.util.Retrying

// 定义重试策略:最多3次重试,仅对TimeoutException生效
val retryPolicy = RetryPolicy.limitRetries(3) onlyIf {
  case _: TimeoutException => true
  case _ => false
}

// 执行带重试的API调用
val result = Retrying.retry(retryPolicy) {
  determinationService.getTax(getRequest, runID)
}

// 处理最终结果
result match {
  case Success(getResponse) =>
    LOGGER.info("Following response received from determination service:")
    LOGGER.info(getResponse.toString)
    // 后续业务处理逻辑
  case Failure(exception) =>
    LOGGER.error(
      "AppName:{} RunID:{} Final failure when calling Determination service after 3 retries: {}",
      Array(LoggingConstant.APP_NAME, runID, exception): _*
    )
    // 最终失败处理逻辑
}

单元测试调整

为了验证重试逻辑生效,需要调整mock的返回行为,并验证方法调用次数:

测试重试成功场景(前两次失败,第三次成功)

val determinationServiceMock = mock[DeterminationService]
// 前两次返回TimeoutException,第三次返回成功响应
doReturn(Failure(new TimeoutException()))
  .doReturn(Failure(new TimeoutException()))
  .doReturn(Success(mock[GetTaxResponse])) // 替换为实际的返回类型
  .when(determinationServiceMock).getTax(any(), any())

// 执行待测试的业务代码
// ...

// 验证getTax方法被调用3次
verify(determinationServiceMock, times(3)).getTax(any(), any())

测试最终失败场景(3次都失败)

val determinationServiceMock = mock[DeterminationService]
// 每次调用都返回TimeoutException
doReturn(Failure(new TimeoutException()))
  .when(determinationServiceMock).getTax(any(), any())

// 执行待测试的业务代码
// ...

// 验证getTax方法被调用3次
verify(determinationServiceMock, times(3)).getTax(any(), any())

内容的提问来源于stack exchange,提问作者Nick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 14:03:21