为什么要单独讲解TimedSupervisorTask这个类呢?因为这个类在我们DiscoveryClient类的initScheduledTasks方法进行定时任务初始化时被使用得比较多,所以我们需要了解下这个类,我们先看下TimedSupervisorTask这个类在initScheduledTasks的具体使用:

private final ScheduledExecutorService scheduler;
private void initScheduledTasks() {
…省略其他代码
// 初始化定时拉取服务注册信息
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS); …省略其他代码
// 初始化定时服务续约任务
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
…省略其他代码
}

  由此可见,TimedSupervisorTask类被使用在了定时任务的初始化中,我们具体来看看这个类的结构:

public class TimedSupervisorTask extends TimerTask {
private static final Logger logger = LoggerFactory.getLogger(TimedSupervisorTask.class); private final Counter timeoutCounter;
private final Counter rejectedCounter;
private final Counter throwableCounter;
private final LongGauge threadPoolLevelGauge; private final ScheduledExecutorService scheduler;
private final ThreadPoolExecutor executor;
private final long timeoutMillis;
private final Runnable task; private final AtomicLong delay;
private final long maxDelay; public TimedSupervisorTask(String name, ScheduledExecutorService scheduler, ThreadPoolExecutor executor,
int timeout, TimeUnit timeUnit, int expBackOffBound, Runnable task) {
this.scheduler = scheduler;
this.executor = executor;
this.timeoutMillis = timeUnit.toMillis(timeout);
this.task = task;
this.delay = new AtomicLong(timeoutMillis);
this.maxDelay = timeoutMillis * expBackOffBound; // Initialize the counters and register.
timeoutCounter = Monitors.newCounter("timeouts");
rejectedCounter = Monitors.newCounter("rejectedExecutions");
throwableCounter = Monitors.newCounter("throwables");
threadPoolLevelGauge = new LongGauge(MonitorConfig.builder("threadPoolUsed").build());
Monitors.registerObject(name, this);
}
@Override
public void run() {
Future<?> future = null;
try {
future = executor.submit(task);
threadPoolLevelGauge.set((long) executor.getActiveCount());
future.get(timeoutMillis, TimeUnit.MILLISECONDS); // block until done or timeout
delay.set(timeoutMillis);
threadPoolLevelGauge.set((long) executor.getActiveCount());
} catch (TimeoutException e) {
logger.warn("task supervisor timed out", e);
timeoutCounter.increment();
long currentDelay = delay.get();
// 如果出现异常,则将时间*2,然后取 定时时间 和 最长定时时间中最小的为下次任务执行的延时时间
long newDelay = Math.min(maxDelay, currentDelay * 2);
delay.compareAndSet(currentDelay, newDelay);
} catch (RejectedExecutionException e) {
if (executor.isShutdown() || scheduler.isShutdown()) {
logger.warn("task supervisor shutting down, reject the task", e);
} else {
logger.warn("task supervisor rejected the task", e);
}
rejectedCounter.increment();
} catch (Throwable e) {
if (executor.isShutdown() || scheduler.isShutdown()) {
logger.warn("task supervisor shutting down, can't accept the task");
} else {
logger.warn("task supervisor threw an exception", e);
}
throwableCounter.increment();
} finally {
if (future != null) {
future.cancel(true);
}
if (!scheduler.isShutdown()) {
scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);
}
}
}
}

  我们可以仔细看看run方法的具体实现,因为这里有一个值得借鉴的设计思路!!!

  我们简单来看看这个方法具体执行流程:

    1.执行submit()方法提交任务

    2.执行future.get()方法,如果没有在规定的时间得到返回值或者任务出现异常,则进入异常处理catch代码块。

    3.如果发生异常

      a. 发生TimeoutException异常,则执行Math.min(maxDelay, currentDelay ️ 2);得到任务延时时间 ️ 2 和 最大延时时间的最小值,然后改变任务的延时时间timeoutMillis(延时任务时间默认值是30s)

      b.发生RejectedExecutionException异常,则将rejectedCounter值+1

      c.发生Throwable异常,则将throwableCounter值+1

    4.如果没有发生异常,则再设置一次延时任务时间timeoutMillis

    5.进入finally代码块

      a.如果future不为null,则执行future.cancel(true),中断线程停止任务

      b.如果线程池没有shutdown,则创建一个新的定时任务

\(\color{red}{注意}\):不知道有没有小伙伴发现,不管我们的定时任务执行是成功还是结束(如果还没有执行结束,也会被中断),然后会再重新初始化一个新的任务。并且这个任务的延时时间还会因为不同的情况受到改变,在try代码块中如果不发现异常,则会重新初始化延时时间,如果发生TimeoutException异常,则会更改延时时间,更改为 任务延时时间 ️ 2 和 最大延时时间的最小值。所以我们会发现这样的设计会让整个延时任务很灵活。如果不发生异常,则延时时间不会变;如果发现异常,则增长延时时间;如果程序又恢复正常了,则延时时间又恢复成了默认值。

总结:我们在设计延时/周期性任务时就可以参考TimedSupervisorTask的实现,程序一旦遇到发生超时异常,就将间隔时间调大,如果连续超时,那么每次间隔时间都会增大一倍,一直到达外部参数设定的上限为止,一旦新任务不再发生超时异常,间隔时间又会自动恢复为初始值。

Eureka系列(六) TimedSupervisorTask类解析的更多相关文章

  1. 【Java集合系列六】LinkedHashMap解析

    2017-08-14 16:30:10 1.简介 LinkedHashMap继承自HashMap,能保证迭代顺序,支持其他Map可选的操作.采用双向链表存储元素,默认的迭代序是插入序.重复插入一个已经 ...

  2. java基础解析系列(六)---深入注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer ja ...

  3. java基础解析系列(六)---注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及 ...

  4. 【Owin 学习系列】2. Owin Startup 类解析

    Owin Startup 类解析 每个 Owin 程序都有 startup 类,在这个 startup 类里面你可以指定应用程序管道模型中的组件.你可以通过不同的方式来连接你的 startup 类和运 ...

  5. Eureka 系列(04)客户端源码分析

    Eureka 系列(04)客户端源码分析 [TOC] 0. Spring Cloud 系列目录 - Eureka 篇 在上一篇 Eureka 系列(01)最简使用姿态 中对 Eureka 的简单用法做 ...

  6. Eureka 系列(02)Eureka 一致性协议

    目录 Eureka 系列(02)Eureka 一致性协议 0. Spring Cloud 系列目录 - Eureka 篇 1. 服务发现方案对比 1.1 技术选型 1.2 数据模型 2. Eureka ...

  7. WCF编程系列(六)以编程方式配置终结点

    WCF编程系列(六)以编程方式配置终结点   示例一中我们的宿主程序非常简单:只是简单的实例化了一个ServiceHost对象,然后调用open方法来启动服务.而关于终结点的配置我们都是通过配置文件来 ...

  8. 《深入理解java虚拟机》第六章 类文件结构

    第六章 类文件结构   6.2 无关性的基石 各种不同平台的虚拟机与所有的平台都统一使用的程序存储格式--字节码(ByteCode)是构成平台无关性的基石.java虚拟机不和包括java在内的任何语言 ...

  9. 【C++自我精讲】基础系列六 PIMPL模式

    [C++自我精讲]基础系列六 PIMPL模式 0 前言 很实用的一种基础模式. 1 PIMPL解释 PIMPL(Private Implementation 或 Pointer to Implemen ...

随机推荐

  1. Hadoop大数据平台搭建之前期配置(2)

    环境:CentOS 7.4 (1708  DVD) 工具:VMware.MobaXterm 一. 克隆大数据集群 1. 选中已经进行了基本配置的虚拟机,进行克隆. 2. 此处改为"创建完整克 ...

  2. PVE 下的虚拟机磁盘扩容

    扩容背景:一台测试机磁盘不足,需要扩容: /dev/mapper/centos-root 40G 40G 20K 100% / 先到PVE网页上对需要扩容的机器扩容,这里新建20G示例: 另外之前也分 ...

  3. CorelDRAW文件损坏的几种解决方法

    以前做好的CorelDRAW文件突然打不开了,或者是死机.非法操作等原因造成CorelDRAW文件损坏,有时打开源文件发现一片空白,源文件保存损坏无法打开怎么办?此时不要着急,你可以试试以下几种办法帮 ...

  4. FL studio系列教程(十七):FL Studio走带面板介绍

    FL Studio走带面板主要是用来控制播放.录音以及调整歌曲速度的,除此之外还可以用来选择样本剪辑.下面就来详细地看一下这部分菜单. 1.样本/歌曲模式 样本/歌曲模式主要是用来切换样本和歌曲两种模 ...

  5. 【linux】系统调用版串口分析&源码实战

    目录 前言 参考 1. 实战分析 1.1 开发步骤 1.1.1 获取串口设备路径 1.1.2 打开设备文件 1.1.3 配置串口 termios 结构体 1. c_iflag 输入模式标志 2. c_ ...

  6. JVM(三)-java虚拟机类加载机制

    概述: 上一篇文章,介绍了java虚拟机的运行时区域,Java虚拟机根据不同的分工,把内存划分为各个不同的区域.在java程序中,最小的运行单元一般都是创建一个对象,然后调用对象的某个 方法.通过上一 ...

  7. redis new

    redis cluster 数据结构 geo,heperloglog 3个非核心dict:阻塞dict,非阻塞dict,watch dict 3个bio线程,生产者消费者模式,主线程生产者: 1.la ...

  8. 【mq读书笔记】定时消息

    mq不支持任意的时间京都,如果要支持,不可避免的需要在Broker层做消息排序,加上持久化方面的考量,将不可避免地带来巨大的性能消耗,所以rocketMQ只支持特定级别的延迟消息. 在Broker短通 ...

  9. LeetCode 036 Valid Sudoku

    题目要求:Valid Sudoku Determine if a Sudoku is valid, according to: Sudoku Puzzles - The Rules. The Sudo ...

  10. 软件工程与UML第二次作业

    博客班级 https://edu.cnblogs.com/campus/fzzcxy/2018SE2/ 作业要求 https://edu.cnblogs.com/campus/fzzcxy/2018S ...