hystrix线程池隔离的原理与验证

简介: hystrix可以完成隔离、限流、熔断、降级这些常用保护功能。这四个功能可以这么来理解: hystrix的隔离分为线程池隔离和信号量隔离。

引子

 


幸福很简单:

 

今天项目半年规划被通过,终于可以早点下班。先坐公交,全程开着灯,买了了几天的书竟然有时间看了。半小时后,公交到站,换乘大巴车。车还等着上人的功夫,有昏暗的灯光,可以继续看会儿书。过会儿车跑起来了,灯关了。我合上书,头靠着车窗,眼睛看着窗外,脑子想着怎么把书里的东西用到工作中进行知行合一。想着想着出了神,突然听到报我们小区的名字,赶紧下了车,刚好没坐过站。

 

回家一看,那个声称今天会下班很晚的人果然比我还晚。边洗漱边想着上周末,和小鲜肉一起看了动画片。小鲜肉非要我买培根。因为他想像动画片里一样用两个荷包蛋做眼睛,培根做嘴巴。于是晚上睡觉的时候,我下单买了培根,早上7点快递送了来。我就和小鲜肉一起做早餐。他说想吃糖口味的蛋,于是我们改良了一下把荷包蛋眼睛变成摊鸡蛋大脸。本来小鲜肉说只要一片培根。我想一包都打开了,干脆一起煎了。结果因为是自己动手,小鲜肉只好多吃,结果吃培根吃的一天都感觉油腻。想着这周,一包培根要分到一天三顿里,加上配菜,和小鲜肉一起做出三种花样来。关键是小鲜肉到周末比我还忙,8点开始跆拳道,一天各种兴趣班。他想自己做早餐得早起。


1112728-20201023093205214-782438303.png


洗漱完,吹完头发。看看洗澡水又烧的差不多了。这次终于轮到我发那条消息:“洗澡水够了”。然后拔掉烧水的电源确保安全。竟然有时间贴上面膜再看会书,满足。

 

原来幸福就是:其实很忙,但是能挤出点时间干点自己的事情,提升提升自己。

 

hystrix隔离原理

 


hystrix可以完成隔离、限流、熔断、降级这些常用保护功能。这四个功能可以这么来理解:

 

hystrix的隔离分为线程池隔离和信号量隔离。

 

信号量隔离原理

 


信号量隔离就是hystrix的限流功能。虽然名字叫隔离,实际上它是通过信号量来实现的。而信号量说白了就是个计数器。计数器计算达到设定的阈值,直接就做异常处理。

 

ratelimiter的令牌桶算法和漏桶算法,都是直接对请求量来计数。只是令牌桶算法可以将前面一段时间没有用掉的请求量允许余额拿过继续用。而漏桶算法一段时间就允许这么多,前面没用掉的也不能用了。

 

而hystrix信号量隔离限制的是tomcat等Web容器线程数,一段时间仅仅能支持这么多。多余的请求再来请求线程资源,就被拒绝了。所以是一种“曲径通幽”的限流方式。因为实际是通过隔离了部分容器线程资源,也算是一种隔离方式。

 

线程池隔离原理

 


信号量隔离只是起了个限制作用,它的保护能力有限:如果下游服务有问题,长时间不返回结果。本身信号量隔离对这个单个请求是起不到任何作用的。它只能限制这样的请求太多了就拒绝,不让整个服务挂。

 

为了解决这个问题,hystrix又产生了线程池隔离。这种隔离方式是通过引入额外线程的方式。对原来的web容器线程做管理控制:如果一个线程超时未返回,则熔断。既然引入额外的线程就涉及线程池管理、线程的上下文切换这些额外的开销。所以相比信号量隔离,线程池隔离成本更高。

 

熔断原理

 


隔离不但可以做保护,还可以做统计:成功了多少失败了多少。既然有统计数据了,它就可以进一步处理:失败太多了,说明现在有问题,执行完了再发现失败太浪费资源,干脆就先不让worker线程执行了。过段时间再试试。这就是断路器模式,也就是熔断的原理。

 

降级原理

 


任何异常需要熔断的场景,为的都是反正都是错,干脆把这资源省了。直接返回一个预定的错误。这个熔断后返回设定错误的过程就是降级。

 

 

资源保护的流程

 


从上面来看所谓的hystrix的四大功能:限流、隔离、熔断和降级。只是完成了一整个对资源保护的生命周期。来看看对应的BPMN流程图:

 

信号量隔离的流程


1112728-20201023093233026-522184034.png


线程池隔离的流程


1112728-20201023093311036-1324943040.png


现在大家请回答我一个问题:我上面说的是对的吗?

 

hystrix隔离验证

 


采用淘金式的思维,不要别人说什么都信。网上很多技术博客里说的都是错的。我的文章也有可能是错的,事实上我说的很多东西都带有自己脑补的成分。

 

既然不确定是否是对的,就要去验证。先验证


1>hystrix隔离确实能限制资源


2>信号量隔离采用的Web容器的线程池,而线程池隔离采用的是自己独立的线程池。

 

本次验证,Web容器使用的是spring boot内嵌的jetty。

 

信号量隔离验证

 


隔离hystrix配置


import com.netflix.hystrix.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class DemoHystrixCommand extends HystrixCommand<String> {
    private static final Logger logger = LoggerFactory.getLogger(DemoHystrixCommand.class);
    private String poolName;
    public DemoHystrixCommand() {
        super(Setter.withGroupKey(
                //服务分组
                HystrixCommandGroupKey.Factory.asKey("DemoGroup"))
                //线程分组
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("DemoPool"))
                //线程池配置
                .andThreadPoolPropertiesDefaults(
                        HystrixThreadPoolProperties.Setter()
                                .withCoreSize(2)
                                .withKeepAliveTimeMinutes(5)
                                .withMaxQueueSize(2)
                                .withQueueSizeRejectionThreshold(10))
                //线程池隔离
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.Setter()
                                .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
                )
        );
    }
    @Override
    protected String run() throws Exception {
        logger.info(poolName + ":我伤心我无奈,可是我默默等待");
        TimeUnit.MILLISECONDS.sleep(100);
        return poolName + "-run:缘分就是一生的等待";
    }
    public void setPoolName(String poolName) {
        this.poolName = poolName;
    }
}


调用方


import com.brmayi.yuna.util.DemoHystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class HystrixController {
    @Autowired
    private ApplicationContext applicationContext;
    @GetMapping("/hystrix")
    public String hystrix(String poolName) throws Exception {
        DemoHystrixCommand demoHystrixCommand = applicationContext.getBean(DemoHystrixCommand.class);
        demoHystrixCommand.setPoolName(poolName);
        return demoHystrixCommand.execute();
    }
}


验证


1112728-20201023093416511-1027957073.png


多点几次


1112728-20201023093437469-1449595913.png


多点几次


1112728-20201023093502973-1558547790.png


看日志,日志前缀里Web容器的其他日志线程号和请求hystrix的线程号规则一致,可说明是Web容器的线程。


注意看,不管是倩倩还是萍儿,它们的线程数都没有超过最大线程数

 

线程池隔离验证

 


只要将隔离hystrix配置


HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE


改成


HystrixCommandProperties.ExecutionIsolationStrategy.THREAD


重启后重复上面验证步骤


1112728-20201023093934705-1240767905.png


看线程池名变成了隔离hystrix配置的线程名规则!并且不管是倩倩还是萍儿,它们的线程数都没有超过最大线程数。

 

以上实验说明


1>hystrix隔离确实能限制资源


2>信号量隔离采用的Web容器的线程池,而线程池隔离采用的是自己独立的线程池。

成立。

 

其他部分限于篇幅,我就不验证了。这里用到了打日志的方法验证线程池情况。如果在生产环境,实际上我们是需要对线程池情况做监控的。可以使用java.lang.management包里的工具注册监控。我们平时使用falcon这样的工具来查看,原理也是先通过java.lang.management包里的工具注册上报信息到服务端采集的。如果自己想查看监控结果,可以用jdk自带的jvisualvm安装一个com-sun-tools-visualvm-modules-mbeans.nbm插件来看。1112728-20201023093851857-831941036.png

1112728-20201023093851857-831941036.png


总结


 

本篇文章的验证部分很粗糙,限于篇幅,没有把所有需要验证的点覆盖全。想验证我花的hystrix资源保护生命周期的图,至少要结合源码和验证两方面。先当留作业了,有时间我把详细过程补上。


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
8天前
|
Java
并发编程之线程池的底层原理的详细解析
并发编程之线程池的底层原理的详细解析
44 0
|
24天前
|
Java 调度
Java并发编程:深入理解线程池的原理与实践
【4月更文挑战第6天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将从线程池的基本原理入手,逐步解析其工作过程,以及如何在实际开发中合理使用线程池以提高程序性能。同时,我们还将关注线程池的一些高级特性,如自定义线程工厂、拒绝策略等,以帮助读者更好地掌握线程池的使用技巧。
|
1月前
|
算法 安全 调度
多线程如何工作,工作原理是什么
多线程如何工作,工作原理是什么
|
3月前
|
存储 安全 调度
探索Python的多线程编程:原理与实践
探索Python的多线程编程:原理与实践
60 0
|
30天前
|
存储 安全 Java
Java线程池ThreadPoolExcutor源码解读详解04-阻塞队列之PriorityBlockingQueue原理及扩容机制详解
1. **继承实现图关系**: - `PriorityBlockingQueue`实现了`BlockingQueue`接口,提供了线程安全的队列操作。 - 内部基于优先级堆(小顶堆或大顶堆)的数据结构实现,可以保证元素按照优先级顺序出队。 2. **底层数据存储结构**: - 默认容量是11,存储数据的数组会在需要时动态扩容。 - 数组长度总是2的幂,以满足堆的性质。 3. **构造器**: - 无参构造器创建一个默认容量的队列,元素需要实现`Comparable`接口。 - 指定容量构造器允许设置初始容量,但不指定排序规则。 - 可指定容量和比较
230 2
|
1月前
|
设计模式 安全 C++
【C++ const 函数 的使用】C++ 中 const 成员函数与线程安全性:原理、案例与最佳实践
【C++ const 函数 的使用】C++ 中 const 成员函数与线程安全性:原理、案例与最佳实践
238 2
|
1月前
|
Java 程序员 API
【深入探究 Qt 线程】一文详细解析Qt线程的内部原理与实现策略
【深入探究 Qt 线程】一文详细解析Qt线程的内部原理与实现策略
79 0
|
1月前
|
Java API 开发者
springboot 多线程的使用原理与实战
在Spring Boot中实现多线程,主要依赖于Spring框架的@Async注解以及底层Java的并发框架。这里将深入剖析Spring Boot多线程的原理,包括@Async注解的工作方式、任务执行器的角色以及如何通过配置来调整线程行为。
44 5
|
1月前
|
负载均衡 Java 数据处理
【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用(三)
【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用
56 2
|
1月前
|
存储 监控 Java
【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用(二)
【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用
45 1