超时引发的牛角尖二(hystrix中的超时)

news2024/10/7 12:27:44

至今我都清楚记得自己负责的系统请求云上关联系统时所报的异常信息。为了解决这个异常,我坚持让这个关联系统的负责人查看,并且毫不顾忌他的嘲讽和鄙视,甚至无视他烦躁的情绪。不过我还是高估了自己的脸皮,最终在其恶狠狠地抛下“你自己的问题为啥不自己看!”这句话后悻悻离开。当时,这个“超时”问题就铭刻在了我的脑海里。

回到座位,我就狠地翻起了代码,最终发现我们系统调用他们系统地请求会被包装到HystrixCommand子类对象中,然后通过调用该对象上地execute()方法来完成。但是我依旧没有弄明白超时是怎么实现的。直到今天,我才有了一个大概的思路。为了将自己的思路描述出来,先让我们看一些工作中经常用到的工具类吧。

1. CountDownLatch

首先要介绍的就是CountDownLatch。它是java并发包(java.util.concurrent)中提供的一个同步工具类,其允许一个或多个线程等待其他线程完成一组操作后再执行。它再多线程编程中主要用于实现线程间的协调和同步。接下来让我们看一下其源码:

public class CountDownLatch {
    /**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

    private final Sync sync;

    /**
     * Constructs a {@code CountDownLatch} initialized with the given count.
     *
     * @param count the number of times {@link #countDown} must be invoked
     *        before threads can pass through {@link #await}
     * @throws IllegalArgumentException if {@code count} is negative
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    /**
     * Causes the current thread to wait until the latch has counted down to
     * zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
     *
     * <p>If the current count is zero then this method returns immediately.
     *
     * <p>If the current count is greater than zero then the current
     * thread becomes disabled for thread scheduling purposes and lies
     * dormant until one of two things happen:
     * <ul>
     * <li>The count reaches zero due to invocations of the
     * {@link #countDown} method; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread.
     * </ul>
     *
     * <p>If the current thread:
     * <ul>
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
     * </ul>
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared.
     *
     * @throws InterruptedException if the current thread is interrupted
     *         while waiting
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    /**
     * Causes the current thread to wait until the latch has counted down to
     * zero, unless the thread is {@linkplain Thread#interrupt interrupted},
     * or the specified waiting time elapses.
     *
     * <p>If the current count is zero then this method returns immediately
     * with the value {@code true}.
     *
     * <p>If the current count is greater than zero then the current
     * thread becomes disabled for thread scheduling purposes and lies
     * dormant until one of three things happen:
     * <ul>
     * <li>The count reaches zero due to invocations of the
     * {@link #countDown} method; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     * <li>The specified waiting time elapses.
     * </ul>
     *
     * <p>If the count reaches zero then the method returns with the
     * value {@code true}.
     *
     * <p>If the current thread:
     * <ul>
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
     * </ul>
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared.
     *
     * <p>If the specified waiting time elapses then the value {@code false}
     * is returned.  If the time is less than or equal to zero, the method
     * will not wait at all.
     *
     * @param timeout the maximum time to wait
     * @param unit the time unit of the {@code timeout} argument
     * @return {@code true} if the count reached zero and {@code false}
     *         if the waiting time elapsed before the count reached zero
     * @throws InterruptedException if the current thread is interrupted
     *         while waiting
     */
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    /**
     * Decrements the count of the latch, releasing all waiting threads if
     * the count reaches zero.
     *
     * <p>If the current count is greater than zero then it is decremented.
     * If the new count is zero then all waiting threads are re-enabled for
     * thread scheduling purposes.
     *
     * <p>If the current count equals zero then nothing happens.
     */
    public void countDown() {
        sync.releaseShared(1);
    }

    /**
     * Returns the current count.
     *
     * <p>This method is typically used for debugging and testing purposes.
     *
     * @return the current count
     */
    public long getCount() {
        return sync.getCount();
    }

    /**
     * Returns a string identifying this latch, as well as its state.
     * The state, in brackets, includes the String {@code "Count ="}
     * followed by the current count.
     *
     * @return a string identifying this latch, as well as its state
     */
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

从源码可以看出,CountDownLatch内部维护了一个计数器(counter),该计数器在构造时初始化为一个特定的值。其中有两个比较常用的方法:1.countDown()方法,当一个线程完成了自己的工作后调用此方法,会将计数器减1;2.await()方法,其会在主线程或者其他等待线程中调用,如果当前计数器不为0,则会阻塞等待,直到计数器递减至0为止。此外还有一个与await()方法作用类似的方法——await(long timeout, TimeUnit unit),该方法可以实现超时等待。在这个方法中,我们会传入一个等待的超时时间(timeout)和时间单位(TimeUnit)。如果在指定的时间内计数器到达0或者当前线程被中断,则该方法会返回true。如果计数器没有到达0并且等待时间超过了指定的超时时间,那么该方法将会返回false。下面让我们看一个例子:

public class SpringTransactionApplication {

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        new Thread() {
            @Override
            public void run() {
                try {
                    // 当前线程等待 10 秒钟
                    if (!countDownLatch.await(3, TimeUnit.SECONDS)) {
                        System.out.println("超时");
                        return;
                    } else {
                        System.out.println("正常");
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }.start();
        // 主线程休眠 8 秒钟
        try {
            Thread.sleep(1000 * 8);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 主线程调用 CountDownLatch 上的 countDown() 方法
        countDownLatch.countDown();
        // 这里会有两种情况,如果主线程 - main 的等待时间超过了当前线程 - t1 的等待时间,那么当前线程 - t1 会输出“超时“
        // 如果主线程 - main 的等待时间小于当前线程 - t1 的等待时间,则当前线程 - t1 会输出“正常”

    }

}

通过执行这个例子,我们不难发现,在主线程等待8秒的时间里,没有任何线程调用CountDownLatch上的countDown()方法,所以线程t1在等待3秒后await(timeout, TimeUnit)方法直接返回了false,所以可以看到控制台输出了超时,如下图所示:

下面让我们一起梳理一下这个类的应用场景吧:

  1. 启动屏障:主线程等待多个子线程完成初始化或任务后才能继续执行。例如,在一个服务启动时,主线程需要等待所有组件和服务加载完毕。主线程创建一个CountDownLatch,并设置计数器为组件数量,每个组件加载完成后调用countDown()方法,当计数器归零时,主线程通过await()方法解除阻塞。
  2. 一次性活动:所有参与者准备好后开始执行一次性的活动,如比赛开始。想象一个学生跑步比赛的例子,所有参赛者(线程)准备就绪后,裁判(主线程)等待所有选手(通过CountDownLatch)表示准备完毕,然后鸣枪开始比赛。
  3. 批处理任务同步:在分布式系统中,可能需要等待多个独立的异步任务完成后再进行下一步操作。比如,你可能有一个工作线程池处理大量任务,主线程使用CountDownLatch来等待所有这些任务结束。
  4. 性能测试:在压力测试或性能基准测试中,可以用来确保所有并发线程都已开始执行任务后才开始测量时间,最后再等待所有线程完成以计算总耗时。
  5. 阶段控制:多阶段任务中,某个阶段需要等待前一阶段的所有工作全部完成。例如,在构建过程中,等待所有编译任务结束后再统一进行部署。
  6. 资源初始化:当某些共享资源必须在多个线程能够访问之前先完成初始化时,可以通过CountDownLatch来实现同步。

总的来看,CountDownLatch通常用于解决一个或多个线程必须等待一组其他线程完成各自的工作之后才能继续执行的问题。(仔细审视我们的超时案例,其编程模式与CountDownLatch通常要解决的问题一致)

2.Executor及其子类

Executor是java并发编程框架中的一个核心接口,位于java.util.concurrent包中。它定义了一种统一的方式来执行异步任务,即实现了Executor的对象能够接收Runnable对象作为任务,并负责安排这些任务在某一时刻执行。下面是Executor接口的源码:

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

从上述源码可以看出,execute()方法会接收一个Runnable类型的任务参数。该任务将在Executor管理的线程中异步执行。通过这个接口,我们可以将关注点从如何创建和管理线程转移到如何定义和提交执行的任务上,从而简化了多线程编程模型,提高了程序的可读性和可维护性。另外,java提供了Executors工具类,它是一个静态工厂类,提供了多种预配置的线程池实现,比如固定大小的线程池、单线程执行器、可缓存线程池等等,这些都是ExecutorService接口(该接口继承了Executor接口)的实现,进一步增强了对线程池功能的支持,包括任务调度、线程生命周期管理以及任务结果的获取等。下面让我们来研究一下java并发包提供的一个扩展自Executor 接口的重要接口——ExecutorService,它提供了更多管理和控制线程池的方法ExecutorService 提供了启动、执行和关闭线程池的能力,以及对异步任务的更丰富的控制,比如可以取消正在执行的任务,查询线程池状态,提交具有返回值的任务等。该接口中的主要方法包括:

  1. submit(Callable<T> task): 提交一个有返回值的任务,返回一个 Future 对象,通过该对象可以获取任务执行结果或者取消任务。
  2. submit(Runnable task, T result): 提交一个无返回值的任务,并提供一个结果,返回一个 Future 对象。
  3. execute(Runnable command): 执行一个 Runnable 类型的任务,与 Executor 接口中的方法一致。
  4. shutdown(): 关闭 ExecutorService,不再接收新任务,但会将已提交的任务执行完毕。
  5. shutdownNow(): 尝试停止所有正在执行的任务并返回尚未开始的任务列表。
  6. awaitTermination(long timeout, TimeUnit unit): 等待所有任务都已完成,或者超过指定时间后终止等待。
  7. invokeAll(Collection<? extends Callable<T>> tasks): 执行给定的任务集合,当所有任务完成或超时后返回包含每个任务结果的 Future 列表。
  8. invokeAny(Collection<? extends Callable<T>> tasks): 执行给定的任务集合,返回第一个成功完成的任务的结果。

通过 ExecutorService,我们可以更加方便地管理和控制并发任务,有效地利用系统资源,提高程序的性能和响应速度等。下面让我们一起来看一下这个接口的继承体系:

首先跟大家说声抱歉,今天无法完成本篇文章的主旨了,明天我会继续就文章主旨进行梳理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1433451.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

每日一练:LeeCode-513、找树左下角的值【二叉树】

本文是力扣LeeCode-513、找树左下角的值 学习与理解过程&#xff0c;本文仅做学习之用&#xff0c;对本题感兴趣的小伙伴可以出门左拐LeeCode。 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: …

图数据库(neo4j)在工业控制中的应用

最近看到国外发表的一篇文章&#xff0c;提到将OPC UA 模型映射到neo4j图模型数据库中&#xff0c;通过GraphQL 访问效率很高&#xff0c;顿时感觉自己眼睛一亮&#xff0c;这是一个好主意。 图模型 事物的模型中&#xff0c;除了它自身的某些特征之外&#xff0c;还包括它与其…

第十二讲_JavaScript浏览器对象模型BOM

JavaScript浏览器对象模型BOM 1. 浏览器对象模型介绍2. location2.1 常用的属性2.2 常用的方法 3. navigator3.1 常用的属性 4. history4.1 常用的方法&#xff1a; 5. 本地存储 1. 浏览器对象模型介绍 BOM(Browser Object Model) 是指浏览器对象模型&#xff0c;浏览器对象模…

Unity3D实现坦克大战

一、效果图演示 二、逻辑剖析 从界面上&#xff1a; 需要一个Canvas满屏对着用户&#xff0c;该Canvas上展示用户的游戏数据&#xff0c;比如血条。需要一个Canvas放在蓝色坦克上方&#xff0c;也需要实时对着用户&#xff0c;显示敌人的血条信息两个坦克一个平面Plane放草地…

Nice Touch

Nice Touch是Unity最简单的多点触控输入解决方案,来自Corgi Engine和Infinite Runner Engine的制造商。所有功能完整文档论坛 功能列表: • 超级简单的设置 •兼容Unity的新旧输入系统 • 内置多点触控输入 •适用于所有移动设备 • 虚拟操纵杆 • 动态虚拟操纵杆 • 可重新定…

WebChat——一个开源的聊天应用

Web Chat 是开源的聊天系统&#xff0c;支持一键免费部署私人Chat网页的应用程序。 开源地址&#xff1a;https://github.com/loks666/webchat 目录树 TOC &#x1f44b;&#x1f3fb; 开始使用 & 交流&#x1f6f3; 开箱即用 A 使用 Docker 部署B 使用 Docker-compose…

web前端-------弹性盒子(2)

上一讲我们谈的是盒子的容器实行&#xff0c;今天我们来聊一聊弹性盒子的项目属性&#xff1b; *******************&#xff08;1&#xff09;顺序属性 order属性&#xff0c;用于定义容器中项目的出现顺序。 顺序属性值&#xff0c;为整数&#xff0c;可以为负数&#xff…

由亚马逊云科技 Graviton4 驱动的全新内存优化型实例 Amazon EC2 实例(R8g),现已开放预览

下一代 Amazon Elastic Compute CloudAmazon EC2) 实例的预览版现已公开 提供。全新的 R8g 实例 搭载新式 Graviton4 处理器&#xff0c;其性价比远超任何现有的内存优化实例。对于要求较高的内存密集型工作负载&#xff0c;R8g 实例是不二之选&#xff1a;大数据分析、高性能数…

SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式 基础(持续更新~)

具体操作&#xff1a; day2: 作用&#xff1a; 出现跨域问题 配相对应进行配置即可解决&#xff1a; IDEA连接的&#xff0c;在url最后加参数?useSSLfalse注意链接密码是123&#xff08;docker中mysql密码&#xff09; 注意&#xff0c;虚拟机中设置的密码和ip要和主机上…

代码随想录算法训练营第17天 | 110.平衡二叉树, 257. 二叉树的所有路径 ,404.左叶子之和

二叉树理论基础&#xff1a; https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE 110.平衡二叉树 题目链接&#xff1a;https://leetcode.cn/problems/balanced-binary-tree…

2024最新最详细【接口测试总结】

序章 ​ 说起接口测试&#xff0c;网上有很多例子&#xff0c;但是当初做为新手的我来说&#xff0c;看了不不知道他们说的什么&#xff0c;觉得接口测试&#xff0c;好高大上。认为学会了接口测试就能屌丝逆袭&#xff0c;走上人生巅峰&#xff0c;迎娶白富美。因此学了点开发…

林浩然与杨凌芸的Java奇缘:一场继承大戏

林浩然与杨凌芸的Java奇缘&#xff1a;一场继承大戏 Lin Haoran and Yang Lingyun’s Java Odyssey: A Tale of Inheritance 在一个充满代码香气的午后&#xff0c;我们故事的男主角——林浩然&#xff0c;一个热衷于Java编程的程序员&#xff0c;正在和他的“梦中女神”、同样…

【蓝桥杯选拔赛真题64】python数字塔 第十五届青少年组蓝桥杯python 选拔赛比赛真题解析

python数字塔 第十五届蓝桥杯青少年组python比赛选拔赛真题 一、题目要求 (注:input()输入函数的括号中不允许添加任何信息) 提示信息: 数字塔是由 N 行数堆积而成,最顶层只有一个数,次顶层两个数,以此类推。相邻层之间的数用线连接,下一层的每个数与它上一层左上…

JS第二天、原型、原型链、正则

☆☆☆☆ 什么是原型&#xff1f; 构造函数的prototype 就是原型 专门保存所有子对象共有属性和方法的对象一个对象的原型就是它的构造函数的prototype属性的值。prototype是哪来的&#xff1f;所有的函数都有一个prototype属性当函数被创建的时候&#xff0c;prototype属性…

机器学习超参数优化算法(贝叶斯优化)

文章目录 贝叶斯优化算法原理贝叶斯优化的实现&#xff08;三种方法均有代码实现&#xff09;基于Bayes_opt实现GP优化基于HyperOpt实现TPE优化基于Optuna实现多种贝叶斯优化 贝叶斯优化算法原理 在贝叶斯优化的数学过程当中&#xff0c;我们主要执行以下几个步骤&#xff1a; …

前端 - 基础 列表标签 - 自定义列表 详解

使用场景 &#xff1a; 常用于对术语或名词进行解释和描述&#xff0c;定义列表的列表前没有任何项目符号。 在 HTML 标签中&#xff0c; < dl > 标签用于定义 描述列表 &#xff08; 或定义列表 &#xff09; 该标签会与 <dt> ( 定义项目/名字 ) 和 <dd…

vue不同环境配置不同打包命令

这个需求非常普遍&#xff0c;通常情况我们在开发的时候一般会有三个环境&#xff1a;开发环境、测试环境、生产环境&#xff0c;我们一步步来看下。 vue环境变量是什么&#xff1f; 指的是在不同地方&#xff08;开发环境、测试环境、生产环境&#xff09;&#xff0c;变量就…

【教学类-46-04】吉祥字门贴4.0(华文彩云 文本框 空心字涂色 建议简体)

作品展示 背景需求&#xff1a; 1、制作了空心字的第1款 华光通心圆_CNKI &#xff0c;发现它不能识别某些简体字&#xff0c;但可以识别他们的繁体字&#xff08;繁体为准&#xff09; 【教学类-46-01】吉祥字门贴1.0&#xff08;华光通心圆_CNKI 文本框 空心字涂色&#xf…

3D Line Mapping Revisited论文阅读

1. 代码地址 GitHub - cvg/limap: A toolbox for mapping and localization with line features. 2. 项目主页 3D Line Mapping Revisited 3. 摘要 提出了一种基于线的重建算法&#xff0c;Limap&#xff0c;可以从多视图图像中构建3D线地图&#xff0c;通过线三角化、精心…

如何在 Microsoft Azure 上部署和管理 Elastic Stack

作者&#xff1a;来自 Elastic Osman Ishaq Elastic 用户可以从 Azure 门户中查找、部署和管理 Elasticsearch。 此集成提供了简化的入门体验&#xff0c;所有这些都使用你已知的 Azure 门户和工具&#xff0c;因此你可以轻松部署 Elastic&#xff0c;而无需注册外部服务或配置…