Spring Cloud 学习(3)--- Hystrix与断路器

一、 什么是灾难性的雪崩效应

在微服务架构的项目中,尤其是中大型项目,肯定会出现一个服务调用其他的服务,其他服务又调用别的服务,服务和服务之间形成了一种链式的调用关系。

当少量请求时,对于整个服务链条是没有过多的影响的。

image.png

虽然每个服务的请求都是少量的,但是最终都访问服务T。所以对于服务T来说请求量就是比较大的。所在的服务器CPU压力比较高。

image.png

当其中某一个服务突然遇到大量请求时。整个链条上所有服务负载骤增。

image.png

导致服务U和服务T的负载过高。运行性能下降。会导致其他调用服务U和服务T的链条出现问题。从而所有的项目可能都出现的问题。

这种情况就称为灾难性的雪崩效应。

image.png

造成灾难性雪崩效应的原因,可以简单归结为下述三种:

服务提供者(Application Service)不可用。如:硬件故障、程序BUG、缓存击穿、并发请求量过大等。

重试加大流量。如:用户重试、代码重试逻辑等。

服务调用者(Application Client)不可用。如:同步请求阻塞造成的资源耗尽等。

雪崩效应最终的结果就是:服务链条中的某一个服务不可用,导致一系列的服务不可用,最终造成服务逻辑崩溃。这种问题造成的后果,往往是无法预料的。

二、如何防止灾难性雪崩效应

降级
超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值.
保证:服务出现问题整个项目还可以继续运行。
熔断
当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快速失败会进行快速恢复。
通俗理解:熔断就是具有特定条件的降级,当出现熔断时在设定的时间内容就不在请求Application Service了。所以在代码上熔断和降级都是一个注解
保证:服务出现问题整个项目还可以继续运行。
请求缓存
提供了请求缓存。服务A调用服务B,如果在A中添加请求缓存,第一次请求后走缓存了,就不在访问服务B了,即使出现大量请求时,也不会对B产生高负载。
请求缓存可以使用Spring Cache实现。
保证:减少对Application Service的调用。
请求合并
提供请求合并。当服务A调用服务B时,设定在5毫秒内所有请求合并到一起,对于服务B的负载就会大大减少,解决了对于服务B负载激增的问题。
保证:减少对Application Service的调用。
隔离
隔离分为线程池隔离和信号量隔离。通过判断线程池或信号量是否已满,超出容量的请求直接降级,从而达到限流的作用。

三、Hystrix简介

  1. 在Spring Cloud中解决灾难性雪崩效应就是通过Spring Cloud Netflix Hystrix实现的。
  2. Hystrix [hɪst'rɪks],中文含义是豪猪,因其背上长满棘刺,从而拥有了自我保护的能力。本文所说的Hystrix(中文:断路器)是Netflix开源的一款容错框架,同样具有自我保护能力。
  3. 通俗解释:Hystrix就是保证在高并发下即使出现问题也可以保证程序继续运行的一系列方案。作用包含两点:容错和限流。
  4. 在Spring cloud中处理服务雪崩效应,都需要依赖hystrix组件。在Application Client应用的pom文件中都需要引入下述依赖:
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

四、降级

  1. 降级是指,当请求超时、资源不足等情况发生时进行服务降级处理,不调用真实服务逻辑,而是使用快速失败(fallback)方式直接返回一个托底数据,保证服务链条的完整,避免服务雪崩。
  2. 解决服务雪崩效应,都是避免application client请求application service时,出现服务调用错误或网络问题。处理手法都是在application client中实现。我们需要在application client相关工程中导入hystrix依赖信息。并在对应的启动类上增加新的注解@EnableCircuitBreaker,这个注解是用于开启hystrix熔断器的,简言之,就是让代码中的hystrix相关注解生效。

Dome

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
spring:
  application:
    name: fallback-demo
eureka:
  client:
    service-url:
      defaultZone: http://eurekaserver1:8761/eureka/


server:
  port: 8081

@Configuration
public class RibbonConfig {
    //此处使用@LoadBalancer方式快捷配置负载均衡。
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

接下来就是service、controller、application启动

public interface DemoService {
    String test();
}

@Service
public class DemoServiceImpl implements DemoService {
    @Autowired
    private RestTemplate restTemplate;
    @HystrixCommand(fallbackMethod = "myFallback")
    @Override
    public String test() {
        String result = restTemplate.postForObject("http://application-service-demo/demo", null, String.class);
        System.out.println(result);
        return result;
    }

    public String myFallback(){
        return "托底数据";
    }
}

@Controller
public class FallbackController {

    @Autowired
    private DemoService demoService;

    @RequestMapping("/demo")
    @ResponseBody
    public String demo(){
        return demoService.test();
    }
}  

@SpringBootApplication
@EnableCircuitBreaker
public class ApplicationClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApplicationclientdemoApplication.class, args);
    }

}

五、熔断

  1. 当一定时间内,异常请求比例(请求超时、网络故障、服务异常等)达到阀值时,启动熔断器,熔断器一旦启动,则会停止调用具体服务逻辑,通过fallback快速返回托底数据,保证服务链的完整。
  2. 熔断有自动恢复机制,如:当熔断器启动后,每隔5秒,尝试将新的请求发送给Application Service,如果服务可正常执行并返回结果,则关闭熔断器,服务恢复。如果仍旧调用失败,则继续返回托底数据,熔断器持续开启状态。
  3. 降级是出错了返回托底数据,而熔断是出错后如果开启了熔断将会一定时间不在访问application service


    image.png

1、属性

HystrixCommand里面的属性
熔断的实现是在调用远程服务的方法上增加@HystrixCommand注解。当注解配置满足则开启或关闭熔断器。
@HystrixProperty的name属性取值可以使用HystrixPropertiesManager常量,也可以直接使用字符串进行操作。

CIRCUIT_BREAKER_ENABLED
"circuitBreaker.enabled";
是否开启熔断策略。默认值为true。


CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD
"circuitBreaker.requestVolumeThreshold";
单位时间内(默认10s内),请求超时数超出则触发熔断策略。默认值为20次请求数。通俗说明:单位时间内容要判断多少次请求。

EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS
"execution.isolation.thread.timeoutInMilliseconds"
设置单位时间,判断circuitBreaker.requestVolumeThreshold的时间单位,默认10秒。单位毫秒。


CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS
"circuitBreaker.sleepWindowInMilliseconds";
当熔断策略开启后,延迟多久尝试再次请求远程服务。默认为5秒。单位毫秒。这5秒直接执行fallback方法,不在请求远程application service。

CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE
"circuitBreaker.errorThresholdPercentage";
单位时间内,出现错误的请求百分比达到限制,则触发熔断策略。默认为50%。

CIRCUIT_BREAKER_FORCE_OPEN
"circuitBreaker.forceOpen";
是否强制开启熔断策略。即所有请求都返回fallback托底数据。默认为false。

CIRCUIT_BREAKER_FORCE_CLOSED
"circuitBreaker.forceClosed";
是否强制关闭熔断策略。即所有请求一定调用远程服务。默认为false。
@HystrixCommand(fallbackMethod = "myFallback",commandProperties = {
        // 条件一: 请求数量到达3个
        @HystrixProperty(name= "circuitBreaker.requestVolumeThreshold",value="3"),
        //可以这样写@HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD ,value="3")
        // 判断时间,没10秒作为一个判断单位
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="10000"),
        // 条件二: 失败了到达50%
        @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="50"),
        // 结果: 开启熔断后,30秒不在请求远程服务
        @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value = "30000")
})

六、请求缓存

Hystrix为了降低访问服务的频率,支持将一个请求与返回结果做缓存处理。如果再次请求的URL没有变化,那么Hystrix不会请求服务,而是直接从缓存中将结果返回。这样可以大大降低访问服务的压力。

Hystrix自带缓存。有两个缺点:

  1. 是一个本地缓存。在集群情况下缓存是不能同步的。
  2. 不支持第三方缓存容器。Redis,memcached不支持的。
    所以可以利用spring cache。实现请求缓存。
    在降级处理的代码基础上完成下面变化。

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

修改配置文件
添加redis的配置,此处使用的是redis单机版。如果是redis集群使用spring.redis.cluster.nodes进行配置。

spring:
  redis:
    host: ipaddress #你的redis的IP地址

注意添加东西

  • 在启动类上添加@EnableCaching注解
  • 在service实现类方法上面额外在添加一个注解。
@Cacheable(key = "'key值'",cacheNames = "缓存名字")

七、请求合并

没有请求合并

Application Service 负载是Application Client发送请求的总数量

image.png

请求合并

把一段时间范围内的所有请求合并为一个请求。大大的降低了Application Service 负载。

image.png

什么情况下使用请求合并

在微服务架构中,我们将一个项目拆分成很多个独立的项目,这些独立的项目通过远程调用来互相配合工作,但是,在高并发情况下,通信次数的增加会导致总的通信时间增加,同时,线程池的资源也是有限的,高并发环境会导致有大量的线程处于等待状态,进而导致响应延迟,为了解决这些问题,我们需要来了解Hystrix的请求合并。

请求合并的缺点

设置请求合并之后,本来一个请求可能5ms就搞定了,但是现在必须再等10ms看看还有没有其他的请求一起的,这样一个请求的耗时就从5ms增加到15ms了,不过,如果我们要发起的命令本身就是一个高延迟的命令,那么这个时候就可以使用请求合并了,因为这个时候时间窗的时间消耗就显得微不足道了,另外高并发也是请求合并的一个非常重要的场景。

请求合并参数介绍

image.png

  1. @HystrixCollapser 进行请求合并
  2. batchMethod:处理请求合并的方法
  3. scope - 合并请求的请求作用域。可选值有global和request。
    global代表所有的请求线程都可以等待可合并。 常用
    request代表一个请求线程中的多次远程服务调用可合
  4. timerDelayInMilliseconds:等待时长,默认10毫秒。
  5. maxRequestInBatch:最大请求合并数量。
  6. @HystrixCommand 处理请求合并的方法必须有此注解。
  • 实现类中client(String)方法一旦被@HystrixCollapser标记,方法就不会被执行,方法体中为空即可。直接执行batchMethod对应的方法。
  • batchMethod方法返回值顺序和传递进来的参数顺序有关系的。
    注意:
    在实际测试中scope使用默认值REQUEST会出现空指针异常,请换成GLOBAL
Future<String> client(String name);
//上面是接口
@Override
@HystrixCollapser(batchMethod = "myBatchMethod",scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL
,collapserProperties = {@HystrixProperty(name="timerDelayInMilliseconds",value="10"),@HystrixProperty(name="maxRequestsInBatch",value = "200")})
public Future<String> client(String name) {
    System.out.println("client方法,有请求合并时将不支持这个方法");
    return null;
}

@HystrixCommand
public List<String> myBatchMethod(List<String> name){
    System.out.println("传递过去的参数:"+name);
    List<String> list = restTemplate.postForObject("http://APPLICATION-SERVICE/service2", name, List.class);
    return list;
}

八、隔离

1、线程隔离

1.1 为什么使用线程池隔离

没有线程池隔离的时候可能因为某个接口的高并发导致其他接口也出现问题。

image.png

当使用线程池隔离。不同接口有着自己独立的线程池

image.png

即使某个线程池都被占用,也不影响其他线程。

image.png

1.2 Hystrix的线程池隔离

Hystrix采用Bulkhead Partition舱壁隔离技术。

舱壁隔离指的的是讲船体内部分为多个隔舱,一旦其中某几个隔舱发生破损进水,水流不会在其他舱壁中流动,从而保证船舱依然具有足够的浮力和稳定性,降低沉船危险。


image.png

1.3线程池隔离的优缺点

  • 优点:
  1. 任何一个服务都会被隔离在自己的线程池内,即使自己的线程池资源填满也不会影响其他服务。
  2. 当依赖的服务重新恢复时,可通过清理线程池,瞬间恢复服务的调用。但是如果是tomcat线程池被填满,再恢复就会很麻烦。
  3. 每个都是独立线程池。一定程度上解决了高并发问题。
  4. 由于线程池中线程个数是有限制,所以也解决了限流问题。
  • 缺点:
  1. 增加了CPU开销。因为不仅仅有Tomcat的线程池,还需要有Hystrix线程池。
  2. 每个操作都是独立的线程,就有排队、调度和上下文切换等问题。
    代码:
    //代码只是把这个注解加到实现类的方法上面就好了
    //注解里面的意思是一个服务名为groupKey = "jqk",服务接口是commandKey = "abc",虽然自定义但是一般为方法名,线程名称是threadPoolKey = "jqk"
    @HystrixCommand(groupKey = "jqk",commandKey = "abc",threadPoolKey = "jqk",threadPoolProperties = {
        //threadPoolProperties线程池里面的配置,最大并发也是最大数量name="coreSize",value="8"
        @HystrixProperty(name="coreSize",value="8"),
        //设置了最大队列长度name="maxQueueSize",value="5"
        @HystrixProperty(name="maxQueueSize",value="5"),
        //设置了线程最大存活时间name="keepAliveTimeMinutes",value="2"单位分钟
        @HystrixProperty(name="keepAliveTimeMinutes",value="2"),
        //设置了拒绝请求临界值为name="queueSizeRejectionThreshold",value="5",在maxQueueSize的基础之上再加这个值设置数量超过就拒绝请求响应了
        @HystrixProperty(name="queueSizeRejectionThreshold",value="5")
    })
    @Override
    public String thread() {
        System.out.println(Thread.currentThread().getName());
        return "thread1";
    }
    @Override
    public String thread2() {
        System.out.println(Thread.currentThread().getName());
        return "thread2";
    }

参数说明

image.png

2、信号量隔离

2.1信号量是什么

  1. java.util.concurrent.Semaphore用来控制可同时并发的线程数。通过构造方法指定内部虚拟许可的数量。每次线程执行操作时先通过acquire方法获得许可,执行完毕再通过release方法释放许可。如果无可用许可,那么acquire方法将一直阻塞,直到其它线程释放许可。
  2. 如果采用信号量隔离技术,每接收一个请求,都是服务自身线程去直接调用依赖服务,信号量就相当于一道关卡,每个线程通过关卡后,信号量数量减1,当为0时不再允许线程通过,而是直接执行fallback逻辑并返回,说白了仅仅做了一个限流。
    代码演示
@HystrixCommand(commandProperties = {
        //.EXECUTION_ISOLATION_STRATEGY默认就是线程池隔离,现在改为SEMAPHORE信号隔离
        @HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value="SEMAPHORE"),
        //信号最大并发度
        @HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value="10")
    },fallbackMethod = "jqk")
    @Override
    public String semaphore() {
        System.out.println("执行了,访问service");

参数说明

image.png

线程池隔离和信号量隔离
image.png

限流说明

  1. 在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方面是为了防止网络攻击。
  2. 通过Hystrix的线程池隔离和信号量隔离控制了线程数量也就实现了限流效果。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容