Eureka系列(六) TimedSupervisorTask类解析
为什么要单独讲解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类解析的更多相关文章
- 【Java集合系列六】LinkedHashMap解析
		2017-08-14 16:30:10 1.简介 LinkedHashMap继承自HashMap,能保证迭代顺序,支持其他Map可选的操作.采用双向链表存储元素,默认的迭代序是插入序.重复插入一个已经 ... 
- java基础解析系列(六)---深入注解原理及使用
		java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer ja ... 
- java基础解析系列(六)---注解原理及使用
		java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及 ... 
- 【Owin 学习系列】2. Owin Startup 类解析
		Owin Startup 类解析 每个 Owin 程序都有 startup 类,在这个 startup 类里面你可以指定应用程序管道模型中的组件.你可以通过不同的方式来连接你的 startup 类和运 ... 
- Eureka 系列(04)客户端源码分析
		Eureka 系列(04)客户端源码分析 [TOC] 0. Spring Cloud 系列目录 - Eureka 篇 在上一篇 Eureka 系列(01)最简使用姿态 中对 Eureka 的简单用法做 ... 
- Eureka 系列(02)Eureka 一致性协议
		目录 Eureka 系列(02)Eureka 一致性协议 0. Spring Cloud 系列目录 - Eureka 篇 1. 服务发现方案对比 1.1 技术选型 1.2 数据模型 2. Eureka ... 
- WCF编程系列(六)以编程方式配置终结点
		WCF编程系列(六)以编程方式配置终结点 示例一中我们的宿主程序非常简单:只是简单的实例化了一个ServiceHost对象,然后调用open方法来启动服务.而关于终结点的配置我们都是通过配置文件来 ... 
- 《深入理解java虚拟机》第六章 类文件结构
		第六章 类文件结构 6.2 无关性的基石 各种不同平台的虚拟机与所有的平台都统一使用的程序存储格式--字节码(ByteCode)是构成平台无关性的基石.java虚拟机不和包括java在内的任何语言 ... 
- 【C++自我精讲】基础系列六 PIMPL模式
		[C++自我精讲]基础系列六 PIMPL模式 0 前言 很实用的一种基础模式. 1 PIMPL解释 PIMPL(Private Implementation 或 Pointer to Implemen ... 
随机推荐
- vue统计组件库和ui框架
			UI组件 element ★13489 - 饿了么出品的Vue2的web UI工具套件 Vux ★8133 - 基于Vue和WeUI的组件库 iview ★6634 - 基于 Vuejs 的开源 UI ... 
- java开发两年,这些线程知识你都不知道,你怎么涨薪?
			前言 什么是线程:程序中负责执行的哪个东东就叫做线程(执行路线,进程内部的执行序列),或者说是进程的子任务. Java中实现多线程有几种方法 继承Thread类: 实现Runnable接口: 实现Ca ... 
- MindManager 2021 版新增了哪些功能
			MindManager Windows 21是一款强大的可视化工具和思维导图软件,在工作应用中有出色的表现.今天就带大家来看下这个新版本增加了哪些功能? 1.新增现代主题信息样式MindManager ... 
- python 几个循环的效率测试
			前言:对于我这种追求极致的人来说,效率很重要. 前面看到网上关于python循环的测评,到自己在项目中的应用,发现,并不是这么回事.所以,写下次博文,一次性了解这个问题. 语言版本:python3.6 ... 
- linux命令-awk,sort,uniq
			学习地址:http://man.linuxde.net/awk#awk的工作原理 awk 选项参数说明: -F fs or --field-separator fs 指定输入文件折分隔符,fs是一个字 ... 
- [BUGCASE]层叠上下文和z-index属性使用不当引发的文本被遮挡的问题
			一.问题描述 在一个fixed-data-table(一个React组件)制作的表格中,需要给表头的字段一个提示的特效,所以做了一个提示层 这个提示层被固定(拖动表格的水平滚动条时固定)的表格列遮住 ... 
- 在VMware下创建windows server 2008虚拟机
			1.创建新的虚拟机 打开VMware软件,点击主页内创建新的虚拟机 2.进入新建虚拟机向导 点击典型,点击下一步 3.在下一步中单击稍后安装操作系统 点击下一步 4.选择操作系统类型 客户机操作系统选 ... 
- 更改ubuntu的分辨率
			乘号使用xyz的x打出来 
- Integer中的奇妙位运算
			Integer中的奇妙位运算 参考资料 https://segmentfault.com/a/1190000015763941 highestOneBit(int i) 函数的作用是获得传入参数的最高 ... 
- MacOS JMeter安装(多图)
			本文基于 MacOS 环境下进行 Jmeter 的安装. 一.下载JMeter 本文选用 JMeter 5.3 版本安装,5.3 版本需要 JDK 1.8 + 版本环境. Jmeter 5.3 下载: ... 
