如何在模式匹配中实现针对特定异常的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库简化开发:
- 添加依赖(以sbt为例):
libraryDependencies += "com.github.cb372" %% "scala-retry" % "0.5.0"
- 实现重试逻辑:
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
相关产品推荐
相关产品推荐

