hystrix 全局熔断_108、服务容错保护-Hystrix

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

微服务架构中,一个请求需要调用多个服务是非常常见的。如客户端访问A服务,而A服务需要调用B服务,B服务需要调用C服务,由于网络原因或者自身的原因,如果B服务或者C服务不能及时响应,A服务将处于阻塞状态,直到B服务C服务响应。此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。

1df1b8dac5d208b5b5c9eb8a7fbca6a4.png

7c8a868adbc3cca98ccc511623e1fe6f.png

7289d9d7186354f25b3cbe490cac588a.png

97303dd3cb1172cf12554845f080f5de.png

造成雪崩原因可以归结为以下三个:

1.服务提供者不可用(硬件故障,程序Bug,缓存击穿,用户大量请求)

2.重试加大流量(用户重试,代码逻辑重试)

3.服务调用者不可用(同步等待造成的资源耗尽)

最终的结果就是一个服务不可用,导致一-系列服务的不可用,而往往这种后果往往无法预料的。

二、如何解决灾难性雪崩效应

降级

超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。实现-一个fallback方法,当请求后端服务出现异常的时候,可以使用fallback方法返回的值.

隔离(线程池隔离和信号量隔离)

限制调用分布式服务的资源使用,某--个调用的服务出现问题不会影响其他服务调用。

熔断

当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快

速失败会进行快速恢复。

缓存

提供了请求缓存。

请求合并

提供请求合并。

三、降级

对服务进行降级处理

创建项目

6d27a467c812290ace1d5d91a22c934e.png

修改pom文件添加hystrix的坐标

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>

修改全局配置文件

spring.application.name=eureka-consumer-ribbon-hystrix
server.port=9010

eureka.client.serviceUrl.defaultZone=http://user:123456@eureka1:8761/eureka/,http://user:123456@eureka2:8761/eureka/

修改启动器开启熔断器

@EnableCircuitBreaker  //开启熔断器  (断路器)
@EnableEurekaClient
@SpringBootApplication
public class SpringcloudEurekaConsumerhystrixApplication {

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

}

在项目中添加Product实体

public class Product {
    private Integer id;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Product(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Product() {
    }
}

修改ProductService

@Service
public class UserService {
    @Autowired
    private LoadBalancerClient balancerClient;//ribbon 负载均衡器

    @HystrixCommand(fallbackMethod = "fallback")
    public List<Product> getUser(){
        //选择调用的服务的名称
        //ServiceInstance 封装了服务的基本信息,如 IP,端口
        ServiceInstance si = this.balancerClient.choose("ego-product-provider");
        //拼接访问服务的URL
        StringBuffer sb = new StringBuffer();
        //http://localhost:9001/product/findAll
        sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/product/findAll");
        System.out.println(sb.toString());
        //springMVC RestTemplate
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Product>> type = new ParameterizedTypeReference<List<Product>>() {};
        //ResponseEntity封装了返回值的信息
        ResponseEntity<List<Product>> response = rt.exchange(sb.toString(), HttpMethod.GET,null,type);
        List<Product> list = response.getBody();
        return list;
    }

    //返回拖地数据的方法
    public List<Product> fallback(){
        List<Product> list = new ArrayList<>();
        list.add(new Product(-1,"我是托底数据"));
        return list;
    }
}

正常情况下

845412eb6c5a1bece81fcdc5d25eb30a.png

provider宕机了

34d304dd08cd7b781c95ab62c7c9d5f2.png

以下四种情况将触发getFallback调用

(1)方法抛出非Hystrix BadRequestException异常。

(2)方 法调用超时

(3)熔断器开启拦截调用

(4)线程池/队列/信号量是否跑满

四、请求缓存

Hystrix为了降低访问服务的频率,支持将--个请求与返回结果做缓存处理。如果再次

请求的URL没有变化,那么Hystrix 不会请求服务,而是直接从缓存中将结果返回。这样可

以大大降低访问服务的压力。

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

(1)是一个本地缓存。在集群情况下缓存是不能同步的。

(2)不支持 第三方缓存容器。Redis, memcache 不支持的。

可以使用spring 的cache.

安装Redis并启动

创建项目

11b6ece15ce0b14444369f33293e2a97.png

修改pom文件添加springCache坐标

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.13.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.sxt</groupId>
    <artifactId>springcloud-eureka-consumer-ribbon-cache</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springcloud-eureka-consumer-ribbon-cache</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>
        <!--springCache-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

在配置文件中配置Redis链接信息

spring.application.name=eureka-consumer-ribbon-cache
server.port=9010

eureka.client.serviceUrl.defaultZone=http://user:123456@eureka1:8761/eureka/,http://user:123456@eureka2:8761/eureka/

# Redis
spring.redis.database=0
#Redis服务器地址
spring.redis.host=192.168.226.128
#Redis服务器连接端口
spring.redis.port=6379
#Redis服务器连接密码(默认为空)
spring.redis.password=
#连接池最大连接数(负值表示没有限制)
spring.redis.pool.max-active=100
#连接池最大阻塞等待时间(负值表示没有限制)
spring.redis.poo1.max-wait=3000
#连接池最大空闭连接数
spring.redis.pool.max-idle=200
#连接汉最小空闲连接数
spring.redis.pool.min-idle=50
#连接超时时间(毫秒)
spring.redis.pool.timeout=600

修改启动类开启缓存

@EnableCaching
@EnableEurekaClient
@SpringBootApplication
public class SpringcloudEurekaConsumerhystrixApplication {

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

}

修改ProductService

@CacheConfig(cacheNames = "com.sxt.ego.product")
@Service
public class ProductService {
    @Autowired
    private LoadBalancerClient balancerClient;//ribbon 负载均衡器

    @HystrixCommand(fallbackMethod = "fallback")
    public List<Product> getUser(){
        //选择调用的服务的名称
        //ServiceInstance 封装了服务的基本信息,如 IP,端口
        ServiceInstance si = this.balancerClient.choose("ego-product-provider");
        //拼接访问服务的URL
        StringBuffer sb = new StringBuffer();
        //http://localhost:9001/product/findAll
        sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/product/findAll");
        System.out.println(sb.toString());
        //springMVC RestTemplate
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Product>> type = new ParameterizedTypeReference<List<Product>>() {};
        //ResponseEntity封装了返回值的信息
        ResponseEntity<List<Product>> response = rt.exchange(sb.toString(), HttpMethod.GET,null,type);
        List<Product> list = response.getBody();
        return list;
    }

    //返回拖地数据的方法
    public List<Product> fallback(){
        List<Product> list = new ArrayList<>();
        list.add(new Product(-1,"我是托底数据"));
        return list;
    }

    //根据ID查询商品
    @Cacheable(key = "'product'+ #id")
    public Product getProductById(Integer id){
        System.out.println("========GET======="+id);
        return new Product(id,"新的商品");
    }

    //根据ID删除商品
    @CacheEvict(key = "'product'+ #id")
    public void delProductById(Integer id){
        System.out.println("========DEL======="+id);
    }
}

修改ProductController

@RestController
public class CacheController {
    @Autowired
    private ProductService userService;

    @RequestMapping("/consumer")
    public List<Product> getUsers(){
        return this.userService.getUser();
    }

    @RequestMapping(value = "/get",method = RequestMethod.GET)
    public Product get(Integer id){
        return this.userService.getProductById(id);
    }

    @RequestMapping(value = "/del",method = RequestMethod.GET)
    public void del(Integer id){
        this.userService.delProductById(id);
    }

}

c163b4083090faad5549472d812d5543.png

40dd20d2364e085f232187655bd6d5a0.png

删除操作

ef154a10c69461e1fc2d6e92dad2c08b.png

五、请求合并

Hystrix中的请求合并,就是利用一个合并处理器,将对同一个服务发起的连续请求合并成一个请求进行处理(这些连续请求的时间窗默认为10ms)

没合并的请求

da8e3468e4b3d88be8453dc1a074a9bc.png

请求合并

cbbb2ef1a05096c91cfd46b0b9dd9c47.png

什么情况下使用请求合并

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

请求合并的缺点

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

创建项目

38e0d4959f70effe8be40d10a1125e4b.png

修改pom文件添加hystrix坐标

<dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-hystrix</artifactId>
 </dependency>

修改全局配置文件

spring.application.name=eureka-consumer-ribbon-batch
server.port=9010

eureka.client.serviceUrl.defaultZone=http://user:123456@eureka1:8761/eureka/,http://user:123456@eureka2:8761/eureka/

修改ProductService

@Service
public class UserService {
    //利用 hystrix 合并请求
    @HystrixCollapser(batchMethod = "batchProduct", scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
            collapserProperties = {
                    //请求时间间隔在 20ms 之内的请求会被合并为一个请求,默认为 10ms
                    @HystrixProperty(name = "timerDelayInMilliseconds", value = "20"),
                    //设置触发批处理执行之前,在批处理中允许的最大请求数。
                    @HystrixProperty(name = "maxRequestsInBatch", value = "200"),
            })
    //consumer 的 controller 调用的方法 该方法返回值必须要返回Future 类型
    public Future<Product> getProduct(Integer id){
        System.out.println("========"+id+"===============");
        return null;
    }

    //调用provider服务的方法
    @HystrixCommand
    public List<Product> batchProduct(List<Integer> ids){
        for (Integer id:ids){
            System.out.println(id);
        }
        //假设是调用Provider服务后返回的list
        List<Product> list = new ArrayList<>();
        list.add(new Product(1,"电视"));
        list.add(new Product(2,"电脑"));
        list.add(new Product(3,"冰箱"));
        list.add(new Product(4,"洗衣机"));
        list.add(new Product(10,"list.........."));
        System.out.println("dddddddddddddddddd");
        return list;
    }
}

修改 Controller

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/consumer")
    public void getUsers() throws Exception{
        Future<Product> p1 = this.userService.getProduct(1);
        Future<Product> p2 = this.userService.getProduct(2);
        Future<Product> p3 = this.userService.getProduct(3);
        System.out.println(p1.get().toString());
        System.out.println(p2.get().toString());
        System.out.println(p3.get().toString());
    }
}

94d064cd674aec47c66721c11d586498.png

请求参数介绍

df9ed59902f936198db179eaa6afdee7.png

六、服务熔断

熔断机制相当于电路的跳闸功能。

例如:我们可以配置熔断策略为当前请求错误比例在10s内>50%时,该服务将进入熔断状态,后续请求都会进入fallback。

c014322375ed69d5e989becc84d73c5a.png

创建项目

ad5cf9a21384bef137fc80119ab2bc16.png

修改pom文件添加hystrix坐标

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>

修改全局配置文件

spring.application.name=eureka-consumer-ribbon-breaker
server.port=9010

eureka.client.serviceUrl.defaultZone=http://user:123456@eureka1:8761/eureka/,http://user:123456@eureka2:8761/eureka/

启动类

@EnableCircuitBreaker  //开启熔断器  (断路器)
@EnableEurekaClient
@SpringBootApplication
public class SpringcloudEurekaConsumerhystrixApplication {

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

}

修改ProductService

@Service
public class UserService {
    @Autowired
    private LoadBalancerClient balancerClient;//ribbon 负载均衡器

    @HystrixCommand(fallbackMethod = "fallback",
            commandProperties = {
            //默认 20 个;10s 内请求数大于 20 个时就启动熔断器,当请求符合熔断条件时将触发 getFallback()。

            @HystrixProperty(name= HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,
                    value="10"),
            //请求错误率大于 50%时就熔断,然后 for 循环发起请求,当请求符合熔断条件时将触发 getFallback()。

            @HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,
                    value="50"),
            //默认 5 秒;熔断多少秒后去尝试请求

            @HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,
                    value="5000"),
            })
    public List<Product> getUser(int flag){
        System.out.println(flag);
        if (flag==1){
            throw new RuntimeException();
        }
        //选择调用的服务的名称
        //ServiceInstance 封装了服务的基本信息,如 IP,端口
        ServiceInstance si = this.balancerClient.choose("ego-product-provider");
        //拼接访问服务的URL
        StringBuffer sb = new StringBuffer();
        //http://localhost:9001/product/findAll
        sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/product/findAll");
        System.out.println(sb.toString());
        //springMVC RestTemplate
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Product>> type = new ParameterizedTypeReference<List<Product>>() {};
        //ResponseEntity封装了返回值的信息
        ResponseEntity<List<Product>> response = rt.exchange(sb.toString(), HttpMethod.GET,null,type);
        List<Product> list = response.getBody();
        return list;
    }

    //返回拖地数据的方法
    public List<Product> fallback(int flag){
        List<Product> list = new ArrayList<>();
        list.add(new Product(-1,"我是托底数据"));
        return list;
    }
}

修改 ProductController

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/consumer")
    public List<Product> getUsers(@RequestParam("flag") Integer flag){
        return this.userService.getUser(flag);
    }
}

371279f2b6edc6849f2fd69cb39e7042.png

f97546cc2937dc5340275d1fb9d4c522.png

106fb98d4fabd1b67f1922cd362eec3b.png

熔断参数

3470a13e922ca8058467d1b1b6456532.png

七、隔离

线程池隔离

8155ae49b40a057a997432be63b9ebea.png

d7d4a7866fde43add1666220167615bf.png

43d392a980346f3fefcc8f675eddec75.png

0ef67d79635bbfcd7d941342339c15de.png

线程池隔离的优缺点:

优点:

1、使用线程池隔离可以完全隔 离依赖的服务(例如图中的A. B、C服务), 请求线程可以快速放回。

2、当线程池出现问题时 ,线程池隔离是独立的,不会影响其他服务和接口。

3、当失败的服务再次变得可用时,线程池将清理并可立即恢复,而不需要一 一个长时间的复。

4、独立的线程池提高了并发性。

缺点:

线程池隔离的主要缺点是它们增加计算开销(CPU).每个命令的执行涉及到排队、调度和上下文切换都是在一个单独的线程上运行的。

创建项目

ef4f2d6ed82ec18c584f82a2eec5b693.png

修改pom文件添加hystrix坐标

  <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>

修改配置文件

spring.application.name=eureka-consumer-ribbon-threadpool
server.port=9010

eureka.client.serviceUrl.defaultZone=http://user:123456@eureka1:8761/eureka/,http://user:123456@eureka2:8761/eureka/

修改启动类

@EnableCircuitBreaker  //开启熔断器  (断路器)
@EnableEurekaClient
@SpringBootApplication
public class SpringcloudEurekaConsumerhystrixApplication {

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

}

修改ProductService

@Service
public class UserService {
    @Autowired
    private LoadBalancerClient balancerClient;//ribbon 负载均衡器

    @HystrixCommand(groupKey="ego-product-provider",
            commandKey = "getUsers",
            threadPoolKey="ego-product-provider",
            threadPoolProperties = {
                    @HystrixProperty(name = "coreSize", value ="30"),//线程池大小
                    @HystrixProperty(name = "maxQueueSize",value = "100"),//最大队列长度
                    @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),//线程存活时间
                    @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15")//拒绝请求
            },
            fallbackMethod = "fallback")
    public List<Product> getUser(){
        System.out.println(Thread.currentThread().getName());
        //选择调用的服务的名称
        //ServiceInstance 封装了服务的基本信息,如 IP,端口
        ServiceInstance si = this.balancerClient.choose("ego-product-provider");
        //拼接访问服务的URL
        StringBuffer sb = new StringBuffer();
        //http://localhost:9001/product/findAll
        sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/product/findAll");
        System.out.println(sb.toString());
        //springMVC RestTemplate
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Product>> type = new ParameterizedTypeReference<List<Product>>() {};
        //ResponseEntity封装了返回值的信息
        ResponseEntity<List<Product>> response = rt.exchange(sb.toString(), HttpMethod.GET,null,type);
        List<Product> list = response.getBody();
        return list;
    }

    //返回拖地数据的方法
    public List<Product> fallback(){
        System.out.println(Thread.currentThread().getName());
        List<Product> list = new ArrayList<>();
        list.add(new Product(-1,"我是托底数据"));
        return list;
    }

    public void showThread(){
        System.out.println(Thread.currentThread().getName());
    }
}

修改 ProductController

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/consumer")
    public List<Product> getUsers(){
        return this.userService.getUser();
    }

    @RequestMapping("/consumer1")
    public void getUsers1(){
        this.userService.showThread();
    }
}

eaf4bf475957b6f4ae02902a24d9e9e3.png

线程池隔离参数

b3913f29679898d343b3676a766223e5.png
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值