为什么要单独讲解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. 真的可惜,四面阿里,结果我被JVM垃圾回收机制与 OOM异常卡住了

    前言 为什么需要垃圾回收 首先我们来聊聊为什么会需要垃圾回收,假设我们不进行垃圾回收会造成什么后果,我们举一个简单的例子 我们住在一个房子里面,我们每天都在里面生活,然后垃圾都丢在房子里面,又不打扫, ...

  2. Mac系统应该用什么软件进行清理?

    作为一枚资深的Windows系统使用者,小编刚刚转向Mac系统的怀抱时,各种不适应,Windows系统中普遍使用的360清理软件目前暂时没有Mac版本的,这就让小编很是头疼了,大家的Mac都是用的什么 ...

  3. js 手机号验证

    1 js 通过正则表达式对手机号进行验证 2 3 var reg = /^1[3|4|5|8][0-9]\d{4,8}$/; 4 var sMobile = document.mobileform.m ...

  4. 10万级etl批量作业自动化调度工具Taskctl之轻量级Web应用版

    什么是批量作业: 批量处理是银行业整个信息后台最为重要的技术形态,也是银行核心信息资产数据的分享.传输.演化的重要技术手段.有调查指出,全球70%的数据是经过批量处理得以再次使用,可见批量处理在整个信 ...

  5. Redis分布式锁—SETNX+Lua脚本实现篇

    前言 平时的工作中,由于生产环境中的项目是需要部署在多台服务器中的,所以经常会面临解决分布式场景下数据一致性的问题,那么就需要引入分布式锁来解决这一问题. 针对分布式锁的实现,目前比较常用的就如下几种 ...

  6. JZOJ 2020.10.7 提高B组反思

    JZOJ 2020.10.7 提高B组反思 T1 比较简单的一道题 跑\(k\)遍\(SPFA\) 然后全排列顺序枚举求解 TLE 60 双向存边数组没开两倍-- T2 搞出分母 分子不会求 \(n^ ...

  7. SpringBoot系列:六、集成Swagger文档

    本篇开始介绍Api文档Swagger的集成 一.引入maven依赖 <dependency> <groupId>io.springfox</groupId> < ...

  8. Feign 超时设置

    问题描述 微服务之间使用 Feign 调用,偶发超时问题,配置如下: feign: client: config: default: connectTimeout: 10000 readTimeout ...

  9. Spring Boot 2.x 多数据源配置之 JPA 篇

    场景假设:现有电商业务,商品和库存分别放在不同的库 配置数据库连接 app: datasource: first: driver-class-name: com.mysql.cj.jdbc.Drive ...

  10. 2017 Mid Central Regional G. Faulty Robot(dfs + 尬模)

    这道题看上去太像tarjan缩点了,我一上去本来想把所有的环给缩掉然后统计答案,后来发现哦,这道题不是这么回事儿. 给出黑边红边,一次性走至多只能走一次黑边,问有多少个点可以走到,并且让机器人停下来, ...