Striveonger

vuePress-theme-reco Mr.Lee    2015 - 2025
Striveonger Striveonger
主页
分类
  • 文章
  • 笔记
  • 工具
标签
时间轴
author-avatar

Mr.Lee

264

Article

134

Tag

主页
分类
  • 文章
  • 笔记
  • 工具
标签
时间轴

SpringCloud 学习笔记-Hystrix(六)

vuePress-theme-reco Mr.Lee    2015 - 2025

SpringCloud 学习笔记-Hystrix(六)

Mr.Lee 2021-05-11 11:16:23 SpringCloudHystrix

SpringCloud 学习笔记, 第六章 Hystrix

# 雪崩场景模拟

image-20210513165204934

用户使用客户端, 调用暴露在外的 Server A 服务. 而 Server A 又调用了 Server B, Server B 中又需要调用 Server C , Server D

  • 在时间点(A) Server D 挂掉
  • 时间点(B) 由于 Server B 在调用 Server D 时, 因为 Server D TimeOut, 导致 Server B 挂掉
  • 同理: 时间点(C) 由于 Server A 在调用 Server B 时, 因为 Server B TimeOut, 导致 Server A 挂掉
  • 即使Server C 可以对外正常提供服务, 整个服务链, 也是挂掉了.

# Hystrix 介绍

在微服务架构中,服务与服务之间通过远程调用的方式进行通信,一旦某个被调用的服务发生了故障,其依赖服务也会发生故障,此时就会发生故障的蔓延,最终导致系统瘫痪。Hystrix实现了断路器模式,当某个服务发生故障时,通过断路器的监控,给调用方返回一个错误响应,而不是长时间的等待,这样就不会使得调用方由于长时间得不到响应而占用线程,从而防止故障的蔓延。Hystrix具备服务降级、服务熔断、线程隔离、请求缓存、请求合并及服务监控等强大功能。

# 熔断

熔断机制是应对雪崩效应的一种微服务链路保护机制. 当扇出链路的某个Server 不可用或者响应时间太长时, 会进行服务的降级, 进而熔断该服务节点的调用, 快速返回”错误”的响应信息.

# 熔断来源

我们家用电闸上都有保险丝模块, 当电压出现短路问题时, 自动跳闸, 此刻电路主动断开, 我们的电器就会收到保护. 否则, 不能断开的话, 后果不堪设想.

# 微服务的熔断

在分布式系统中, 我们往往需要依赖下游服务, 不管是内部系统还是第三方服务. 如果下游出现问题, 我们还是盲目地去请求, 即使失败了多次, 还是傻傻的去请求, 去等待. 这样, 会增加了整个链路的响应时间, 可能会造成上层服务的Timeout

# 熔断的作用

熔断器, 可以防止应用程序不断地尝试可能超时和失败的服务, 能达到应用程序执行而不必等待下游服务修正错误服务.

熔断器, 是能让应用程序自我诊断下游系统的错误是否已修正. 如果没有, 不放量去请求, 如果请求成功了, 慢慢的增加请求, 再次尝试调用.

熔断的示意图

看下面的代码:

package com.striveonger.demo.client.hello.web.config;

import com.netflix.hystrix.*;

/**
 * @description:
 * @author: Mr.Lee
 * @date: 2021-05-12 14:09
 */
public class CustomCircuitBreakerCommand extends HystrixCommand<String> {

    public CustomCircuitBreakerCommand() {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("CommandKey"))
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("threadPoolKey"))
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.Setter()
                                .withCircuitBreakerEnabled(true)                           // 默认是true, 用于控制是否启用服务降级
                                .withCircuitBreakerForceOpen(false)                        // 默认是false, 强制打开断路器, 拒绝所有请求
                                .withCircuitBreakerForceClosed(false)                      // 默认是false, 强制关闭断路器, 接收所有请求
                                .withExecutionTimeoutInMilliseconds(2000)                  // run 方法的执行超时时间
                                .withMetricsRollingStatisticalWindowInMilliseconds(10000)  // 设置断路器窗口检查时长为10s
                                .withCircuitBreakerRequestVolumeThreshold(5)               // 1. 设置断路器10秒内的流量阀值(10s内调用, 超过5次)
                                .withCircuitBreakerErrorThresholdPercentage(0)             // 2. 错误百分比超过0% (同时超过1, 2条件的阀值时, 熔断器打开)
                                .withCircuitBreakerSleepWindowInMilliseconds(1000)         // 隔1s之后, 熔断器会尝试半开(关闭), 重新放进来请求
                )
                .andThreadPoolPropertiesDefaults(
                        HystrixThreadPoolProperties.Setter()
                                .withMaxQueueSize(10)    // 配置队列大小
                                .withCoreSize(3)         // 配置线程池里的线程数
                )
        );

    }

    @Override
    protected String run() throws Exception {
        // 模拟业务耗时
        Thread.sleep(100);
        return "Hello...";
    }

    @Override
    protected String getFallback() {
        /*
         * 执行 Fallback 的四种情况:
         *   1. run 方法 throw Exception
         *   2. 请求超时
         *   3. 超出线程数
         *   4. 断路开关打开 isCircuitBreakerOpen() = true
         */
        return "Fallback...";
    }
}

class TestHystrixCommand {

    public static void main(String[] args) throws Exception {
      for (int i = 0; i < 25; i++) {
            Thread.sleep(100);
            HystrixCommand<String> command = new CustomCircuitBreakerCommand();
            String result = command.execute();
            System.out.println(String.format("call times: %02d; result: %s; isCircuitBreakerOpen: %s", i + 1, result, command.isCircuitBreakerOpen()));
        }
        /*
         * 执行结果:
         *
         * call times: 01; result: Hello...; isCircuitBreakerOpen: false
         * call times: 02; result: Hello...; isCircuitBreakerOpen: false
         * call times: 03; result: Hello...; isCircuitBreakerOpen: false
         * call times: 04; result: Hello...; isCircuitBreakerOpen: false
         * call times: 05; result: Hello...; isCircuitBreakerOpen: false
         * call times: 06; result: Fallback...; isCircuitBreakerOpen: true // 超过 流量阀值: 5(执行时间, 0.5s)
         * call times: 07; result: Fallback...; isCircuitBreakerOpen: true
         * call times: 08; result: Fallback...; isCircuitBreakerOpen: true
         * call times: 09; result: Fallback...; isCircuitBreakerOpen: true
         * call times: 10; result: Fallback...; isCircuitBreakerOpen: true
         * call times: 11; result: Fallback...; isCircuitBreakerOpen: true
         * call times: 12; result: Fallback...; isCircuitBreakerOpen: true
         * call times: 13; result: Fallback...; isCircuitBreakerOpen: true
         * call times: 14; result: Fallback...; isCircuitBreakerOpen: true
         * call times: 15; result: Fallback...; isCircuitBreakerOpen: true
         * call times: 16; result: Hello...; isCircuitBreakerOpen: false // 熔断器半开状态, 重试请求(成功, 重置流量阀值)
         * call times: 17; result: Hello...; isCircuitBreakerOpen: false
         * call times: 18; result: Hello...; isCircuitBreakerOpen: false
         * call times: 19; result: Hello...; isCircuitBreakerOpen: false
         * call times: 20; result: Hello...; isCircuitBreakerOpen: false
         * call times: 21; result: Hello...; isCircuitBreakerOpen: true // 再次超过 流量阀值: 5(执行时间, 2.1s) 
         * call times: 22; result: Fallback...; isCircuitBreakerOpen: true
         * call times: 23; result: Fallback...; isCircuitBreakerOpen: true
         * call times: 24; result: Fallback...; isCircuitBreakerOpen: true
         * call times: 25; result: Fallback...; isCircuitBreakerOpen: true
         */
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

上面的Demo中, 熔断规则:

  1. 单位时间(10s)内的请求超过流量阀值(5)
  2. 失败百分比大于阀值(0%, 有错就会熔断)

其中, 较为重要的 熔断开关: isCircuitBreakerOpen, 下面还会用到哦~

# 降级

# 降级的本质

降级就是为了解决资源不足和访问量增加的矛盾.

# 降级牺牲的是什么

从强一致性 变成 最终一致性

大多数的系统是不需要强一致性的. 强一致性就要求多种资源的占用, 减少强一致性就能释放更多资源. 这也是我们一般利用消息中间件来削峰填谷.

# 隔离

# 线程池隔离
# 信号量隔离

# 请求缓存

# 请求合并

参考文章:

  • https://www.cnblogs.com/yufeng218/p/11042555.html
  • https://cloud.spring.io/spring-cloud-netflix/multi/multi__circuit_breaker_hystrix_clients.html