All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.phantomthief.scope.ScopeAsyncRetry Maven / Gradle / Ivy

package com.github.phantomthief.scope;

import static com.github.phantomthief.scope.Scope.getCurrentScope;
import static com.github.phantomthief.scope.Scope.supplyWithExistScope;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.util.concurrent.Futures.addCallback;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator;
import static java.lang.Thread.MAX_PRIORITY;
import static java.util.concurrent.Executors.newFixedThreadPool;
import static java.util.concurrent.Executors.newScheduledThreadPool;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;

import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.github.phantomthief.util.ThrowableSupplier;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

/**
 * 支持 {@link Scope} 级联,并且支持单次调用独立设置超时的异步重试封装
 * 

* 使用方法: *

{@code
 *
 * class MyClass {
 *
 *   private final ScopeAsyncRetry retrier = ScopeAsyncRetry.shared();
 *   private final ListeningExecutorService executor = listeningDecorator(newFixedThreadPool(10);
 *
 *   ListenableFuture<String> asyncCall() {
 *     return executor.submit(() -> {
 *       sleepUninterruptibly(ThreadLocalRandom.current().nextLong(150L), MILLISECONDS);
 *       return "myTest"
 *     });
 *   }
 *
 *   void foo() throws ExecutionException, TimeoutException {
 *     ListenableFuture<String> future = retrier.callWithRetry(100, retryNTimes(3), () -> asyncCall());
 *     String unwrapped = getUninterruptibly(future, 200, MILLISECONDS);
 *     System.out.println("result is:" + unwrapped);
 *   }
 * }
 *
 * }
 * 
*

* 注意: 如果最终外部的ListenableFuture.get(timeout)没有超时,但是内部请求都失败了,则上抛 * 会上抛 {@link java.util.concurrent.ExecutionException} 并包含最后一次重试的结果 * 特别的,如果最后一次请求超时 {@link java.util.concurrent.ExecutionException#getCause()} 为 {@link TimeoutException} *

* 注意: 需要重试的方法应该是幂等的操作,不应有任何副作用。 * * @author myco * Created on 2019-01-20 */ public class ScopeAsyncRetry { private final ListeningScheduledExecutorService scheduler; private final Executor callbackExecutor; /** * 因为使用 directExecutor 执行 callback 操作,导致 callback 任务占用 ScheduledExecutorService, * 从而导致超时控制的有效性可能会随着负载提高而急剧下降 * 请使用 {@link ScopeAsyncRetry#createScopeAsyncRetry(ScheduledExecutorService, Executor)} */ @Deprecated public static ScopeAsyncRetry createScopeAsyncRetry(@Nonnegative ScheduledExecutorService executor) { return new ScopeAsyncRetry(executor); } public static ScopeAsyncRetry createScopeAsyncRetry(@Nonnegative ScheduledExecutorService executor, Executor callbackExecutor) { return new ScopeAsyncRetry(executor, callbackExecutor); } /** * 共享的 ScopeAsyncRetry 实例 *

* 建议不同业务使用不同的实例,因为其中通过 ScheduledExecutorService 来检测超时 和 实现间隔重试 * 大量使用共享实例,这里可能成为瓶颈 */ public static ScopeAsyncRetry shared() { return LazyHolder.INSTANCE; } @Deprecated ScopeAsyncRetry(ScheduledExecutorService scheduler) { this(scheduler, directExecutor()); } ScopeAsyncRetry(ScheduledExecutorService scheduler, Executor callbackExecutor) { this.scheduler = listeningDecorator(scheduler); this.callbackExecutor = callbackExecutor; } /** * 内部工具方法,将future结果代理到另一个SettableFuture上 */ private static FutureCallback setAllResultToOtherSettableFuture(SettableFuture target) { return new FutureCallback() { @Override public void onSuccess(@Nullable T result) { target.set(result); } @Override public void onFailure(Throwable t) { target.setException(t); } }; } private static FutureCallback cancelOtherFuture(Future target, boolean mayInterruptIfRunning) { return new FutureCallback() { @Override public void onSuccess(@Nullable T result) { target.cancel(mayInterruptIfRunning); } @Override public void onFailure(Throwable t) { target.cancel(mayInterruptIfRunning); } }; } private static FutureCallback setSuccessResultToOtherSettableFuture(SettableFuture target) { return new FutureCallback() { @Override public void onSuccess(@Nullable T result) { target.set(result); } @Override public void onFailure(Throwable t) { } }; } private static void addCallbackWithDirectExecutor(ListenableFuture future, FutureCallback callback) { addCallback(future, callback, directExecutor()); } private void addCallbackWithCallbackExecutor(ListenableFuture future, FutureCallback callback) { addCallback(future, callback, callbackExecutor); } private static class RetryConfig { private final long retryInterval; private final boolean hedge; private final boolean triggerGetOnTimeout; private RetryConfig(long retryInterval, boolean hedge, boolean triggerGetOnTimeout) { this.retryInterval = retryInterval; this.hedge = hedge; this.triggerGetOnTimeout = triggerGetOnTimeout; } } /** * 带重试的调用 * * @param singleCallTimeoutMs 单次调用超时限制,单位:ms * @param func 需要重试的调用 * @return 带重试的future */ @Nonnull public ListenableFuture callWithRetry(long singleCallTimeoutMs, RetryPolicy retryPolicy, @Nonnull ThrowableSupplier, X> func) { return callWithRetry(singleCallTimeoutMs, retryPolicy, func, null); } @Nonnull public ListenableFuture callWithRetry(long singleCallTimeoutMs, RetryPolicy retryPolicy, @Nonnull ThrowableSupplier, X> func, @Nullable FutureCallback eachRetryCallback) { checkNotNull(retryPolicy); checkNotNull(func); checkArgument(singleCallTimeoutMs > 0); // 用来保存最终的结果 SettableFuture resultFuture = SettableFuture.create(); AtomicInteger retryTime = new AtomicInteger(0); Supplier retryConfigSupplier = () -> new RetryConfig( retryPolicy.retry(retryTime.incrementAndGet()), retryPolicy.hedge(), retryPolicy.triggerGetOnTimeout()); Scope scope = getCurrentScope(); ThrowableSupplier, X> scopeWrappedFunc = () -> supplyWithExistScope( scope, func); return callWithRetry(scopeWrappedFunc, singleCallTimeoutMs, retryConfigSupplier, resultFuture, eachRetryCallback); } /** * 内部递归方法,返回值是最终的挂了多个retry callback的future */ private SettableFuture callWithRetry( @Nonnull ThrowableSupplier, X> func, long singleCallTimeoutMs, Supplier retryConfigSupplier, SettableFuture resultFuture, FutureCallback eachRetryCallback) { // 如果外部主动 cancel 了,那就不用再做后边没完成的 retry 了 if (resultFuture.isDone()) { return resultFuture; } RetryConfig retryConfig = retryConfigSupplier.get(); // 开始当前一次调用尝试 final SettableFuture currentTry = SettableFuture.create(); if (eachRetryCallback != null) { addCallback(currentTry, eachRetryCallback, callbackExecutor); } AtomicBoolean currentTrySetted = new AtomicBoolean(false); RefHolder> callingFuture = new RefHolder<>(); try { callingFuture.set(func.get()); addCallbackWithDirectExecutor(callingFuture.get(), new FutureCallback() { @Override public void onSuccess(@Nullable T result) { if (currentTrySetted.compareAndSet(false, true)) { currentTry.set(result); } } @Override public void onFailure(Throwable t) { if (currentTrySetted.compareAndSet(false, true)) { currentTry.setException(t); } } }); } catch (Throwable t) { currentTry.setException(t); } if (callingFuture.get() != null) { // 看是先超时还是先执行完成或者执行抛异常 scheduler.schedule(() -> { if (retryConfig.triggerGetOnTimeout) { if (currentTrySetted.compareAndSet(false, true)) { try { // 这里get一下是为了触发一些 listener,例子参考 ScopeAsyncRetryTest.testTimeoutListenableFuture T result = callingFuture.get().get(0, NANOSECONDS); // 如果这会儿成功了还是把结果 set 给 currentTry currentTry.set(result); } catch (Throwable t) { currentTry.setException(t); } } } else { currentTry.setException(new TimeoutException()); } if (!retryConfig.hedge) { // 普通模式下,这次重试超时就把这次的future cancel掉 callingFuture.get().cancel(false); } else { // hedge模式下,这次重试等到最终结果确定下来之后再cancel addCallbackWithDirectExecutor(resultFuture, cancelOtherFuture(callingFuture.get(), false)); } }, singleCallTimeoutMs, MILLISECONDS); } if (retryConfig.hedge && callingFuture.get() != null) { // hedge模式下,不cancel之前的尝试,之前的调用一旦成功就set到最终结果里 addCallbackWithDirectExecutor(callingFuture.get(), setSuccessResultToOtherSettableFuture(resultFuture)); } if (retryConfig.retryInterval < 0) { // 如果不会再重试了,那就不管什么结果都set到最终结果里吧 addCallbackWithCallbackExecutor(currentTry, setAllResultToOtherSettableFuture(resultFuture)); } else { // 本次尝试如果成功,直接给最终结果set上;超时或者异常的话,后边的重试操作都挂在catching里 addCallbackWithCallbackExecutor(currentTry, setSuccessResultToOtherSettableFuture(resultFuture)); } // hedge模式下,resultFuture可能被之前的调用成功set值,所里这里不仅检查是否需要重试,也检查下是否已经取到了最终结果 if (!resultFuture.isDone() && retryConfig.retryInterval >= 0) { // 没拿到最终结果,且重试次数还没用完,那我们接着加重试callback addCallbackWithCallbackExecutor(currentTry, new FutureCallback() { @Override public void onSuccess(@Nullable T result) { // 只有失败了才需要再进行重试,所以这里就啥也不干了 } @Override public void onFailure(Throwable t) { // 不管之前是超时还是执行失败了,只要最终结果没拿到,且重试次数还没用完,就会到这里来 if (retryConfig.retryInterval > 0) { // 延迟一会儿再重试 scheduler.schedule(() -> { callWithRetry(func, singleCallTimeoutMs, retryConfigSupplier, resultFuture, eachRetryCallback); }, retryConfig.retryInterval, MILLISECONDS); } else { // 直接重试 callWithRetry(func, singleCallTimeoutMs, retryConfigSupplier, resultFuture, eachRetryCallback); } } }); } return resultFuture; } private static class RefHolder { private R r; public void set(R ref) { this.r = ref; } public R get() { return r; } } private static final class LazyHolder { private static final ScopeAsyncRetry INSTANCE = createScopeAsyncRetry( newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder() .setPriority(MAX_PRIORITY) .setNameFormat("default-retrier-%d") .build()), newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2, new ThreadFactoryBuilder() .setPriority(MAX_PRIORITY) .setNameFormat("default-callback-%d") .build())); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy