SpringCloud-Netflix-07-Hystrix 服务熔断

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 SpringCloud-Netflix-07-Hystrix 服务熔断,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文


七、Netflix Hystrix 服务熔断

7.1 Hystrix 简介

7.1.1 雪崩效应

在微服务架构中通常会有多个服务级联的调用,在级联调用过程中,某个下游服务如果出现故障或阻塞,很可能造成服务的消费者也处于阻塞状态,造成线程资源占用,如果此时有大量请求访问,线程资源很容易被销毁完毕,导致整个系统的瘫痪;这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。

SpringCloud-Netflix-07-Hystrix 服务熔断

如果上图所示:积分服务的不可用导致了订单服务的不可用,订单服务的不可用导致了商品服务的不可用;最终就跟滚雪球一样越放越大,造成雪崩效应;

7.1.2 Hystrix 简介

Hystrix的中文含义是豪猪,因其背上长满了刺,而拥有自我保护能力。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q7sT13dK-1647869813344)(media/134.png)]

在微服务架构下,很多服务都相互依赖,如果不能对依赖的服务进行隔离,那么服务本身也有可能发生故障,Hystrix 通过 HystrixCommand 对调用进行隔离,这样可以阻止故障的连锁效应,能够让接口调用快速失败并迅速恢复正常,或者回退并优雅降级。

Hystrix 是 Netflix 针对微服务分布式系统采用的熔断保护中间件,用于防止服务的雪崩效应;

Hystrix提供的功能:

  • 1)对远程调用产生的延迟和故障进行控制和保护

  • 2)防止系统的服务级联瘫痪

  • 3)提供回退、限流、降级等操作保护下游微服务

  • 4)提供服务监控、警报等操作

7.2 Hystrix 快速入门

7.2.1 测试方法降级

1)在服务提供者提供接口(Order服务):

package com.cloud.order.controller;

import com.cloud.order.entity.Order;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@RestController
@RequestMapping("/order")
public class OrderController {

    /**
     * 测试hystrix远程调用
     *
     * @param flag
     * @return
     */
    @GetMapping("/testHystrix/{flag}")
    public Map testHystrix(@PathVariable String flag) throws InterruptedException {

        if ("0".equals(flag)) {
            // 模拟异常
            int i = 1 / 0;
        }

        if ("1".equals(flag)) {
            // 模拟阻塞
            Thread.sleep(1500);
        }

        return new HashMap() {{
            put("flag", true);
            put("message", "请求成功");
            put("statusCode", "200");
        }};
    }
}

2)在服务消费者(Item服务)引入Hystrix相关依赖:

<!--hystrix依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

3)在引导类中开启Hystrix:

@EnableCircuitBreaker       // 开启微服务熔断

4)在服务消费者提供降级方法:

package com.cloud.item.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.ribbon.proxy.annotation.Hystrix;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@RestController
@RequestMapping("/item")
public class ItemController {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 测试Hystrix接口
     *
     * @param flag
     * @return
     */
    @HystrixCommand(fallbackMethod = "fallBack")            // 降级方法
    @GetMapping("/testHystrix/{flag}")
    public Map findOrderById(@PathVariable String flag) {

        Map resultMap = restTemplate.getForObject("http://order-service/order/testHystrix/" + flag, Map.class);

        return resultMap;
    }

    // 降级方法
    public Map fallBack(String flag) {

        Map fallBackMap = new HashMap() {{
            put("flag", true);
            put("message", "触发降级方法!");
            put("statusCode", "400");
            put("flag", flag);
        }};

        return fallBackMap;
    }
}

访问:http://localhost:9000/item/testHystrix/0、http://localhost:9000/item/testHystrix/1

SpringCloud-Netflix-07-Hystrix 服务熔断

注意:降级方法的参数列表、返回值等必须和业务方法保持一致;

7.2.2 超时降级

我们在配置方法降级后,不仅是Order服务出现异常之后会触发降级方法,如果Order服务出现超时响应Feign也会触发降级方法;默认情况下,使用Feign在远程调用时,默认1秒钟,如果访问未响应则触发降级方法,在实际开发中,我们可以针对于不同的情况酌情调整;

修改Feign的超时时间:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2000       # 修改超时时间为2s

再次访问:http://localhost:9000/item/testHystrix/1

发现请求能够正常响应;

7.3 Hystrix处理高并发策略

7.3.1 准备测试环境

  • 1)修改OrderController:
package com.cloud.order.controller;

import com.cloud.order.entity.Order;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@RestController
@RequestMapping("/order")
public class OrderController {

    @Value("${spring.cloud.client.ip-address}")
    private String ip;

    @Value("${server.port}")
    private String port;

    /**
     * 根据id查询
     *
     * @param id
     * @return
     */
    @GetMapping("{id}")
    public Map findById(@PathVariable Integer id) {

        return new HashMap() {{
            put("flag", true);
            put("message", "查询成功" + ip + ":" + port);
            put("statusCode", "200");
            put("id", id);
        }};
    }

    /**
     * 查询全部
     *
     * @return
     */
    @GetMapping
    public Map findAll() throws InterruptedException {

        // 模拟线程阻塞
        Thread.sleep(3000);
        return new HashMap() {{
            put("flag", true);
            put("message", "查询成功全部成功;" + ip + ":" + port);
            put("statusCode", "200");
        }};
    }
}
  • 2)修改ItemController:
@RestController
@RequestMapping("/item")
public class ItemController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/findOrderById/{orderId}")
    public Map findOrderById(@PathVariable Integer orderId) {

        Map resultMap = restTemplate.getForObject("http://order-service/order/" + orderId, Map.class);

        return resultMap;
    }

    @GetMapping("/findOrderAll")
    public Map findOrderAll() {

        Map resultMap = restTemplate.getForObject("http://order-service/order/", Map.class);

        return resultMap;
    }
}

7.3.2 JMeter测试工具

7.3.2.1 JMeter简介

Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试, 它可以用于测试静态和动态资源,例如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库、FTP 服务器, 等等。JMeter 可以用于对服务器、网络或对象模拟巨大的负载,来自不同压力类别下测试它们的强度和分析整体性能。

SpringCloud-Netflix-07-Hystrix 服务熔断

JMeter官网:https://jmeter.apache.org/

7.3.2.2 JMeter的使用

1)添加线程组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OnjI5Eel-1647869813347)(media/160.png)]

2)配置并发参数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2cOCoAu3-1647869813348)(media/157.png)]

3)添加HTTP请求:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5QD9cUzW-1647869813349)(media/159.png)]

4)配置HTTP请求参数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tETpeQfb-1647869813349)(media/158.png)]

5)配置结果取样器:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dp8CAkIx-1647869813351)(media/156.png)]

6)启动并发请求:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YD7KjREN-1647869813352)(media/155.png)]

与此同时,在浏览器发送一个http://localhsot:9000/item/findOrderById/1的请求(正常请求),打开F12,查看服务器的响应时间:

SpringCloud-Netflix-07-Hystrix 服务熔断

我们发现在高并发情况下,如果有太多的连接被其他不可用(超时)的服务占用会导致正常的服务访问也有会有问题;

7.4 服务隔离

我们刚刚使用JMeter演示了高并发场景下的雪崩效应;当有某个微服务不能够立即响应时,那么会占用当前连接资源,总的线程连接资源是有限的,如果在微服务调用链路中,有非常多的超时连接无法立即响应时,那么势必会造成其他正常服务的调用超时(因为连接都被超时调用的服务占用);为此Hystrix提供有两种的服务隔离策略,分别为线程池隔离信号量隔离

7.4.1 线程池隔离

SpringCloud-Netflix-07-Hystrix 服务熔断

没有进行线程池隔离前,项目的所有接口都运行在一个线程池中,当某个接口压力过大时容易耗尽整个线程池中的所有线程,如果这个接口出现超时或故障时,那么其他正常的接口将会一直处于等待状态;

线程池隔离将某几个接口(访问量大的)分配一个独立的线程池,如果系统流量大时,那么最多只会耗尽该线程池中的资源,对整体的项目不会有很大的影响;

  • 修改ItemController:
package com.cloud.item.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.ribbon.proxy.annotation.Hystrix;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@RestController
@RequestMapping("/item")
public class ItemController {

    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(
            // 服务组名称,用于一个服务组保护多个接口(多个接口使用的服务组名称一致即可)
            groupKey = "item-orderService",
            // 接口名称,默认为方法名(名称任意)
            commandKey = "abc-findOrderById",
            // 线程池名称
            threadPoolKey = "item-orderService-findOrderByIdPool",
            commandProperties = {
                    @HystrixProperty(
                            // 超时时间,默认为1000ms
                            name = "execution.isolation.thread.timeoutInMilliseconds",
                            value = "10000"
                    ),
                    // 线程池隔离策略(默认值)
                    @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "THREAD"),
            },
            threadPoolProperties = {
                    // 线程池大小
                    @HystrixProperty(name = "coreSize", value = "2"),
                    // 队列等待阈值
                    @HystrixProperty(name = "maxQueueSize", value = "2"),
            },
            fallbackMethod = "findOrderByIdFallBack"            // 降级方法
    )
    @GetMapping("/findOrderById/{orderId}")
    public Map findOrderById(@PathVariable Integer orderId) {

        System.out.println(Thread.currentThread().getName());
        Map resultMap = restTemplate.getForObject("http://order-service/order/" + orderId, Map.class);

        return resultMap;
    }

    /**
     * findOrderById降级方法
     * @param flag
     * @return
     */
    public Map findOrderByIdFallBack(Integer flag) {

        Map fallBackMap = new HashMap() {{
            put("flag", true);
            put("message", "触发降级方法!");
            put("statusCode", "400");
            put("flag", flag);
        }};

        System.out.println("触发降级方法....findOrderByIdFallBack");
        return fallBackMap;
    }


    @HystrixCommand(
            // 服务组名称(名称任意)
            groupKey = "item-orderService",
            // 接口名称,默认为方法名(名称任意)
            commandKey = "abc-findOrderAll",
            // 线程池名称
            threadPoolKey = "item-orderService-findAllPool",
            commandProperties = {
                    @HystrixProperty(
                            // 超时时间,默认为1000ms
                            name = "execution.isolation.thread.timeoutInMilliseconds",
                            value = "10000"
                    ),
                    // 线程池隔离策略(默认值)
                    @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "THREAD"),
            },
            threadPoolProperties = {
                    // 线程池大小
                    @HystrixProperty(name = "coreSize", value = "3"),
                    // 队列等待阈值
                    @HystrixProperty(name = "maxQueueSize", value = "1"),
            },
            fallbackMethod = "findOrderAllFallBack"     // 降级方法
    )
    @GetMapping("/findOrderAll")
    public Map findOrderAll() {

        System.out.println(Thread.currentThread().getName());
        Map resultMap = restTemplate.getForObject("http://order-service/order/", Map.class);

        return resultMap;
    }

    /**
     * findOrderAll 降级方法
     * @return
     */
    public Map findOrderAllFallBack() {

        System.out.println("触发降级方法....findOrderAllFallBack");
        Map fallBackMap = new HashMap() {{
            put("flag", true);
            put("message", "触发降级方法!");
            put("statusCode", "400");
        }};

        return fallBackMap;
    }

}

coreSize为3代表最多只能处理三个并发,maxQueueSize为1,代表请求队列中最多只能等待1个请求,也就是说这个接口最多能够处理4个并发,如果第5个并发请求来了,那么会直接触发降级方法,立即响应释放连接资源;

使用JMeter发起高并发请求findOrderAll接口,在浏览器访问findOrderById接口,查看是否快速响应;

7.4.2 信号量隔离

SpringCloud-Netflix-07-Hystrix 服务熔断

信号量隔离为某个接口分配具体的并发量,超出了则触发降级立即响应;

package com.cloud.item.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
import com.netflix.ribbon.proxy.annotation.Hystrix;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.HystrixProperties;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@RestController
@RequestMapping("/item")
public class ItemController {

    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(
            commandProperties = {
                    @HystrixProperty(
                            // 超时时间,默认为1000ms
                            name = "execution.isolation.thread.timeoutInMilliseconds",
                            value = "10000"
                    ),
                    // 隔离策略 默认为Thread(线程池隔离) SEMAPHORE:信号量隔离
                    @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),
                    // 信号量最大并发
                    @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "5"),
            },
            fallbackMethod = "findOrderByIdFallBack"             // 降级方法
    )
    @GetMapping("/findOrderById/{orderId}")
    public Map findOrderById(@PathVariable Integer orderId) {

        System.out.println(Thread.currentThread().getName());
        Map resultMap = restTemplate.getForObject("http://order-service/order/" + orderId, Map.class);

        return resultMap;
    }

    /**
     * findOrderById降级方法
     * @param flag
     * @return
     */
    public Map findOrderByIdFallBack(Integer flag) {

        System.out.println("触发降级方法....findOrderByIdFallBack");
        Map fallBackMap = new HashMap() {{
            put("flag", true);
            put("message", "触发降级方法!");
            put("statusCode", "400");
            put("flag", flag);
        }};

        return fallBackMap;
    }

    @HystrixCommand(
            commandProperties = {
                    @HystrixProperty(
                            // 超时时间,默认为1000ms
                            name = "execution.isolation.thread.timeoutInMilliseconds",
                            value = "10000"
                    ),
                    // 隔离策略 默认为Thread(线程池隔离) SEMAPHORE:信号量隔离
                    @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),
                    // 信号量最大并发
                    @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "5"),
            },
            fallbackMethod = "findOrderAllFallBack"             // 降级方法
    )
    @GetMapping("/findOrderAll")
    public Map findOrderAll() {

        System.out.println(Thread.currentThread().getName());
        Map resultMap = restTemplate.getForObject("http://order-service/order/", Map.class);

        return resultMap;
    }

    /**
     * findOrderAll 降级方法
     * @return
     */
    public Map findOrderAllFallBack() {

        System.out.println("触发降级方法....findOrderAllFallBack");
        Map fallBackMap = new HashMap() {{
            put("flag", true);
            put("message", "触发降级方法!");
            put("statusCode", "400");
        }};

        return fallBackMap;
    }
}

7.4.3 小结

  • 线程池隔离:

    • 请求线程和调用线程Provider不是同一条线程;(消耗资源)
    • 支持熔断,当线程池到达最大线程并且阈值已经满了时,在请求触发fallback降级方法;
    • 采用单独的线程池进行隔离
    • 支持同步和异步两种方式;
    • 资源消耗大,大量的线程进行上下文切换、排队、调度等
    • 无法传递Http Header信息
  • 信号量隔离:

    • 请求线程和调用Provider线程是同一条线程
    • 支持熔断,信号量达到maxConcurrentRequests后再请求触发fallback降级方法;
    • 采用信号量令牌进行隔离
    • 同步调用,不支持异步调用
    • 资源消耗小
    • 可以传递Http Header
  • 何时采用线程池隔离?

请求并发量大,并且==调用服务耗时长==,可采用线程池隔离,保证大量容器的线程处于可用状态,不会由于服务本身原因一直处于等待或阻塞状态,快速失败返回;

  • 何时采用信号量隔离?

请求并发量大,并且==调用服务耗时短==(通常是命中缓存等情况),可采用信号量隔离,这类服务的调用通常返回都较快,不会占用容器线程太长时间,而且也减少了线程切换的开销;

7.5 Hystrix Dashboard

Hystrix 仪表盘(Hystrix Dashboard),Hystrix 仪表盘主要用来监控Hystrix 的实时运行状态,通过它我们可以看到 Hystrix 的各项指标信息,如每秒请求数量,成功/失败/熔断次数等,Hystrix 仪表盘通过很好的可视化工具帮我们展示这些指标,从而快速发现系统中存在的问题进而解决它。

7.5.1 搭建Hystrix DashBoard

1)引入依赖

<dependencies>
    <!--dashboard监控依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
</dependencies>

2)编写配置

server:
  port: 10102
hystrix:
  dashboard:
    proxy-stream-allow-list: "localhost"        # 允许本机访问

3)启动类

package com.cloud.dashboard;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@EnableHystrixDashboard         // 开启Hystrix仪表盘
@SpringBootApplication
public class DashBoardApplication {

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

访问http://localhost:10102/hystrix

SpringCloud-Netflix-07-Hystrix 服务熔断

7.5.2 配置Hystrix监控

1)在服务消费者引入依赖:

<!--hystrix依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

<!--SpringBoot监控依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2)编写配置

management:
  endpoints:
    web:
      exposure:
        include: '*'      # 开放所有监控端点

访问http://localhost:9000/actuator/hystrix.stream(需要开启@EnableCircuitBreaker并提供降级方法)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LZDKTVRk-1647869813354)(media/150.png)]

我们需要访问服务下的任意一个接口之后才可以获取具体的ping信息:http://localhost:9000/item/findOrderById/1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ecQJBFH-1647869813355)(media/149.png)]

7.5.3 使用DashBoard监控

访问http://localhost:10102/hystrix

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PnCsAx4S-1647869813355)(media/148.png)]

监控面板:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g03cbu9j-1647869813356)(media/147.png)]

修改ItemController:

package com.cloud.item.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@RestController
@RequestMapping("/item")
public class ItemController {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 测试Hystrix接口
     *
     * @param flag
     * @return
     */
    @HystrixCommand(fallbackMethod = "fallBack")            // 降级方法
    @GetMapping("/testHystrix/{flag}")
    public Map findOrderById(@PathVariable String flag) {

        Map resultMap = restTemplate.getForObject("http://order-service/order/testHystrix/" + flag, Map.class);

        return resultMap;
    }

    // 降级方法
    public Map fallBack(String flag) {

        Map fallBackMap = new HashMap() {{
            put("flag", true);
            put("message", "触发降级方法!");
            put("statusCode", "400");
            put("flag", flag);
        }};

        return fallBackMap;
    }

 
    @HystrixCommand(fallbackMethod = "findOrderByIdFallBack")
    @GetMapping("/findOrderById/{orderId}")
    public Map findOrderById(@PathVariable Integer orderId) {

        Map resultMap = restTemplate.getForObject("http://order-service/order/" + orderId, Map.class);
        return resultMap;
    }


    /**
     * findOrderById降级方法
     * @param flag
     * @return
     */
    public Map findOrderByIdFallBack(Integer flag) {

        Map fallBackMap = new HashMap() {{
            put("flag", true);
            put("message", "触发降级方法!");
            put("statusCode", "400");
            put("flag", flag);
        }};

        System.out.println("触发降级方法....findOrderByIdFallBack");
        return fallBackMap;
    }

    @HystrixCommand(fallbackMethod = "findOrderAllFallBack")
    @GetMapping("/findOrderAll")
    public Map findOrderAll() {

        Map resultMap = restTemplate.getForObject("http://order-service/order/", Map.class);
        return resultMap;
    }

    /**
     * findOrderAll 降级方法
     * @return
     */
    public Map findOrderAllFallBack() {

        System.out.println("触发降级方法....findOrderAllFallBack");
        Map fallBackMap = new HashMap() {{
            put("flag", true);
            put("message", "触发降级方法!");
            put("statusCode", "400");
        }};

        return fallBackMap;
    }
    
}

7.5.4 DashBoard面板参数

SpringCloud-Netflix-07-Hystrix 服务熔断

7.7 Hystrix断路器

7.7.1 断路器的工作原理

Hystrix 能使你的系统在出现依赖服务失效的时候,通过隔离系统所依赖的服务,防止服务级联失败,同时提供失败回退机制,更优雅地应对失效,并使你的系统能更快地从异常中恢复。了解熔断器模式请看下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XPLG99Ce-1647869813357)(media/145.png)]

熔断器有三个状态 CLOSEDOPENHALF_OPEN熔断器默认关闭状态,当触发熔断后状态变更为OPEN,在等待到指定的时间,Hystrix会放请求检测服务是否开启,这期间熔断器会变为 HALF_OPEN半开启状态,熔断探测服务可用则继续变更为 CLOSED关闭熔断器。

  • 1)Closed:断路器处于关闭状态,closed是断路器的默认状态,,表示当前所有的请求都可以正常访问;
  • 2)Open:断路器属于打开状态,当失败请求百分比达到50%,并且请求次数不小于20次,则触发熔断,断路器会开启。
  • 3)Half Open:断路器属于半开状态,当断路器属于open状态时会进入休眠(默认5s),之后断路器进入半开状态。此时若请求成功1次,则断路器自动关闭,否则断路器再次进入打开状态,再次进行5s的休眠;

我们发送请求:http://localhost:9000/item/testHystrix/0 20次触发熔断器

在5s内(Open状态)发送http://localhost:9000/item/testHystrix/1 请求,发现依旧被降级;

7.7.2 断路器相关参数

断路器开启添加默认为请求失败比率为50%,并且需要20次请求,当断路器打开时休眠5s后进入半开状态;我们可以通过一系列参数来修改断路器的配置;

关于断路器的配置有全局配置和方法配置两种,全局配置针对于所有的服务调用,方法配置只针对于本方法的配置,两种都配置了采取就近原则(方法配置)

全局配置:在服务消费者(Item服务)中配置:

hystrix:
  command:
    default:
      circuitBreaker:
        requestVolumeThreshold: 5             # 熔断触发的错误次数的阈值
        sleepWindowInMilliseconds: 10000      # 断路器开启后多少毫秒进入半开状态
        errorThresholdPercentage: 50          # 断路器统计的请求失败比率阈值

方法配置:修改testHystrix接口:

/**
 * 测试Hystrix接口
 *
 * @param flag
 * @return
 */
@HystrixCommand(
        // 降级方法
        fallbackMethod = "fallBack",
        commandProperties = {
                @HystrixProperty(
                        // 超时时间,默认为1000ms
                        name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS,
                        value = "10000"
                ),
                // 熔断触发的错误次数的阈值为5次
                @HystrixProperty(
                        name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, 
                        value = "5"
                ),
                // 断路器开启后3s进入半开状态
                @HystrixProperty(
                        name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, 
                        value = "3000"
                ),
                // 断路器统计的请求失败比率阈值为50
                @HystrixProperty(
                        name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, 
                        value = "100"
                )
        })
@GetMapping("/testHystrix/{flag}")
public Map testHystrix(@PathVariable String flag) {

    Map resultMap = restTemplate.getForObject("http://order-service/order/testHystrix/" + flag, Map.class);

    return resultMap;
}

Tips:当同时配置注解和yml配置时,以注解为准

注意:断路器除了达到上面的条件会开启时,线程池隔离和信号量隔离时也会触发断路器

1)线程池隔离:超出了等待线程也会触发断路器,当线程队列中的线程正常消费完毕时,断路器关闭;

2)信号量隔离:信号量令牌全部耗尽时,如果还有新的请求来临,那么触发断路器,当请求归还信号量令牌并且令牌能完全分配给请求线程时,断路器关闭;

7.8 Feign 集成 Hystrix

7.8.1 Feign 降级

Spring Cloud Fegin中默认已为Feign整合了hystrix,所以添加Feign依赖后就不用在添加hystrix,Feign与Hystrix集成也非常的方便,我们只需要在配置文件中激活Feign即可:

  • 引入feign依赖:
<!--Feign依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 开启feign支持:
feign:
  hystrix:
    enabled: true         # 开启hystrix熔断

编写远程调用接口:

package com.cloud.item.client;

import com.cloud.item.client.fallback.OrderClientFallBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.Map;

/**
 * 远程调用接口
 * name: 服务名
 * fallback: 降级处理类
 */
@FeignClient(name = "order-service", fallback = OrderClientFallBack.class)
public interface OrderClient {

    /**
     * 测试hystrix远程调用
     *
     * @param flag
     * @return
     */
    @GetMapping("/order/testHystrix/{flag}")
    public Map testHystrix(@PathVariable("flag") String flag);

    /**
     * 根据id查询
     *
     * @param id
     * @return
     */
    @GetMapping("/order/{id}")
    public Map findById(@PathVariable Integer id);

    /**
     * 查询全部
     *
     * @return
     */
    @GetMapping("/order/")
    public Map findAll();
}

编写降级类:

package com.cloud.item.client.fallback;

import com.cloud.item.client.OrderClient;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * @author lscl
 * @version 1.0
 * @intro: Order服务远程调用失败的降级处理
 */
@Component
public class OrderClientFallBack implements OrderClient {
    @Override
    public Map testHystrix(String flag) {
        return new HashMap() {{
            put("flag", true);
            put("message", "触发testHystrix降级方法!");
            put("statusCode", "400");
            put("flag", flag);
        }};
    }

    @Override
    public Map findById(Integer id) {
        return new HashMap() {{
            put("flag", true);
            put("message", "触发findById降级方法!");
            put("statusCode", "400");
            put("id", id);
        }};
    }

    @Override
    public Map findAll() {
        return new HashMap() {{
            put("flag", true);
            put("message", "触发findAll降级方法!");
            put("statusCode", "400");
        }};
    }
}
  • 编写ItemController:
package com.cloud.item.controller;

import com.cloud.item.client.OrderClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@RestController
@RequestMapping("/item")
public class ItemController {

    @Autowired
    private OrderClient orderClient;

    /**
     * 测试Hystrix接口
     *
     * @param flag
     * @return
     */
    @GetMapping("/testHystrix/{flag}")
    public Map testHystrix(@PathVariable String flag) {

        Map resultMap = orderClient.testHystrix(flag);
        return resultMap;
    }

    @GetMapping("/findOrderById/{orderId}")
    public Map findOrderById(@PathVariable Integer orderId) {

        Map resultMap = orderClient.findById(orderId);

        return resultMap;
    }

    @GetMapping("/findOrderAll")
    public Map findOrderAll() {

        Map resultMap = orderClient.findAll();

        return resultMap;
    }

}

访问:http://localhost:9000/demo/testHystrix/2

7.8.2 Feign的超时配置

Feign集成了Ribbon,Feign的降级超时由Ribbon的超时时间与Hystrix的超时时间一起控制,取时间短的为准;

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000       # 修改超时时间为2s
ribbon:
  ReadTimeout: 1500               # 请求处理的超时时间为1.5s

Tips:两个需要一起配置,根据取时间短的为准原则,服务调用的超时时间为:1.5s

7.8.3 Feign配置线程池隔离

1)修改OrderClient,配置降级属性

@FeignClient(
    name = "order-service", 
    fallback = OrderClientFallBack.class,
    fallbackFactory = OrderThreadIsolationConfig.class			// 降级配置
)

2)编写降级配置类

package com.cloud.item.client.isolation;

import com.netflix.hystrix.*;
import feign.Feign;
import feign.Target;
import feign.hystrix.SetterFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;

/**
 * @author lscl
 * @version 1.0
 * @intro: 调用Order服务的线程池隔离配置
 */
@Configuration
public class OrderThreadIsolationConfig {
    @Bean
    public SetterFactory setterFactory(){
        SetterFactory setterFactory =new SetterFactory() {
            @Override
            public HystrixCommand.Setter create(Target<?> target, Method method) {

                // 服务分组名称
                String groupKey = target.name();

                // 接口名称
                String commandKey = Feign.configKey(target.type(), method);

                // Hystrix配置
                HystrixCommandProperties.Setter commandProp = HystrixCommandProperties.Setter()
                        // 设置线程隔离
                        .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD);

                // 线程池配置
                HystrixThreadPoolProperties.Setter threadPoolProp = HystrixThreadPoolProperties.Setter()
                        // 线程池大小
                        .withCoreSize(3)
                        // 线程池最大等待阈值
                        .withMaxQueueSize(1);

                return HystrixCommand.Setter
                        .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
                        .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
                        .andCommandPropertiesDefaults(commandProp).
                                andThreadPoolPropertiesDefaults(threadPoolProp);
            }
        };
        return setterFactory;
    }
}

7.8.4 Feign配置信号量隔离

1)修改OrderClient降级处理配置类

@FeignClient(
    name = "order-service", 
    fallback = OrderClientFallBack.class,
    fallbackFactory = OrderSemaphoreIsolationConfig.class			// 降级配置
)

2)编写信号量配置:

package com.cloud.item.client.isolation;

import com.netflix.hystrix.*;
import feign.Feign;
import feign.Target;
import feign.hystrix.SetterFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;

/**
 * @author lscl
 * @version 1.0
 * @intro: 调用Order服务的线程池隔离配置
 */
@Configuration
public class OrderSemaphoreIsolationConfig {
    @Bean
    public SetterFactory setterFactory() {
        SetterFactory setterFactory = new SetterFactory() {
            @Override
            public HystrixCommand.Setter create(Target<?> target, Method method) {

                // 服务分组名称
                String groupKey = target.name();

                // 接口名称
                String commandKey = Feign.configKey(target.type(), method);

                // Hystrix配置
                HystrixCommandProperties.Setter commandProp = HystrixCommandProperties.Setter()
                        // 设置信号量隔离
                        .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
                    // 信号量令牌个数
                        .withExecutionIsolationSemaphoreMaxConcurrentRequests(3);

                return HystrixCommand.Setter
                        .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
                        .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
                        .andCommandPropertiesDefaults(commandProp);
            }
        };
        return setterFactory;
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131746.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!