0.背景
前段时间,其他组同事找到我,说让我协助看一个事务未回滚问题,刚开始以为是事务隔离设置方面的原因,结果原因很简单,因为要给接口添加超时中断的功能,他根据网上文章使用java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)
来实现超时中断功能,结果超时功能实现了,但是超时抛出异常后接口事务没有回滚,我看了下代码,主要原因是方法中使用的是声明式事务,对于新引入的异步任务来说事务管理粒度太粗糙,且捕获超时异常后,任务未手动取消。下意识准备调整为编程式事务来使事务管理细化,结果刚要下手时想到不是有现成的轮子嘛,下面就该轮子——guava的TimeLimiter
进行简要介绍。
1.使用场景描述
网上找了段话描述接口超时中断的需求场景:
- 如果调用方法超过1秒,就应该停止调用,不要一直阻塞下去,防止把本身的服务资源搞挂。
- 在不可预知可能出现死锁/死循环的代码,要加上时间的阀值,避免阻塞。
2.TimeLimiter实现接口超时中断
guava工具包里面包含了超时的控制方法。即TimeLimiter
接口,其有两个实现类。
-
FakeTimeLimiter
, 常用于debug调试时,限制时间超时调试。 -
SimpleTimeLimiter
常用于正式方法中,调用方法超时,即抛出异常。
我们在实际开发中也是使用SimpleTimeLimiter
来实现超时控制功能,其有两种实现模式:代理模式,回调模式。实现很简单,看代码即可了解。
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
对象实现了超时中断控制。
结合代理模式调用超时链路即很清晰的展示超时中断控制实现,此时接口仍旧使用声明式事务,超时后事务正常回滚。