接口超时治理方案

153 阅读3分钟

1. 实现原理

一图胜千言

interface_time_out.png

大致的实现原理就是当请求发生的时候,会往时间轮里面添加一个延时任务,至于延时时长就是任务的超时阈值,当任务在设置的超时阈值内还没执行完成,就会触发延迟任务逻辑。延迟任务逻辑就是会 set 此次请求超时后的默认值。所以我们需要将任务包装成一个 futureTask

前置知识:

  1. 时间轮:Kafka延迟任务时间轮解析 + java版源码 - 掘金 (juejin.cn)
  2. futureTask底层原理和futureTask增强:FutureTask源码解析,以及增强适配 - 掘金 (juejin.cn)

2. 快速入门

 /**
     * 测试异步不超时
     */
    public static void testNormalAsync() {
        System.out.print("开始执行, 时间 : ");
        printCurTime();
        RequestExecutor.doRequestAsync(
                () -> {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return "正常返回结果";
                },
                (res, e) -> {
                    System.out.print("触发回调逻辑, 时间 : ");
                    printCurTime();
                    System.out.println(res);
                },
                2,
                TimeUnit.SECONDS,
                "默认返回结果"
        );
        //开始执行, 时间 : 2024-08-13 00:26:54
        //触发回调逻辑, 时间 : 2024-08-13 00:26:55
        //正常返回结果
    }

    /**
     * 测试异步超时
     */
    public static void testTimeOutAsync() {
        System.out.print("开始执行, 时间 : ");
        printCurTime();
        RequestExecutor.doRequestAsync(
                () -> {
                    // 模拟耗时操作 2s
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return "正常返回结果";
                },
                (res, e) -> {
                    System.out.print("触发回调逻辑, 时间 : ");
                    printCurTime();
                    System.out.println(res);
                },
                1,
                TimeUnit.SECONDS,
                "默认返回结果"
        );
        // 开始执行, 时间 : 2024-08-13 00:27:30
        // 触发回调逻辑, 时间 : 2024-08-13 00:27:31
        // 默认返回结果
    }


    /**
     * 测试同步超时
     */
    public static void testTimeOutSync() {
        System.out.print("开始执行, 时间 : ");
        printCurTime();
        String res = RequestExecutor.doRequestSync(
                () -> {
                    // 模拟耗时操作 2s
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return "正常返回结果";
                },
                1,
                TimeUnit.SECONDS,
                "默认返回结果"
        );
        System.out.print("结果 :" + res + ", 时间 :");
        printCurTime();
        // 开始执行, 时间 : 2024-08-13 00:32:27
        // 结果 :默认返回结果, 时间 :2024-08-13 00:32:28
    }

    /**
     * 测试同步正常
     */
    public static void testNormalSync() {
        System.out.print("开始执行, 时间 : ");
        printCurTime();
        String res = RequestExecutor.doRequestSync(
                () -> {
                    return "正常返回结果";
                },
                2,
                TimeUnit.SECONDS,
                "默认返回结果"
        );
        System.out.print("结果 :" + res + ", 时间 :");
        printCurTime();
        // 开始执行, 时间 : 2024-08-13 00:32:02
        // 结果 :正常返回结果, 时间 :2024-08-13 00:32:02
    }

3. 源码讲解

package com.hdu;

import java.util.concurrent.*;
import java.util.function.BiConsumer;
import java.util.function.Supplier;

/**
 * 请求执行器 , 可以给请求设置超时时间 如果超时未完成 那么返回设置的默认结果
 */
public class RequestExecutor {


    private static final SystemTimer timer = TimerFactory.createSystemTimer(
        "doRequest",
        200,
        TimeUnit.MILLISECONDS,
        5
    );

    private static ExecutorService executorService = Executors.newFixedThreadPool(1, new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "doRequest");
        }
    });

    /**
     * 同步调用
     *
     * @param supplier      执行逻辑
     * @param timeout       超时时间
     * @param timeUnit      超时时间单位
     * @param defaultResult 默认返回结果
     * @param <T>
     * @return
     */
    public static <T> T doRequestSync(Supplier<T> supplier,
                                      long timeout, TimeUnit timeUnit,
                                      T defaultResult) {
        // 1. 包装原始执行逻辑 + 添加回调逻辑
        EnhanceFutureTask<T> enhanceFutureTask = EnhanceFutureTask.of(
            () -> {
                try {
                    return supplier.get();
                } catch (Exception e) {
                    return defaultResult;
                }
            }
        );
        // 2. 提交延迟任务到时间轮, 超时返回默认返回结果
        TimerTask timerTask = new TimerTask(timeout, timeUnit) {
            @Override
            public void run() {
                enhanceFutureTask.set(defaultResult);
            }
        };
        timer.add(timerTask);
        try {
            executorService.submit(enhanceFutureTask);
            return enhanceFutureTask.get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 异步调用
     *
     * @param supplier      执行逻辑
     * @param callback      回调逻辑
     * @param timeout       超时时间
     * @param timeUnit      超时时间单位
     * @param defaultResult 默认返回结果
     * @param <T>
     */
    public static <T> void doRequestAsync(Supplier<T> supplier,
                                          BiConsumer<T, Throwable> callback,
                                          long timeout, TimeUnit timeUnit,
                                          T defaultResult) {
        // 1. 包装原始执行逻辑 + 添加回调逻辑
        EnhanceFutureTask<T> enhanceFutureTask = EnhanceFutureTask
            .of(
            () -> {
                try {
                    return supplier.get();
                } catch (Exception e) {
                    throw e;
                }
            }
        ).addFinallyListener(
            (futureTask) -> {
                callback.accept(futureTask.getResult(), futureTask.getException());
            }
        );
        // 2. 提交延迟任务到时间轮, 超时返回默认返回结果
        timer.add(
            new TimerTask(timeout, timeUnit) {
                @Override
                public void run() {
                    enhanceFutureTask.set(defaultResult);
                }
            }
        );
        executorService.submit(enhanceFutureTask);
    }

}

4. 源码

interface_timeout_control: interface_timeout_control (gitee.com)