Eureka 源码解析 —— 任务批处理

摘要: 原创出处 http://www.iocoder.cn/Eureka/batch-tasks/ 「芋道源码」欢迎转载,保留摘要,谢谢!

本文主要基于 Eureka 1.8.X 版本

  • 1. 概述
  • 2. 整体流程
  • 3. 任务处理器
  • 4. 创建任务分发器

  • 5. 创建任务接收执行器
  • 6. 创建任务执行器
  • 7. 网络通信整形器
  • 8. 任务接收执行器【处理任务】
  • 9. 任务接收线程【调度任务】
  • 10. 任务执行器【执行任务】


  • 1. 概述

    本文主要分享 任务批处理。Eureka-Server 集群通过任务批处理同步应用实例注册实例,所以本文也是为 Eureka-Server 集群同步的分享做铺垫。

    本文涉及类在 com.netflix.eureka.util.batcher 包下,涉及到主体类的类图如下( 打开大图 ):

    • 紫色部分 —— 任务分发器
    • 蓝色部分 —— 任务接收器
    • 红色部分 —— 任务执行器
    • 绿色部分 —— 任务处理器
    • 黄色部分 —— 任务持有者( 任务 )

    推荐 Spring Cloud 书籍

    2. 整体流程

    任务执行的整体流程如下( 打开大图 ):

    • 细箭头 —— 任务执行经历的操作
    • 粗箭头 —— 任务队列流转的方向
    • 不同于一般情况下,任务提交了立即同步或异步执行,任务的执行拆分了三层队列

      • 第一层,接收队列( acceptorQueue ),重新处理队列( reprocessQueue )。

        • 蓝线:分发器在收到任务执行请求后,提交到接收队列,任务实际未执行
        • 黄线:执行器的工作线程处理任务失败,将符合条件( 见 「3. 任务处理器」 )的失败任务提交到重新执行队列。
        • 第二层,待执行队列( processingOrder )

          • 粉线:接收线程( Runner )将重新执行队列,接收队列提交到待执行队列。

        • 第三层,工作队列( workQueue )

          • 粉线:接收线程( Runner )将待执行队列的任务根据参数( maxBatchingSize )将任务合并成批量任务,调度( 提交 )到工作队列。
          • 黄线:执行器的工作线程,一个工作线程可以拉取一个批量任务进行执行。



    • 三层队列的好处

      • 接收队列,避免处理任务的阻塞等待。
      • 接收线程( Runner )合并任务,将相同任务编号( 是的,任务是带有编号的 )的任务合并,只执行一次。
      • Eureka-Server 为集群同步提供批量操作多个应用实例的接口,一个批量任务可以一次调度接口完成,避免多次调用的开销。当然,这样做的前提是合并任务,这也导致 Eureka-Server 集群之间对应用实例的注册和下线带来更大的延迟。毕竟,Eureka 是在 CAP 之间,选择了 AP


    3. 任务处理器

    com.netflix.eureka.util.batcher.TaskProcessor ,任务处理器接口。接口代码如下:


           
           
    public interface TaskProcessor<T> {
    /
    * A processed task/task list ends up in one of the following states:
    * <ul>
    * <li>{@code Success} processing finished successfully</li>
    * <li>{@code TransientError} processing failed, but shall be retried later</li>
    * <li>{@code PermanentError} processing failed, and is non recoverable</li>
    * </ul>
    /
    enum ProcessingResult {
    /
    成功
    /
    Success,
    /
    拥挤错误
    /
    Congestion,
    /
    瞬时错误
    /
    TransientError,
    /
    永久错误
    /
    PermanentError
    }
    /
    处理单任务
    * In non-batched mode a single task is processed at a time.
    /
    ProcessingResult process(T task);
    /*
    * 处理批量任务
    For batched mode a collection of tasks is run at a time. The result is provided for the aggregated result,
    * and all tasks are handled in the same way according to what is returned (for example are rescheduled, if the
    * error is transient).
    */
    ProcessingResult process(List<T> tasks);
    }
    • ProcessingResult ,处理任务结果。
      • Success ,成功。
      • Congestion ,拥挤错误,任务将会被重试。例如,请求被限流。
      • TransientError ,瞬时错误,任务将会被重试。例如,网络请求超时。
      • PermanentError ,永久错误,任务将会被丢弃。例如,执行时发生程序异常。

    • #process(task) 方法,处理单任务。
    • #process(tasks) 方法,处理批量任务。

    4. 创建任务分发器

    com.netflix.eureka.util.batcher.TaskDispatcher ,任务分发器接口。接口代码如下:


           
           
    public interface TaskDispatcher<ID, T> {
    void process(ID id, T task, long expiryTime);
    void shutdown();
    }
    • #process(...) 方法,提交任务编号,任务,任务过期时间给任务分发器处理。

    com.netflix.eureka.util.batcher.TaskDispatchers ,任务分发器工厂类,用于创建任务分发器。其内部提供两种任务分发器的实现:

    • 批量任务执行的分发器,用于 Eureka-Server 集群注册信息的同步任务。
    • 单任务执行的分发器,用于 Eureka-Server 向亚马逊 AWS 的 ASG ( Autoscaling Group ) 同步状态。虽然本系列暂时对 AWS 相关的不做解析,从工具类的角度来说,本文会对该分发器进行分享。

    com.netflix.eureka.cluster.ReplicationTaskProcessor ,实现 TaskDispatcher ,Eureka-Server 集群任务处理器。感兴趣的同学,可以点击链接自己研究,我们将在 《Eureka 源码解析 —— Eureka-Server 集群同步》 有详细解析。

    4.1 批量任务执行分发器

    调用 TaskDispatchers#createBatchingTaskDispatcher(...) 方法,创建批量任务执行的分发器,实现代码如下:


           
           
    // TaskDispatchers.java
    1: /*
    2: 创建批量任务执行的分发器
    3:
    4: @param id 任务执行器编号
    5: * @param maxBufferSize 待执行队列最大数量
    6: * @param workloadSize 单个批量任务包含任务最大数量
    7: * @param workerCount 任务执行器工作线程数
    8: * @param maxBatchingDelay 批量任务等待最大延迟时长,单位:毫秒
    9: * @param congestionRetryDelayMs 请求限流延迟重试时间,单位:毫秒
    10: * @param networkFailureRetryMs 网络失败延迟重试时长,单位:毫秒
    11: * @param taskProcessor 任务处理器
    12: * @param <ID> 任务编号泛型
    13: * @param <T> 任务泛型
    14: * @return 批量任务执行的分发器
    15: */
    16: public static <ID, T> TaskDispatcher<ID, T> createBatchingTaskDispatcher(String id,
    17: int maxBufferSize,
    18: int workloadSize,
    19: int workerCount,
    20: long maxBatchingDelay,
    21: long congestionRetryDelayMs,
    22: long networkFailureRetryMs,
    23: TaskProcessor<T> taskProcessor) {
    24: // 创建 任务接收执行器
    25: final AcceptorExecutor<ID, T> acceptorExecutor = new AcceptorExecutor<>(
    26: id, maxBufferSize, workloadSize, maxBatchingDelay, congestionRetryDelayMs, networkFailureRetryMs
    27: );
    28: // 创建 批量任务执行器
    29: final TaskExecutors<ID, T> taskExecutor = TaskExecutors.batchExecutors(id, workerCount, taskProcessor, acceptorExecutor);
    30: // 创建 批量任务分发器
    31: return new TaskDispatcher<ID, T>() {
    32: @Override
    33: public void process(ID id, T task, long expiryTime) {
    34: acceptorExecutor.process(id, task, expiryTime);
    35: }
    36:
    37: @Override
    38: public void shutdown() {
    39: acceptorExecutor.shutdown();
    40: taskExecutor.shutdown();
    41: }
    42: };
    43: }
    • 第 1 至 23 行 :方法参数。比较多哈,请耐心理解。
      • workloadSize 参数,单个批量任务包含任务最大数量。
      • taskProcessor 参数,自定义任务执行器实现

    • 第 24 至 27 行 :创建任务接收执行器。在 「5. 创建任务接收器」 详细解析。
    • 第 28 至 29 行 :创建批量任务执行器。在 「6.1 创建批量任务执行器」 详细解析。
    • 第 30 至 42 行 :创建批量任务分发器。

      • 第 32 至 35 行 :#process() 方法的实现,调用 AcceptorExecutor#process(...) 方法,提交 [ 任务编号 , 任务 , 任务过期时间 ] 给任务分发器处理。


    4.2 单任务执行分发器

    调用 TaskDispatchers#createNonBatchingTaskDispatcher(...) 方法,创建单任务执行的分发器,实现代码如下:


           
           
    1: /*
    2: 创建单任务执行的分发器
    3:
    4: @param id 任务执行器编号
    5: * @param maxBufferSize 待执行队列最大数量
    6: * @param workerCount 任务执行器工作线程数
    7: * @param maxBatchingDelay 批量任务等待最大延迟时长,单位:毫秒
    8: * @param congestionRetryDelayMs 请求限流延迟重试时间,单位:毫秒
    9: * @param networkFailureRetryMs 网络失败延迟重试时长,单位:毫秒
    10: * @param taskProcessor 任务处理器
    11: * @param <ID> 任务编号泛型
    12: * @param <T> 任务泛型
    13: * @return 单任务执行的分发器
    14: /
    15: public static <ID, T> TaskDispatcher<ID, T> createNonBatchingTaskDispatcher(String id,
    16: int maxBufferSize,
    17: int workerCount,
    18: long maxBatchingDelay,
    19: long congestionRetryDelayMs,
    20: long networkFailureRetryMs,
    21: TaskProcessor<T> taskProcessor) {
    22: // 创建 任务接收执行器
    23: final AcceptorExecutor<ID, T> acceptorExecutor = new AcceptorExecutor<>(
    24: id, maxBufferSize, / workloadSize = 1 */ 1, maxBatchingDelay, congestionRetryDelayMs, networkFailureRetryMs
    25: );
    26: final TaskExecutors<ID, T> taskExecutor = TaskExecutors.singleItemExecutors(id, workerCount, taskProcessor, acceptorExecutor);
    27: return new TaskDispatcher<ID, T>() {
    28: @Override
    29: public void process(ID id, T task, long expiryTime) {
    30: acceptorExecutor.process(id, task, expiryTime);
    31: }
    32:
    33: @Override
    34: public void shutdown() {
    35: acceptorExecutor.shutdown();
    36: taskExecutor.shutdown();
    37: }
    38: };
    39: }
    • 第 1 至 21 行 :方法参数。比较多哈,请耐心理解。
      • workloadSize 参数,相比 #createBatchingTaskDispatcher(...) 少这个参数。在第 24 行,你会发现该参数传递给 AcceptorExecutor 使用 1 噢
      • taskProcessor 参数,自定义任务执行器实现

    • 第 21 至 25 行 :创建任务接收执行器。和 #createBatchingTaskDispatcher(...) 只差 workloadSize = 1 参数。在 「5. 创建任务接收器」 详细解析。
    • 第 28 至 29 行 :创建任务执行器。#createBatchingTaskDispatcher(...) 差别很大「6.2 创建单任务执行器」 详细解析。
    • 第 30 至 42 行 :创建任务分发器。和 #createBatchingTaskDispatcher(...) 一样。

    5. 创建任务接收执行器

    com.netflix.eureka.util.batcher.AcceptorExecutor ,任务接收执行器。创建构造方法代码如下:


           
           
    1: class AcceptorExecutor<ID, T> {
    2:
    3: private static final Logger logger = LoggerFactory.getLogger(AcceptorExecutor.class);
    4:
    5: /
    6: * 待执行队列最大数量
    7: * {@link #processingOrder}
    8: /
    9: private final int maxBufferSize;
    10: /
    11: 单个批量任务包含任务最大数量
    12: /
    13: private final int maxBatchingSize;
    14: /
    15: 批量任务等待最大延迟时长,单位:毫秒
    16: /
    17: private final long maxBatchingDelay;
    18:
    19: /
    20: 是否关闭
    21: /
    22: private final AtomicBoolean isShutdown = new AtomicBoolean( false);
    23: /
    24: 接收任务队列
    25: /
    26: private final BlockingQueue<TaskHolder<ID, T>> acceptorQueue = new LinkedBlockingQueue<>();
    27: /
    28: 重新执行任务队列
    29: /
    30: private final BlockingDeque<TaskHolder<ID, T>> reprocessQueue = new LinkedBlockingDeque<>();
    31: /
    32: 接收任务线程
    33: /
    34: private final Thread acceptorThread;
    35:
    36: /
    37: 待执行任务映射
    38: /
    39: private final Map<ID, TaskHolder<ID, T>> pendingTasks = new HashMap<>();
    40: /
    41: 待执行队列
    42: /
    43: private final Deque<ID> processingOrder = new LinkedList<>();
    44:
    45: /
    46: 单任务工作请求信号量
    47: /
    48: private final Semaphore singleItemWorkRequests = new Semaphore( 0);
    49: /
    50: 单任务工作队列
    51: /
    52: private final BlockingQueue<TaskHolder<ID, T>> singleItemWorkQueue = new LinkedBlockingQueue<>();
    53:
    54: /
    55: 批量任务工作请求信号量
    56: /
    57: private final Semaphore batchWorkRequests = new Semaphore( 0);
    58: /
    59: 批量任务工作队列
    60: /
    61: private final BlockingQueue<List<TaskHolder<ID, T>>> batchWorkQueue = new LinkedBlockingQueue<>();
    62:
    63: /
    64: 网络通信整形器
    65: */
    66: private final TrafficShaper trafficShaper;
    67:
    68: AcceptorExecutor(String id,
    69: int maxBufferSize,
    70: int maxBatchingSize,
    71: long maxBatchingDelay,
    72: long congestionRetryDelayMs,
    73: long networkFailureRetryMs) {
    74: this.maxBufferSize = maxBufferSize;
    75: this.maxBatchingSize = maxBatchingSize;
    76: this.maxBatchingDelay = maxBatchingDelay;
    77:
    78: // 创建 网络通信整形器
    79: this.trafficShaper = new TrafficShaper(congestionRetryDelayMs, networkFailureRetryMs);
    80:
    81: // 创建 接收任务线程
    82: ThreadGroup threadGroup = new ThreadGroup( "eurekaTaskExecutors");
    83: this.acceptorThread = new Thread(threadGroup, new AcceptorRunner(), "TaskAcceptor-" + id);
    84: this.acceptorThread.setDaemon( true);
    85: this.acceptorThread.start();
    86:
    87: // TODO (省略代码)芋艿:监控相关,暂时无视
    88: }
    89: }
    • 第 5 至 61 行 :属性。比较多哈,请耐心理解。
      • 眼尖如你,会发现 AcceptorExecutor 即存在单任务工作队列( singleItemWorkQueue ),又存在批量任务工作队列( batchWorkQueue ) ,在 「9. 任务接收线程【调度任务】」 会解答这个疑惑。

    • 第 78 至 79 行 :创建网络通信整形器。在 「7. 网络通信整形器」 详细解析。
    • 第 81 至 85 行 :创建接收任务线程

    6. 创建任务执行器

    com.netflix.eureka.util.batcher.TaskExecutors ,任务执行器。其内部提供创建单任务和批量任务执行器的两种方法。TaskExecutors 构造方法如下:


           
           
    class TaskExecutors<ID, T> {
    private static final Logger logger = LoggerFactory.getLogger(TaskExecutors.class);
    /
    * 是否关闭
    /
    private final AtomicBoolean isShutdown;
    /
    工作线程池
    /
    private final List<Thread> workerThreads;
    TaskExecutors(WorkerRunnableFactory<ID, T> workerRunnableFactory, int workerCount, AtomicBoolean isShutdown) {
    this.isShutdown = isShutdown;
    this.workerThreads = new ArrayList<>();
    // 创建 工作线程池
    ThreadGroup threadGroup = new ThreadGroup( "eurekaTaskExecutors");
    for ( int i = 0; i < workerCount; i++) {
    WorkerRunnable<ID, T> runnable = workerRunnableFactory.create(i);
    Thread workerThread = new Thread(threadGroup, runnable, runnable.getWorkerName());
    workerThreads.add(workerThread);
    workerThread.setDaemon( true);
    workerThread.start();
    }
    }
    /*
    * 创建工作线程工厂
    @param <ID> 任务编号泛型
    * @param <T> 批量任务执行器
    */
    interface WorkerRunnableFactory<ID, T> {
    WorkerRunnable<ID, T> create(int idx);
    }
    }
    • workerThreads 属性,工作线程工作任务队列会被工作线程池并发拉取,并发执行
    • com.netflix.eureka.util.batcher.TaskExecutors.WorkerRunnableFactory ,创建工作线程工厂接口。单任务和批量任务执行器的工作线程实现不同,通过自定义工厂实现类创建。

    6.1 创建批量任务执行器

    调用 TaskExecutors#batchExecutors(...) 方法,创建批量任务执行器。实现代码如下:


           
           
    /*
    创建批量任务执行器
    @param name 任务执行器名
    * @param workerCount 任务执行器工作线程数
    * @param processor 任务处理器
    * @param acceptorExecutor 接收任务执行器
    * @param <ID> 任务编号泛型
    * @param <T> 任务泛型
    * @return 批量任务执行器
    /
    static <ID, T> TaskExecutors<ID, T> batchExecutors(final String name,
    int workerCount,
    final TaskProcessor<T> processor,
    final AcceptorExecutor<ID, T> acceptorExecutor) {
    final AtomicBoolean isShutdown = new AtomicBoolean();
    final TaskExecutorMetrics metrics = new TaskExecutorMetrics(name);
    // 创建批量任务执行器
    return new TaskExecutors<>( new WorkerRunnableFactory<ID, T>() { // 批量任务工作线程工厂
    @Override
    public WorkerRunnable<ID, T> create(int idx) {
    return new BatchWorkerRunnable<>( "TaskBatchingWorker-" + name + '-' + idx / 线程名 */, isShutdown, metrics, processor, acceptorExecutor);
    }
    }, workerCount, isShutdown);
    }
    • com.netflix.eureka.util.batcher.TaskExecutors.WorkerRunnable.BatchWorkerRunnable ,批量任务工作线程。

    6.2 创建单任务执行器

    调用 TaskExecutors#singleItemExecutors(...) 方法,创建批量任务执行器。实现代码如下:


           
           
    /*
    创建单任务执行器
    @param name 任务执行器名
    * @param workerCount 任务执行器工作线程数
    * @param processor 任务处理器
    * @param acceptorExecutor 接收任务执行器
    * @param <ID> 任务编号泛型
    * @param <T> 任务泛型
    * @return 单任务执行器
    /
    static <ID, T> TaskExecutors<ID, T> singleItemExecutors(final String name,
    int workerCount,
    final TaskProcessor<T> processor,
    final AcceptorExecutor<ID, T> acceptorExecutor) {
    final AtomicBoolean isShutdown = new AtomicBoolean();
    final TaskExecutorMetrics metrics = new TaskExecutorMetrics(name);
    // 创建单任务执行器
    return new TaskExecutors<>( new WorkerRunnableFactory<ID, T>() { // 单任务工作线程工厂
    @Override
    public WorkerRunnable<ID, T> create(int idx) {
    return new SingleTaskWorkerRunnable<>( "TaskNonBatchingWorker-" + name + '-' + idx / 线程名 */, isShutdown, metrics, processor, acceptorExecutor);
    }
    }, workerCount, isShutdown);
    }
    • com.netflix.eureka.util.batcher.TaskExecutors.WorkerRunnable.SingleTaskWorkerRunnable ,单任务工作线程。

    6.3 工作线程抽象类

    com.netflix.eureka.util.batcher.TaskExecutors.WorkerRunnable ,任务工作线程抽象类。BatchWorkerRunnable 和 SingleTaskWorkerRunnable 都实现该类,差异在 #run() 的自定义实现。WorkerRunnable 实现代码如下:


           
           
    abstract static class WorkerRunnable<ID, T> implements Runnable {
    /
    * 线程名
    /
    final String workerName;
    /
    是否关闭
    /
    final AtomicBoolean isShutdown;
    final TaskExecutorMetrics metrics;
    /
    任务处理器
    /
    final TaskProcessor<T> processor;
    /
    任务接收执行器
    */
    final AcceptorExecutor<ID, T> taskDispatcher;
    // ... 省略构造方法和 getting 方法。
    }

    7. 网络通信整形器

    com.netflix.eureka.util.batcher.TrafficShaper ,网络通信整形器。当任务执行发生请求限流,或是请求网络失败的情况,则延时 AcceptorRunner 将任务提交到工作任务队列,从而避免任务很快去执行,再次发生上述情况。TrafficShaper 实现代码如下:


           
           
    class TrafficShaper {
    /
    * Upper bound on delay provided by configuration.
    /
    private static final long MAX_DELAY = 30 1000;
    /
    * 请求限流延迟重试时间,单位:毫秒
    /
    private final long congestionRetryDelayMs;
    /
    网络失败延迟重试时长,单位:毫秒
    /
    private final long networkFailureRetryMs;
    /
    最后请求限流时间戳,单位:毫秒
    /
    private volatile long lastCongestionError;
    /
    最后网络失败时间戳,单位:毫秒
    /
    private volatile long lastNetworkFailure;
    TrafficShaper( long congestionRetryDelayMs, long networkFailureRetryMs) {
    this.congestionRetryDelayMs = Math.min(MAX_DELAY, congestionRetryDelayMs);
    this.networkFailureRetryMs = Math.min(MAX_DELAY, networkFailureRetryMs);
    }
    void registerFailure(ProcessingResult processingResult) {
    if (processingResult == ProcessingResult.Congestion) {
    lastCongestionError = System.currentTimeMillis();
    } else if (processingResult == ProcessingResult.TransientError) {
    lastNetworkFailure = System.currentTimeMillis();
    }
    }
    /
    计算提交延迟,单位:毫秒
    @return 延迟
    */
    long transmissionDelay() {
    // 无延迟
    if (lastCongestionError == - 1 && lastNetworkFailure == - 1) {
    return 0;
    }
    long now = System.currentTimeMillis();
    // 计算最后请求限流带来的延迟
    if (lastCongestionError != - 1) {
    long congestionDelay = now - lastCongestionError;
    if (congestionDelay >= 0 && congestionDelay < congestionRetryDelayMs) { // 范围内
    return congestionRetryDelayMs - congestionDelay; // 补充延迟
    }
    lastCongestionError = - 1; // 重置时间戳
    }
    // 计算最后网络失败带来的延迟
    if (lastNetworkFailure != - 1) {
    long failureDelay = now - lastNetworkFailure;
    if (failureDelay >= 0 && failureDelay < networkFailureRetryMs) { // 范围内
    return networkFailureRetryMs - failureDelay; // 补充延迟
    }
    lastNetworkFailure = - 1; // 重置时间戳
    }
    // 无延迟
    return 0;
    }
    }

    8. 任务接收执行器【处理任务】

    调用 AcceptorExecutor#process(...) 方法,添加任务到接收任务队列。实现代码如下:


           
           
    // AcceptorExecutor.java
    void process(ID id, T task, long expiryTime) {
    acceptorQueue.add( new TaskHolder<ID, T>(id, task, expiryTime));
    acceptedTasks++;
    }
    • com.netflix.eureka.util.batcher.TaskHolder ,任务持有者,实现代码如下:

               
               
      class TaskHolder<ID, T> {
      /**
      * 任务编号
      */
      private final ID id;
      /**
      * 任务
      */
      private final T task;
      /**
      * 任务过期时间戳
      */
      private final long expiryTime;
      /**
      * 任务提交时间戳
      */
      private final long submitTimestamp;
      }

    9. 任务接收线程【调度任务】

    后台线程执行 AcceptorRunner#run(...) 方法,调度任务。实现代码如下:


           
           
    1: @Override
    2: public void run() {
    3: long scheduleTime = 0;
    4: while (!isShutdown.get()) {
    5: try {
    6: // 处理完输入队列( 接收队列 + 重新执行队列 )
    7: drainInputQueues();
    8:
    9: // 待执行任务数量
    10: int totalItems = processingOrder.size();
    11:
    12: // 计算调度时间
    13: long now = System.currentTimeMillis();
    14: if (scheduleTime < now) {
    15: scheduleTime = now + trafficShaper.transmissionDelay();
    16: }
    17:
    18: // 调度
    19: if (scheduleTime <= now) {
    20: // 调度批量任务
    21: assignBatchWork();
    22: // 调度单任务
    23: assignSingleItemWork();
    24: }
    25:
    26: // 1)任务执行器无任务请求,正在忙碌处理之前的任务;或者 2)任务延迟调度。睡眠 10 秒,避免资源浪费。
    27: // If no worker is requesting data or there is a delay injected by the traffic shaper,
    28: // sleep for some time to avoid tight loop.
    29: if (totalItems == processingOrder.size()) {
    30: Thread.sleep( 10);
    31: }
    32: } catch (InterruptedException ex) {
    33: // Ignore
    34: } catch (Throwable e) {
    35: // Safe-guard, so we never exit this loop in an uncontrolled way.
    36: logger.warn( "Discovery AcceptorThread error", e);
    37: }
    38: }
    39: }
    • 第 4 行 :无限循环执行调度,直到关闭。
    • 第 6 至 7 行 :调用 #drainInputQueues() 方法,循环处理完输入队列( 接收队列 + 重新执行队列 ),直到有待执行的任务。实现代码如下:

               
               
      1: private void drainInputQueues() throws InterruptedException {
      2: do {
      3: // 处理完重新执行队列
      4: drainReprocessQueue();
      5: // 处理完接收队列
      6: drainAcceptorQueue();
      7:
      8: // 所有队列为空,等待 10 ms,看接收队列是否有新任务
      9: if (!isShutdown.get()) {
      10: // If all queues are empty, block for a while on the acceptor queue
      11: if (reprocessQueue.isEmpty() && acceptorQueue.isEmpty() && pendingTasks.isEmpty()) {
      12: TaskHolder<ID, T> taskHolder = acceptorQueue.poll( 10, TimeUnit.MILLISECONDS);
      13: if (taskHolder != null) {
      14: appendTaskHolder(taskHolder);
      15: }
      16: }
      17: }
      18: } while (!reprocessQueue.isEmpty() || !acceptorQueue.isEmpty() || pendingTasks.isEmpty()); // 处理完输入队列( 接收队列 + 重新执行队列 )
      19: }
      • 第 2 行 && 第 18 行 :循环,直到同时满足如下全部条件:
        • 重新执行队列( reprocessQueue ) 和接收队列( acceptorQueue )为空
        • 待执行任务映射( pendingTasks )不为空

      • 第 3 至 4 行 :处理完重新执行队列( reprocessQueue )。实现代码如下:


                   
                   
        1: private void drainReprocessQueue() {
        2: long now = System.currentTimeMillis();
        3: while (!reprocessQueue.isEmpty() && !isFull()) {
        4: TaskHolder<ID, T> taskHolder = reprocessQueue.pollLast(); // 优先拿较新的任务
        5: ID id = taskHolder.getId();
        6: if (taskHolder.getExpiryTime() <= now) { // 过期
        7: expiredTasks++;
        8: } else if (pendingTasks.containsKey(id)) { // 已存在
        9: overriddenTasks++;
        10: } else {
        11: pendingTasks.put(id, taskHolder);
        12: processingOrder.addFirst(id); // 提交到队头
        13: }
        14: }
        15: // 如果待执行队列已满,清空重新执行队列,放弃较早的任务
        16: if (isFull()) {
        17: queueOverflows += reprocessQueue.size();
        18: reprocessQueue.clear();
        19: }
        20: }
        • 第 4 行 :优先从重新执行任务的队尾拿较新的任务,从而实现保留更新的任务在待执行任务映射( pendingTasks ) 里。
        • 第 12 行 :添加任务编号到待执行队列( processingOrder ) 的头部。效果如下图:
        • 第 15 至 18 行 :如果待执行队列( pendingTasks )已满,清空重新执行队列( processingOrder ),放弃较早的任务。

      • 第 5 至 6 行 :处理完接收队列( acceptorQueue ),实现代码如下:


                   
                   
        private void drainAcceptorQueue() {
        while (!acceptorQueue.isEmpty()) { // 循环,直到接收队列为空
        appendTaskHolder(acceptorQueue.poll());
        }
        }
        private void appendTaskHolder(TaskHolder<ID, T> taskHolder) {
        // 如果待执行队列已满,移除待处理队列,放弃较早的任务
        if (isFull()) {
        pendingTasks.remove(processingOrder.poll());
        queueOverflows++;
        }
        // 添加到待执行队列
        TaskHolder<ID, T> previousTask = pendingTasks.put(taskHolder.getId(), taskHolder);
        if (previousTask == null) {
        processingOrder.add(taskHolder.getId());
        } else {
        overriddenTasks++;
        }
        }

      • 第 8 至 17 行 :当所有队列为空,阻塞从接收队列( acceptorQueue ) 拉取任务 10 ms。若拉取到,添加到待执行队列( processingOrder )。



    • 第 12 至 16 行 :计算可调度任务的最小时间( scheduleTime )。

      • scheduleTime 小于当前时间,不重新计算,即此时需要延迟等待调度。
      • scheduleTime 大于等于当前时间,配合 TrafficShaper#transmissionDelay(...) 重新计算。

    • 第 19 行 :当 scheduleTime 小于当前时间,执行任务的调度。
    • 第 21 行 :调用 #assignBatchWork() 方法,调度批量任务。实现代码如下:


               
               
      1: void assignBatchWork() {
      2: if (hasEnoughTasksForNextBatch()) {
      3: // 获取 批量任务工作请求信号量
      4: if (batchWorkRequests.tryAcquire( 1)) {
      5: // 获取批量任务
      6: long now = System.currentTimeMillis();
      7: int len = Math.min(maxBatchingSize, processingOrder.size());
      8: List<TaskHolder<ID, T>> holders = new ArrayList<>(len);
      9: while (holders.size() < len && !processingOrder.isEmpty()) {
      10: ID id = processingOrder.poll();
      11: TaskHolder<ID, T> holder = pendingTasks.remove(id);
      12: if (holder.getExpiryTime() > now) { // 过期
      13: holders.add(holder);
      14: } else {
      15: expiredTasks++;
      16: }
      17: }
      18: //
      19: if (holders.isEmpty()) { // 未调度到批量任务,释放请求信号量
      20: batchWorkRequests.release();
      21: } else { // 添加批量任务到批量任务工作队列
      22: batchSizeMetric.record(holders.size(), TimeUnit.MILLISECONDS);
      23: batchWorkQueue.add(holders);
      24: }
      25: }
      26: }
      27: }
      • 第 2 行 :调用 #hasEnoughTasksForNextBatch() 方法,判断是否有足够任务进行下一次批量任务调度:1)待执行任务( processingOrder )映射已满;或者 2)到达批量任务处理最大等待延迟。实现代码如下:

                   
                   
        private boolean hasEnoughTasksForNextBatch() {
        // 待执行队列为空
        if (processingOrder.isEmpty()) {
        return false;
        }
        // 待执行任务映射已满
        if (pendingTasks.size() >= maxBufferSize) {
        return true;
        }
        // 到达批量任务处理最大等待延迟( 通过待处理队列的头部任务判断 )
        TaskHolder<ID, T> nextHolder = pendingTasks.get(processingOrder.peek());
        long delay = System.currentTimeMillis() - nextHolder.getSubmitTimestamp();
        return delay >= maxBatchingDelay;
        }
        • x

      • 第 5 至 17 行 :获取批量任务( holders )。�� 你会发现,本文说了半天的批量任务,实际是 List<TaskHolder<ID, T>> 哈。


      • 第 4 行 :获取批量任务工作请求信号量( batchWorkRequests ) 。在任务执行器的批量任务执行器,每次执行时,发出 batchWorkRequests每一个信号量需要保证获取到一个批量任务
      • 第 19 至 20 行 :未调度到批量任务,释放请求信号量,代表请求实际未完成,每一个信号量需要保证获取到一个批量任务
      • 第 21 至 24 行 :添加批量任务到批量任务工作队列。
      • 第 23 行 :调用 #assignSingleItemWork() 方法,调度单任务。

    • 第 23 行 :调用 #assignSingleItemWork() 方法,调度单任务,和 #assignBatchWork() 方法类似。实现代码如下:


               
               
      void assignSingleItemWork() {
      if (!processingOrder.isEmpty()) { // 待执行任队列不为空
      // 获取 单任务工作请求信号量
      if (singleItemWorkRequests.tryAcquire( 1)) {
      // 【循环】获取单任务
      long now = System.currentTimeMillis();
      while (!processingOrder.isEmpty()) {
      ID id = processingOrder.poll(); // 一定不为空
      TaskHolder<ID, T> holder = pendingTasks.remove(id);
      if (holder.getExpiryTime() > now) {
      singleItemWorkQueue.add(holder);
      return;
      }
      expiredTasks++;
      }
      // 获取不到单任务,释放请求信号量
      singleItemWorkRequests.release();
      }
      }
      }
      • x

    • 第 26 至 31 行 :当调度任务前的待执行任务数( totalItems )等于当前待执行队列( processingOrder )的任务数,意味着:1)任务执行器无任务请求,正在忙碌处理之前的任务;或者 2)任务延迟调度。睡眠 10 秒,避免资源浪费。



    10. 任务执行器【执行任务】

    10.1 批量任务工作线程

    批量任务工作后台线程( BatchWorkerRunnable )执行 #run(...) 方法,调度任务。实现代码如下:


           
           
    //
    1: @Override
    2: public void run() {
    3: try {
    4: while (!isShutdown.get()) {
    5: // 获取批量任务
    6: List<TaskHolder<ID, T>> holders = getWork();
    7:
    8: // TODO 芋艿:监控相关,暂时无视
    9: metrics.registerExpiryTimes(holders);
    10:
    11: // 获得实际批量任务
    12: List<T> tasks = getTasksOf(holders);
    13: // 调用处理器执行任务
    14: ProcessingResult result = processor.process(tasks);
    15: switch (result) {
    16: case Success:
    17: break;
    18: case Congestion:
    19: case TransientError:
    20: taskDispatcher.reprocess(holders, result); // 提交重新处理
    21: break;
    22: case PermanentError:
    23: logger.warn( "Discarding {} tasks of {} due to permanent error", holders.size(), workerName);
    24: }
    25:
    26: // TODO 芋艿:监控相关,暂时无视
    27: metrics.registerTaskResult(result, tasks.size());
    28: }
    29: } catch (InterruptedException e) {
    30: // Ignore
    31: } catch (Throwable e) {
    32: // Safe-guard, so we never exit this loop in an uncontrolled way.
    33: logger.warn( "Discovery WorkerThread error", e);
    34: }
    35: }
    • 第 4 行 :无限循环执行调度,直到关闭。
    • 第 6 行 :调用 getWork() 方法,获取一个批量任务直到成功。实现代码如下:

               
               
      1: private List<TaskHolder<ID, T>> getWork() throws InterruptedException {
      2: // 发起请求信号量,并获得批量任务的工作队列
      3: BlockingQueue<List<TaskHolder<ID, T>>> workQueue = taskDispatcher.requestWorkItems();
      4: // 【循环】获取批量任务,直到成功
      5: List<TaskHolder<ID, T>> result;
      6: do {
      7: result = workQueue.poll( 1, TimeUnit.SECONDS);
      8: } while (!isShutdown.get() && result == null);
      9: return result;
      10: }
      • 第 3 行 :调用 TaskDispatcher#requestWorkItems() 方法,发起请求信号量,并获得批量任务的工作队列。实现代码如下:

                   
                   
        // TaskDispatcher.java
        /**
        * 批量任务工作请求信号量
        */
        private final Semaphore batchWorkRequests = new Semaphore( 0);
        /**
        * 批量任务工作队列
        */
        private final BlockingQueue<List<TaskHolder<ID, T>>> batchWorkQueue = new LinkedBlockingQueue<>();
        BlockingQueue<List<TaskHolder<ID, T>>> requestWorkItems() {
        batchWorkRequests.release();
        return batchWorkQueue;
        }
        • 注意,批量任务工作队列( batchWorkQueue ) 和单任务工作队列( singleItemWorkQueue ) 是不同的队列

      • 第 5 至 8 行 :循环获取一个批量任务,直到成功。



    • 第 12 行 :调用 #getTasksOf(...) 方法,获得实际批量任务。实现代码如下:


               
               
      private List<T> getTasksOf(List<TaskHolder<ID, T>> holders) {
      List<T> tasks = new ArrayList<>(holders.size());
      for (TaskHolder<ID, T> holder : holders) {
      tasks.add(holder.getTask());
      }
      return tasks;
      }
      • x

    • 第 14 至 24 行 :调用处理器( TaskProcessor ) 执行任务。当任务执行结果为 CongestionTransientError ,调用 AcceptorExecutor#reprocess(...) 提交整个批量任务重新处理,实现代码如下:


               
               
      // AcceptorExecutor.java
      void reprocess(List<TaskHolder<ID, T>> holders, ProcessingResult processingResult) {
      // 添加到 重新执行队列
      reprocessQueue.addAll(holders);
      // TODO 芋艿:监控相关,暂时无视
      replayedTasks += holders.size();
      // 提交任务结果给 TrafficShaper
      trafficShaper.registerFailure(processingResult);
      }


    10.2 单任务工作线程

    单任务工作后台线程( SingleTaskWorkerRunnable )执行 #run(...) 方法,调度任务,和 BatchWorkerRunnable#run(...) 基本类似,就不啰嗦了。实现代码如下:


           
           
    @Override
    // SingleTaskWorkerRunnable.java
    public void run() {
    try {
    while (!isShutdown.get()) {
    // 发起请求信号量,并获得单任务的工作队列
    BlockingQueue<TaskHolder<ID, T>> workQueue = taskDispatcher.requestWorkItem();
    TaskHolder<ID, T> taskHolder;
    // 【循环】获取单任务,直到成功
    while ((taskHolder = workQueue.poll( 1, TimeUnit.SECONDS)) == null) {
    if (isShutdown.get()) {
    return;
    }
    }
    // TODO 芋艿:监控相关,暂时无视
    metrics.registerExpiryTime(taskHolder);
    if (taskHolder != null) {
    // 调用处理器执行任务
    ProcessingResult result = processor.process(taskHolder.getTask());
    switch (result) {
    case Success:
    break;
    case Congestion:
    case TransientError:
    taskDispatcher.reprocess(taskHolder, result); // 提交重新处理
    break;
    case PermanentError:
    logger.warn( "Discarding a task of {} due to permanent error", workerName);
    }
    // TODO 芋艿:监控相关,暂时无视
    metrics.registerTaskResult(result, 1);
    }
    }
    } catch (InterruptedException e) {
    // Ignore
    } catch (Throwable e) {
    // Safe-guard, so we never exit this loop in an uncontrolled way.
    logger.warn( "Discovery WorkerThread error", e);
    }
    }
Resource Page Description 在以前的文章中,我曾多次强调应用程序中异步化的重要性。尤其对于IO密集型操作,异步执行对于应用程序的响应能力和伸缩性有非常关键的影响。正确使用异步编程能够使用尽可能少的线程来执行大量的IO密集型操作。可惜的是,即时异步编程有避免线程阻塞等诸多好处,但是这种编程方式至今没有被大量采用。其原因有很多,其中最主要的一点可能就是异步模型在编程较为困难,导致许多开发人员不愿意去做。 异步,则意味着一个任务至少要被拆分为“二段式”的调用方式:一个方法用于发起异步请求,另一个方法用于异步任务完成后的回调。与传统方法调用方式相比,异步调用时的中间数据不能存放在线程栈上,方法之间的也不能简单地通过参数传递的方式来共享数据。此外,传统方法调用中的try…catch…finally,using等关键字都无法跨越方法边界,因此异步编程在处理异常,保护资源等方面也需要花更大的精力才行。如果一不小心,轻则造成资源泄露,重则使整个应用程序崩溃。 因此,无论是微软官方还是社区中都出现了一些简化异步编程方式的组件,例如微软并行与协调运行时和Wintellect's .NET Power Threading Library中的AsyncEnumerator。同时,我基于AsyncEnumerator构建了一个AsyncTaskDispatcher组件,使多个有依赖关系的异步操作之间的协作调用得以大大简化。 以上是引用,自己做了个更简单的demo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值