Hystrix原理及使用

背景介绍

1. 服务雪崩

分布式系统环境下,服务间类似依赖非常常见,一个业务调用通常依赖多个基础服务。 

一个服务失败,导致整条链路的服务都失败的情形,我们称之为服务雪崩

 

2.引起服务雪崩和服务雪崩的三个阶段

原因大致有四:

硬件故障

程序bug

缓存击穿(用户大量访问缓存中没有键值,导致大量请求数据库,使数据库压力过大)

用户大量请求

 

服务雪崩的第一阶段:

服务不可用

调用端重试加大流量

服务调用者不可用

 

3.解决方案

应用扩容(加机器;升级硬件)

流量控制(限流;关闭重试)

缓存(将用户可能大量访问的数据放入缓存,减少数据库压力)

服务降级

  1. 服务接口拒绝服务
  2. 页面拒绝服务
  3. 延迟持久化
  4. 随机拒绝服务

服务熔断

 

入门

Hystrix是一个用于处理分布式系统延期和容错的开源库,Hystrix能保证在一个依赖出问题的情况下,不会导致整体服务失败,提高分布式弹性。

服务降级: 假设对方系统不可用了,向调用方返回一个符合预期的,可处理的备选响应,而不是长时间等待或者抛出无处理的异常。这样就保证了服务方的线程不会被长时间不必要的占用,从而避免故障在分布式系统中蔓延,乃至雪崩。

服务熔断:达到最大访问后直接拒绝访问

服务限流:秒杀高并发等操作,排队有序进行

 

代码

1.没有引入hystrix的准备工作

 

 

 建这三个工程模拟Hystrix服务降级

EurekaMain7001代码:

pom:

        <!--Eureka server-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

yml:

server:
  port: 7001

eureka:
  instance:
    hostname: eureka7001.com
  client:
    #表示不往注册中心注册自己
    register-with-eureka: false
    #表示自己端就是注册中心,我的职责就是维护实例
    fetch-registry: false
    #设置地址
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

主启动类:

@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain7001.class, args);
    }
}

 

HystrixPaymentMain8001代码:

pom:

        <!--Eureka server-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

yml:

server:
  port: 8001

spring:
  application:
    name: cloud-payment-hystrix-service
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka/
  instance:
    instance-id: hystrix-payment8001
    prefer-ip-address: true

service层:

@Service
public class HystrixPaymentService {
    public String getPaymentInfoSuccess() {
        return "thread poor: " + Thread.currentThread().getName() + " success";
    }

    public String getPaymentInfoTimeout() {
        try {
            int spendTime = 3000;
            TimeUnit.MILLISECONDS.sleep(spendTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "thread poor: " + Thread.currentThread().getName() + " timeout";
    }
}

controller:

@RestController
@Slf4j
public class HystrixPaymentController {
    @Resource
    HystrixPaymentService service;

    @GetMapping("/payment/hystrix/ok")
    public String getPaymentInfoSuccess() {
        return service.getPaymentInfoSuccess();
    }

    @GetMapping("/payment/hystrix/timeout")
    public String getPaymentInfoTimeout() {
        return service.getPaymentInfoTimeout();
    }
}

主启动类:

@SpringBootApplication
@EnableEurekaClient
public class HystrixPaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixPaymentMain8001.class, args);
    }
}

 

HystrixOrderMain80代码:

pom:

        <!-- open feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

yml:

server:
  port: 80

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/db2021?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

ribbon:
  ReadTimeout: 5000
  ConnectTimeout: 5000

service接口:

@Component
@FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE")
public interface HystrixService {
    @GetMapping("/payment/hystrix/ok")
    public String getPaymentInfoSuccess();
    @GetMapping("/payment/hystrix/timeout")
    public String getPaymentInfoTimeout();
}

controller:

@RestController
public class HystrixController {
    @Resource
    private HystrixService hystrixService;

    @GetMapping("/consumer/payment/hystrix/ok")
    public String getPaymentInfoSuccess() {
        return hystrixService.getPaymentInfoSuccess();
    }
    @GetMapping("/consumer/payment/hystrix/timeout")
    public String getPaymentInfoFail() {
        return hystrixService.getPaymentInfoTimeout();
    }
}

主启动类:

@SpringBootApplication
@EnableFeignClients
public class HystrixOrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixOrderMain80.class, args);
    }
}

到此,payment注册到eureka, order用openFeign调用payment过程已经完成。

 

 

2.压力测试

用jmeter两万个线程调用timeout接口,再通过chrome访问ok接口,会发现chrome访问的ok接口也会响应过慢。

 

 

 

ok接口也会出现调用过慢,转圈圈

 

 

 

解决1:在服务端(payment8001)熔断降级:

 service上:

@Service
public class HystrixPaymentService {
    public String getPaymentInfoSuccess() {
        return "thread poor: " + Thread.currentThread().getName() + " success";
    }

    @HystrixCommand(fallbackMethod = "timeoutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="2000")
    })
    public String getPaymentInfoTimeout() {
        try {
            int spendTime = 5000;
            TimeUnit.MILLISECONDS.sleep(spendTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "thread poor: " + Thread.currentThread().getName() + " timeout";
    }

    public String timeoutHandler() {
        return "call back fail";
    }
}

主程序类上:

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class HystrixPaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixPaymentMain8001.class, args);
    }
}

引入pom以解决找不到@HystrixCommand注解问题

        <!--解决@HystrixCommand注解找不到问题-->
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-javanica</artifactId>
            <version>RELEASE</version>
        </dependency>

对payment8001进行测试,超时异常时,熔断降级测试成功:

 再试一下运行异常:

    @HystrixCommand(fallbackMethod = "timeoutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="2000")
    })
    public String getPaymentInfoTimeout() {
        int i = 10/0;
        return "thread poor: " + Thread.currentThread().getName() + " timeout";
    }

    public String timeoutHandler() {
        return "call back fail";
    }

熔断降级测试也能成功:

 

 

 

解决2:在客户端(order80)熔断降级:

准备工作:先修改HystrixOrderMain80里的超时时间,使直接调用不会超时:

@Service
public class HystrixPaymentService {
    public String getPaymentInfoSuccess() {
        return "thread poor: " + Thread.currentThread().getName() + " success";
    }

    @HystrixCommand(fallbackMethod = "timeoutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="5000")
    })
    public String getPaymentInfoTimeout() {
        try {
            TimeUnit.MILLISECONDS.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "thread poor: " + Thread.currentThread().getName() + " timeout";
    }

    public String timeoutHandler() {
        return "call back fail";
    }
}

在cloud-consumer-hystrix-order80里添加代码:

pom:

        <!--解决@HystrixCommand注解找不到问题-->
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-javanica</artifactId>
            <version>RELEASE</version>
        </dependency>

yml:

feign:
  hystrix:
    enabled: true

主程序:

@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class HystrixOrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixOrderMain80.class, args);
    }
}

controller:

@RestController
public class HystrixController {
    @Resource
    private HystrixService hystrixService;

    @GetMapping("/consumer/payment/hystrix/ok")
    public String getPaymentInfoSuccess() {
        return hystrixService.getPaymentInfoSuccess();
    }

    @GetMapping("/consumer/payment/hystrix/timeout")
    @HystrixCommand(fallbackMethod = "orderTimeoutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="1000")
    })
    public String getPaymentInfoFail() {
        return hystrixService.getPaymentInfoTimeout();
    }

    public String orderTimeoutHandler() {
        return "order timeout handler...";
    }
}

测试:

1.调用服务端接口,不会超时:

 

 2.调用消费端接口,出现了熔断降级,验证成功:

 

 

全局服务降级处理:

@RestController
@DefaultProperties(defaultFallback = "globalFallBackMethod")
public class HystrixController {
    @Resource
    private HystrixService hystrixService;

    @GetMapping("/consumer/payment/hystrix/ok")
    @HystrixCommand
    public String getPaymentInfoSuccess() {
        int i = 10/0;
        return hystrixService.getPaymentInfoSuccess();
    }

    @GetMapping("/consumer/payment/hystrix/timeout")
    @HystrixCommand(fallbackMethod = "orderTimeoutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="1000")
    })
    public String getPaymentInfoFail() {
        return hystrixService.getPaymentInfoTimeout();
    }

    public String orderTimeoutHandler() {
        return "order timeout handler...";
    }

    public String globalFallBackMethod() {
        return "global fallback handler...";
    }
}

测试全局异常熔断ok:

 

所以,如果是全局的就加@DefaultProperties(defaultFallback = "globalFallBackMethod")  和@HystrixCommand注解,如果有需要单独的fallback的就加

   @HystrixCommand(fallbackMethod = "orderTimeoutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="1000")
    })

 

在消费端这样添加代码会有冗余重复等现象。所以改进一下,在Feign上添加callback:

@Component
@FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE", fallback = HystrixServiceImpl.class)
public interface HystrixService {
    @GetMapping("/payment/hystrix/ok")
    public String getPaymentInfoSuccess();
    @GetMapping("/payment/hystrix/timeout")
    public String getPaymentInfoTimeout();
}

添加一个实现类:

@Service
public class HystrixServiceImpl implements HystrixService {
    @Override
    public String getPaymentInfoSuccess() {
        return "HystrixServiceImpl ok fallback  ... ";
    }

    @Override
    public String getPaymentInfoTimeout() {
        return "HystrixServiceImpl timeout fallback ... ";
    }
}

#controller上面不用加@HystrixCommand,只在service层加代码即可

关闭服务提供端(payment8001)再测试成功:

 

posted @ 2021-05-26 18:13  圣金巫灵  阅读(427)  评论(0编辑  收藏  举报