Hystrix服务降级与熔断代码例子

1:复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。这就造成有可能会发生 服务雪崩 。那么什么是服务雪崩呢?

2:多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,产生联系。如果联系的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,这就是所谓的” 雪崩效应 “。也就是系统的 高可用 受到了破坏。

3:Hystrix是一个用于处理分布式系统的 延迟 和 容错 的开源库,在分布式系统里,许多依赖不可避免地会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下, 不会导致整个服务失败,避免级联故障,以提高分布式系统的弹性 。

4:服务限流(Flow Limit)秒杀高并发等操作,严禁一窝蜂地过来拥挤,大家排队,一秒钟N个,有序进行。服务限流使用Alibaba的Sentinel框架解决

5:服务提供者8003模块

5.1:在项目下建Module 名字:sprint-cloud-provider-hystrix-demo8001
5.2:pom.xml添加依赖


<dependency>
	<groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>

<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
    <version>1.16.16version>
    <scope>providedscope>
dependency>

<dependency>
     <groupId>org.slf4jgroupId>
     <artifactId>slf4j-apiartifactId>
     <version>1.7.5version>
 dependency>
 
 <dependency>
     <groupId>ch.qos.logbackgroupId>
     <artifactId>logback-classicartifactId>
     <version>1.2.3version>
     <scope>runtimescope>
 dependency>

5.3:yml配置文件

server:
  port: 8001
spring:
  application:
    name: cloud-provider-hystrix-demo
eureka:
  client:
    register-with-eureka: true  #false表示不向注册中心注册自己,默认值都是true
    fetchRegistry: true			 #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务,默认值都是true
    service-url:
      defaultZone: http://localhost:7001/eureka # 入驻的服务注册中心地址

5.4:sprint-cloud-provider-hystrix-demo8001的启动类

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

5.5:service业务类

@Service
public class DemoService {
    /**能够正常访问的方法*/
    public String  demoSuccess(Integer id) {
    	return "success"
    }

    /**模拟出错的方法*/
    public String  demoErroe(Integer id) {
        int time = 3;
        //暂停几秒钟线程,程序本身没有错误,就是模拟超时
        try {
            TimeUnit.SECONDS.sleep(time);
        }catch (InterruptedException e) {
            e.printStackTrace();
        } 
        	return "fail"
    }
}

5.6:controller类

@RestController
@Slf4j
@RequestMapping("/demo/hystrix")
public class DemoController {
    @Resource
    private DemoService demoService;
    @GetMapping("ok/{id}")
    public String demo_OK(@PathVariable("id")Integer id) {
        String result = demoService.demoSuccess(id);
        log.info("===> result: " + result);
        return result;
    }

    @GetMapping("timeout/{id}")
    public String demo_TimeOut(@PathVariable("id")Integer id) {
        String result = demoService.demoErroe(id);
        log.info("===> result: " + result);
        return result;
    }
}

用 Jmeter 进行高并发压力测试,用20000个请求都去访问timeout/{id}服务,我们模拟的是20000的访问量(,实际中可能会有远大于20000的访问量,当访问量更多的时候,甚至可能卡死服务,原因就是 Tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理 。
解决的问题方向
超时导致服务器变慢(转圈) => 超时不再等待
出错(宕机或程序运行出错) => 出错要有兜底

6:服务消费者8080模块进行测试(新建一个Module:spring-cloud-consumer-feign-hystrix-order80作为服务消费方,服务消费方利用feign访问提供方的服务,编写对应的service接口如下:)

@Component
@FeignClient("cloud-provider-hystrix-demo")
public interface DemoConsumerHystrixService {

    @GetMapping("/demo/hystrix/ok/{id}")
    public String demoSuccess(@PathVariable("id") Integer id);

    @GetMapping("/demo/hystrix/timeout/{id}")
    public String demoErroe(@PathVariable("id") Integer id);
}

7:服务消费者8080模块的Controller

@RestController
@Slf4j
public class DemoHystrixController {

    @Resource
    private DemoConsumerHystrixService demoConsumerHystrixService ;

    @GetMapping("/consumer/demo/hystrix/ok/{id}")
    public String demoConsumerSuccess(@PathVariable("id") Integer id) {
        String result = demoConsumerHystrixService .demoSuccess(id);
        return result;
    }

    @GetMapping("/consumer/demo/hystrix/timeout/{id}")
    public String demoConsumerErroe(@PathVariable("id") Integer id) {
        String result = demoConsumerHystrixService .demoErroe(id);
        return result;
    }
}

7:提供方的服务降级

  • 在提供方服务的主启动类上添加 @EnableCircuitBreaker 注解对熔断器进行激活
  • 降级的配置用 @HystrixCommand 注解,在服务提供方自身找问题,设置自身调用超时时间的峰值,在峰值内可以正常运行,超过了峰值需要有兜底的方法处理,用作服务降级。
  • 首先在服务提供方的业务类上启用 @HystrixCommand 实现报异常后如何处理,也就是一旦调用服务方法失败并抛出了错误信息后,会自动调用 @HystrixCommand 标注好的fallbackMethod服务降级方法。在服务提供方的service中我们修改 超时服务:
@Service
public class DemoService {
    /**能够正常访问的方法*/
    public String demoSuccess(Integer id) {
        return "success";
    }
    /**模拟出错的方法*/
    @HystrixCommand(fallbackMethod = "demoErrorHandler",
                    commandProperties = {
            @HystrixProperty(
                    name = "execution.isolation.thread.timeoutInMilliseconds",
                    value = "3000")
    })
    public String demoerror(Integer id) {
        //int age = 10/0; //模拟异常,也会兜底
        int time = 5;
        //暂停几秒钟线程,程序本身没有错误,就是模拟超时
        try {
            TimeUnit.SECONDS.sleep(time);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "error";
    }
    /** 定制服务降级方法*/
    public String demoErrorHandler(Integer id) {
        return "系统忙,请稍后再试";
    }
}

8:客户端模块8080服务消费方的服务降级

8.1:既然服务的提供方可以进行降级保护,那么服务的消费方,也可以更好地保护自己,也可以对自己进行降级保护,也就是说Hystrix服务降级既可以放在服务端(服务提供方),也可以放在客户端(服务消费方),但是! 通常是用客户端做服务降级 ,下面在服务消费方即客户端配置自己的服务降级保护,修改消费方的配置文件,添加如下配置已使其支持Hystrix,支持在@FeignClient配置fallback :

feign:
  hystrix:
    enabled: true

8.2:在消费方的主启动类上添加 @EnableHystrix 激活Hystrix服务。Controller中同样加入 @HystrixCommand 注解已实现服务降级:

@RestController
@Slf4j
public class demoHystrixController {

    @Resource
    private DemoConsumerHystrixService demoConsumerHystrixService ;

    @GetMapping("/consumer/demo/hystrix/ok/{id}")
    public String demoConsumerSuccess(@PathVariable("id") Integer id) {
        String result = demoConsumerHystrixService.demoSuccess(id);
        return result;
    }


    @GetMapping("/consumer/demo/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "demoConsumerFailHandler",
            commandProperties = {
                    @HystrixProperty(
                            name = "execution.isolation.thread.timeoutInMilliseconds",
                            value = "1500")
            })
    public String demoConsumerError(@PathVariable("id") Integer id) {
        String result = demoConsumerHystrixService.demoError(id);
        return result;
    }

    /** 定制服务降级方法*/
    public String demoConsumerFailHandler(Integer id) {
        return "我是消费者,系统忙,请稍后再试";
    }
}

9:统一类全局服务降级方法

而当前的这种处理方式是有问题的,也就是每个业务方法都对应了一个服务降级方法,这会导致代码膨胀 ,所以我们应该定义一个统一的服务降级方法,统一的方法和自定义的方法分开。而且我们 将服务降级方法和业务逻辑混合在了一起,这会导致代码混乱,业务逻辑不清晰。我们可以用feign接口中的 @DefaultProperties(defaultFallback = “”) 注解来配置全局的服务降级方法,也就是说 自己配置过 @HystrixCommand(fallbackMethod = “”) fallbackMethod方法的采用自己配置的服务降级方法,而没有配置过的就采用 @DefaultProperties(defaultFallback = “”) 配置的全局的服务降级方法。 这样的话通用的服务降级方法和独享的服务降级方法分开,避免了代码膨胀,合理减少了代码量,修改服务消费方的Controller入下:

//消费端Controller
@RestController
@Slf4j
//没有特别指明就用这个统一的
@DefaultProperties(defaultFallback = "demoConsumer_Global_FailHandler")
public class DemoHystrixController {

    @Resource
    private DemoConsumerHystrixService demoConsumerHystrixService ;
    
    @GetMapping("/consumer/demo/hystrix/ok/{id}")
    public String demoConsumerSuccess(@PathVariable("id") Integer id) {
        String result = demoConsumerHystrixService .demoSuccess(id);
        return result;
    }

    @GetMapping("/consumer/demo/hystrix/timeout/{id}")
	  //特别指明使用哪一个兜底方法
//    @HystrixCommand(fallbackMethod = "demoConsumerFailHandler",
//            commandProperties = {
//                    @HystrixProperty(
//                            name = "execution.isolation.thread.timeoutInMilliseconds",
//                            value = "1500")
//    })
    @HystrixCommand //没有具体指明就使用全局的
    public String demoConsumerError(@PathVariable("id") Integer id) {
        String result = demoConsumerHystrixService .demoError(id);
        return result;
    }

    /** 定制服务降级方法*/
    public String demoConsumerFailHandler(Integer id) {
        return "我是消费者,系统忙,请稍后再试";
    }

    /**
     * 全局服务降级方法
     * @return
     */
    public String demoConsumer_Global_FailHandler() {
        return "全局异常处理信息";
    }
}

这里需要注意的是, 无论是否配置了定制服务降级方法,都要在其服务上加入注解 @HystrixCommand , 否则服务降级和该服务没关系。

10:消费端接口

客户端的接口添加一个服务降级处理的实现类即可实现解耦,我们的80客户端已经有了DemoConsumerHystrixService 接口,,新建一个类 demoConsumerFallbackService实现该接口,并重写接口中的方法,为接口里的方法进行异常处理,并且我们在 DemoConsumerHystrixService @FeignClient注解中声明其服务降级方法所在的类

@Service
//当出现错误是到demoConsumerFallbackService类中找服务降级方法
@FeignClient(value = "cloud-provider-hystrix-demo",
              fallback = demoConsumerFallbackService.class)
public interface DemoConsumerHystrixService {

    @GetMapping("/demo/hystrix/ok/{id}")
    public String demoSuccess(@PathVariable("id") Integer id);

    @GetMapping("/demo/hystrix/timeout/{id}")
    public String demoError(@PathVariable("id") Integer id);
}

11:消费端接口的实现类

@Service
public class demoConsumerFallbackService implements DemoConsumerHystrixService {
    @Override
    public String demoSuccess(Integer id) {
        return "demoConsumerFallbackService demoSuccess出现异常";
    }
    @Override
    public String demoError(Integer id) {
        return "demoConsumerFallbackService demoError出现异常";
    }
}

12:消费端Controller中耦合的代码都取消

@RestController
@Slf4j
public class demoHystrixController{

    @Resource
    private DemoConsumerHystrixService demoConsumerHystrixService ;

    @GetMapping("/consumer/demo/hystrix/ok/{id}")
    public String demoConsumerSuccess(@PathVariable("id") Integer id) {
        String result = demoConsumerHystrixService.demoSuccess(id);
        return result;
    }

    @GetMapping("/consumer/demo/hystrix/timeout/{id}")
    public String demoConsumerError(@PathVariable("id") Integer id) {
        String result = demoConsumerHystrixService.demoError(id);
        return result;
    }
}

13:@HystrixCommand注解使用

/**
 * 服务熔断
 * fallbackMethod                               服务降级方法
 * circuitBreaker.enabled                       是否开启断路器
 * circuitBreaker.requestVolumeThreshold        请求次数
 * circuitBreaker.sleepWindowInMilliseconds     时间窗口期
 * circuitBreaker.errorThresholdPercentage      失败率达到多少后跳闸
 *
 * 以下配置意思是在10秒时间内请求10次,如果有6次是失败的,就触发熔断器
 *
 * 注解@HystrixProperty中的属性在com.netflix.hystrix.HystrixCommandProperties类中查看
 */
@HystrixCommand(fallbackMethod = "demoCircuitBreaker_fallback", 
       commandProperties = {
             @HystrixProperty(name = "circuitBreaker.enabled", 
                           value = "true"),
             @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", 
                           value = "10"),
             @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", 
                           value = "10000"),
             @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",
                           value = "60")
       })

Execution

  • execution.isolation.strategy
    指示使用哪种隔离策略,有两种:thread(线程池,默认)并发请求受到线程池最大线程限制,semaphore(信号量)受到信号量计数的限制,一般使用线程的方式
  • execution.isolation.thread.timeoutInMilliseconds
    用于设置超时的时间,单位是毫秒,默认为1s
  • execution.timeout.enabled
    用于指示是否启用超时,默认为true
  • execution.isolation.thread.interruptOnTimeout
    是否在服务超时后中断,默认为true,需要注意在 JVM 中我们无法强制中断一个线程,如果 Hystrix 方法里没有处理中断信号的逻辑,那么中断会被忽略。
  • execution.isolation.thread.interruptOnCancel
    是否在服务取消后中断,默认为false
  • execution.isolation.semaphore.maxConcurrentRequests
    设置信号量最大请求数,需要设置execution.isolation.strategy属性为semaphore,默认为10

Fallback 回退

  • fallback.isolation.semaphore.maxConcurrentRequests
    fallback回退方法时的最大并发量,超出此并发数的请求会抛出REJECTED_SEMAPHORE_FALLBACK 异常,默认是10
  • fallback.enabled
    是否启用fallback方法回退,默认为true

Circuit Breaker 熔断器
熔断器相关配置

  • circuitBreaker.enabled
    是否开启熔断器,默认为true
  • circuitBreaker.requestVolumeThreshold
    在一定的窗口时间内,打开熔断器的最小请求数,例如设置为20,在10s的窗口中,有19个线程,即使19个线程都失败了,也不会打开熔断器。默认为20
  • circuitBreaker.sleepWindowInMilliseconds
    熔断器打开后,所有请求都会快速失败,然后过一段时间,Hystrix会放行一个请求,进行测试是否恢复,如果恢复则关闭熔断器。这个属性就是用于指定熔断器开启后过多久会进行一次尝试,默认是5000毫秒
  • circuitBreaker.errorThresholdPercentage
    设置窗口时间内多少百分比的请求失败会打开熔断器,默认是50,也就是超过50%的失败了才会打开
    circuitBreaker.forceOpen、circuitBreaker.forceClosed
    是否强制开启或者强制关闭,默认是false

你可能感兴趣的:(hystrix,java,spring,cloud)