0.背景

前段时间,其他组同事找到我,说让我协助看一个事务未回滚问题,刚开始以为是事务隔离设置方面的原因,结果原因很简单,因为要给接口添加超时中断的功能,他根据网上文章使用java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)来实现超时中断功能,结果超时功能实现了,但是超时抛出异常后接口事务没有回滚,我看了下代码,主要原因是方法中使用的是声明式事务,对于新引入的异步任务来说事务管理粒度太粗糙,且捕获超时异常后,任务未手动取消。下意识准备调整为编程式事务来使事务管理细化,结果刚要下手时想到不是有现成的轮子嘛,下面就该轮子——guava的TimeLimiter进行简要介绍。

1.使用场景描述

网上找了段话描述接口超时中断的需求场景:

  • 如果调用方法超过1秒,就应该停止调用,不要一直阻塞下去,防止把本身的服务资源搞挂。
  • 在不可预知可能出现死锁/死循环的代码,要加上时间的阀值,避免阻塞。

2.TimeLimiter实现接口超时中断

guava工具包里面包含了超时的控制方法。即TimeLimiter 接口,其有两个实现类。

  • FakeTimeLimiter, 常用于debug调试时,限制时间超时调试。
  • SimpleTimeLimiter 常用于正式方法中,调用方法超时,即抛出异常。

我们在实际开发中也是使用SimpleTimeLimiter来实现超时控制功能,其有两种实现模式:代理模式,回调模式。实现很简单,看代码即可了解。

simpleemail timeout 是120秒吗 timeout limit_ide

3.SimpleTimeLimiter 的源码实现

3.1 代理模式

com.google.common.util.concurrent.SimpleTimeLimiter#newProxy

@Override
  public <T> T newProxy(final T target, Class<T> interfaceType,
      final long timeoutDuration, final TimeUnit timeoutUnit) {
    checkNotNull(target);
    checkNotNull(interfaceType);
    checkNotNull(timeoutUnit);
    checkArgument(timeoutDuration > 0, "bad timeout: %s", timeoutDuration);
    checkArgument(interfaceType.isInterface(),
        "interfaceType must be an interface type");

    final Set<Method> interruptibleMethods
        = findInterruptibleMethods(interfaceType);

    InvocationHandler handler = new InvocationHandler() {
      @Override
      public Object invoke(Object obj, final Method method, final Object[] args)
          throws Throwable {
        Callable<Object> callable = new Callable<Object>() {
          @Override
          public Object call() throws Exception {
            try {
              return method.invoke(target, args);
            } catch (InvocationTargetException e) {
              throwCause(e, false);
              throw new AssertionError("can't get here");
            }
          }
        };
        return callWithTimeout(callable, timeoutDuration, timeoutUnit,
            interruptibleMethods.contains(method));
      }
    };
    return newProxy(interfaceType, handler);
  }

3.2 回调模式

com.google.common.util.concurrent.SimpleTimeLimiter#callWithTimeout

@Override
  public <T> T callWithTimeout(Callable<T> callable, long timeoutDuration,
      TimeUnit timeoutUnit, boolean amInterruptible) throws Exception {
    checkNotNull(callable);
    checkNotNull(timeoutUnit);
    checkArgument(timeoutDuration > 0, "timeout must be positive: %s",
        timeoutDuration);
    Future<T> future = executor.submit(callable);
    try {
      if (amInterruptible) {
        try {
          //实际也是通过Future#get(long, java.util.concurrent.TimeUnit)实现超时
          return future.get(timeoutDuration, timeoutUnit);
        } catch (InterruptedException e) {
          future.cancel(true);
          throw e;
        }
      } else {
        return Uninterruptibles.getUninterruptibly(future, 
            timeoutDuration, timeoutUnit);
      }
    } catch (ExecutionException e) {
      throw throwCause(e, true);
    } catch (TimeoutException e) {
      //超时异常取消任务并抛出UncheckedTimeoutException异常
      future.cancel(true);
      throw new UncheckedTimeoutException(e);
    }
  }

代理模式主要针对类,回调模式可以针对某部分代码,可以看到代理模式也是基于回调模式方法做了层代码封装,超时控制的底层实现还是在SimpleTimeLimiter#callWithTimeout,其基于Future#get(long, java.util.concurrent.TimeUnit)实现超时,因此SimpleTimeLimiter本质上也是使用了JDK中的Future对象实现了超时中断控制。

结合代理模式调用超时链路即很清晰的展示超时中断控制实现,此时接口仍旧使用声明式事务,超时后事务正常回滚。

simpleemail timeout 是120秒吗 timeout limit_java_02