CompletableFuture优化首页加载速度

情况是这样的,我们的APP首页,加载的数据非常多,由于业务上对展示数据实时性的要求,不便于使用缓存,所以考虑用并行化查询来优化首页接口的响应速度,使用CompletableFuture,此类的具体使用就不赘述了。

基于CompletableFuture本身的一些缺点,采用包装类规范使用尽可能地来避免,以下是我写的包装类CompletableFutures,具体使用看executeAll方法的注释即可。
注:没有考虑某一个任务超时的情况,我们没有这种场景,就像有些公司一天几百单,非要搞多服务之间的分布式事务,考虑用bigint作为自增id是否会越界,我觉得,想多了。

import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;

/**
 * 对CompletableFuture的包装
 * 使用CompletableFuture的问题如下:
 * 1.尽量使用异步方法,及即带Async后缀的方法,否则使用CompletableFuture意义不大。
 * <p>
 * 2.使用自定义线程池,当supplyAsync不传递Executor时,会使用ForkJoinPool中的共用线程池CommonPool(CommonPool的大小是CPU核数-1,如果是IO密集的应用,线程数可能成为瓶颈)。
 * 本类创建对象要求必传Executor,不存在此问题,自行考量Executor配置。
 * <p>
 * 3.supplyAsync内部嵌套CompletableFuture.supplyAsync, 使用同一个Executor, 导致死锁。
 * supplyAsync向Executor请求线程,并且内部子任务又向Executor请求线程。
 * Executor大小为10,当同一时刻有10个请求到达,则Executor被打满,子任务请求线程时进入阻塞队列排队,但是父任务的完成又依赖于子任务,这时由于子任务得不到线程,父任务无法完成。
 * 主线程执行join()进入阻塞状态,并且永远无法恢复。
 * 本类只接受Callable,不建议任务里面再嵌套子任务。
 * <p>
 * 4.CompletableFuture在回调方法中对异常进行了包装。大部分异常会封装成CompletionException后抛出,真正的异常存储在cause属性中。
 * 本类中已处理
 */
@Slf4j
public class CompletableFutures {
    
    

    private final ExecutorService executor;

    private final String logId;

    private final List<TaskWrapper<?>> taskWrapperList;

    private CompletableFutures(ExecutorService executor, String logId) {
    
    
        this.executor = executor;
        this.logId = logId;
        this.taskWrapperList = new ArrayList<>();
    }

    public static CompletableFutures of(@NonNull ExecutorService executor, @NonNull String logId) {
    
    
        return new CompletableFutures(executor, logId);
    }

    /**
     * 添加任务
     * @param task task的返回值不能为null,如果不需要返回值,可return 1 或 return true
     * @return TaskWrapper 返回任务的包装类,执行完毕后用于获取结果
     */
    public <T> TaskWrapper<T> addTask(Callable<T> task) {
    
    
        TaskWrapper<T> taskWrapper = new TaskWrapper<>(task);
        this.taskWrapperList.add(taskWrapper);
        return taskWrapper;
    }

    /**
     * 提交全部任务,需要保证 tasks 和 results 按顺序一一对应
     * 使用示例如下:
     * CompletableFutures wrapper = CompletableFutures.of(executor, logId);
     * <p>
     * CompletableFutures.TaskWrapper<Integer> taskWrapper1 = wrapper.addTask(() -> 1);
     * CompletableFutures.TaskWrapper<String> taskWrapper2 = wrapper.addTask(() -> "2");
     * <p>
     * wrapper.executeAll();
     * <p>
     * Integer result1 = taskWrapper1.getResult();
     * String result2 = taskWrapper2.getResult();
     */
    public void executeAll() {
    
    
        if (this.taskWrapperList.isEmpty()) {
    
    
            log.info("CompletableFutures#submitAllTask futureTaskWrappers size is zero");
            return;
        }

        // 执行所有任务,并填充到completableFutureList
        log.info("CompletableFutures#submitAllTask before supplyAsync tasks...");
        List<CompletableFuture<?>> completableFutureList = new ArrayList<>();

        for (TaskWrapper<?> taskWrapper : this.taskWrapperList) {
    
    
            CompletableFuture<?> completableFuture = supplyAsync(taskWrapper.getTask());
            completableFutureList.add(completableFuture);
        }
        log.info("CompletableFutures#submitAllTask finished supplyAsync tasks...");

        // 等待所有任务执行完毕
        waitAllOf(completableFutureList);
        log.info("CompletableFutures#submitAllTask All tasks have been completed...");

        // 填充结果
        int size = completableFutureList.size();
        for (int i = 0; i < size; i++) {
    
    
            this.taskWrapperList.get(i).setResult(completableFutureList.get(i).join());
        }
        log.info("CompletableFutures#submitAllTask All results have been filled...");
    }

    /**
     * 提交任务
     */
    public <T> CompletableFuture<T> supplyAsync(@NonNull Callable<T> callable) {
    
    
        // 包装任务,设置logId
        Supplier<T> supplierInner = () -> {
    
    
            LogIdThreadLocal.setLogId(this.logId);
            T t;
            try {
    
    
                t = callable.call();
            } catch (Exception e) {
    
    
                throw new RuntimeException(e);
            } finally {
    
    
                LogIdThreadLocal.clean();
            }
            return t;
        };

        // 提交包装后的任务进入线程池, 设置异常处理方式
        return CompletableFuture
                .supplyAsync(supplierInner, this.executor)
                .exceptionally((ex) -> {
    
    
                    if (ex.getCause() != null) {
    
    
                        throw new RuntimeException(ex.getCause());
                    } else {
    
    
                        throw new RuntimeException(ex);
                    }
                });
    }

    /**
     * 阻塞等待全部任务执行完毕
     *
     * @param completableFutures CompletableFuture的集合
     */
    public void waitAllOf(Collection<CompletableFuture<?>> completableFutures) {
    
    
        CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).join();
    }

    /**
     * 包装任务,组装返回值
     */
    public static class TaskWrapper<T> {
    
    
        private T result;

        private final Callable<T> task;

        private TaskWrapper(@NonNull Callable<T> task) {
    
    
            this.task = task;
        }

        private Callable<T> getTask() {
    
    
            return task;
        }

        private void setResult(Object result) {
    
    
            @SuppressWarnings("unchecked") T t = (T) result;
            this.result = t;
        }

        /**
         * 赋值后才可执行
         */
        public T getResult() {
    
    
            if (result == null) {
    
    
                throw new UnsupportedOperationException("当前任务未经处理,请正确使用本方法");
            }
            return result;
        }
    }
}

import java.util.UUID;

public class LogIdThreadLocal {
    
    
	public static final ThreadLocal<String> logId = new ThreadLocal<String>();

	public static String getLogId() {
    
    
		return logId.get();
	}

	public static void setLogId(String logIdStr) {
    
    
		logId.set(logIdStr);
	}

	public static void clean() {
    
    
		logId.set(null);
		logId.remove();
	}
}

猜你喜欢

转载自blog.csdn.net/w907645377/article/details/129636943