springcloud|Hystrix快速入门

Hystrix快速入门 服务雪崩:请求A服务、A调用B、B调用C,此时C宕机了,请求会一直等到超时。在分布式链路中,只要有一个服务宕机,可能导致整个业务线瘫痪。
为了解决这种情况,1.调整等待时间,这样可以缓解压力。缺点:不灵活,有的服务需要多的时间去完成。2.上游服务知道下游服务状态,如果正常就调用,宕机就return,这样就可以缓解服务雪崩。
Hystrix简介:
Hystrix是Netflix的开源项目,他提供了熔断器功能,阻止分布式系统中出现的联动故障,提供故障的解决方案,从而提高整个分布式系统的弹性。
思路:hystrix都是搭配feign或者ribbon使用的,创建一个包用来写Feign接口的实现类,这个实现类就是熔断情况下走的代码
还是沿用之前eureka-server的代码,springboot版本2.3.12.RELEASE springcloud版本Hoxton.SR12,基本依赖有eureka、spring web

  1. 创建一个租车服务的springboot项目
    server: port: 8080spring: application: name: rent-car-service eureka: client: service-url: defaultZone: http://localhost:8761/eureka instance: hostname: localhost instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

    @RestController public class RentCarController {@GetMapping("rent") public String rent(){ return "租车成功"; } }

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

  2. 创建调用者springboot项目代码
    @SpringBootApplication @EnableEurekaClient @EnableFeignClients public class CustomerServiceApplication {public static void main(String[] args) { SpringApplication.run(CustomerServiceApplication.class, args); }}

    @RestController public class CustomerController {@Autowired private CustomerRentFeign customerRentFeign; @GetMapping("customerRent") public String CustomerRent(){ System.out.println("请求进入"); String rent = customerRentFeign.rent(); return rent; } }

    feign接口代码,注解中fallback属性是对应实现类
    /** * 需要指定熔断的类 */ @FeignClient(value = "https://www.it610.com/article/rent-car-service",fallback = CustomerRentFeignHystrix.class) public interface CustomerRentFeign { @GetMapping("rent") public String rent(); }

    hystrix feign接口的实现类代码
    @Component public class CustomerRentFeignHystrix implements CustomerRentFeign {/** * 这个方法就是备选方案 * @return */ @Override public String rent() { return "熔断的情况"; } }

  3. 如果全部启动,会得到租车成功的结果,如果把租车服务停掉,再次访问localhost:8081/customerRent,返回结果就变成了 熔断的情况
  4. 调用者的controller代码会报错,原因是这个类型有两个实现,一个是feign的代理对象,另一个是hystrix对feign接口的实现类创建的对象
    @Autowired private CustomerRentFeign customerRentFeign;

    虽然代码报错,但是不影响正常使用,如果想解决报错可以使用@Qualifier注解指定Hystrix对Feign接口的实现类,或者修改高亮提示。
  5. 简单实现一个断路器:
启动类代码:
@SpringBootApplication public class HystrixRealizedApplication {public static void main(String[] args) { SpringApplication.run(HystrixRealizedApplication.class, args); }@Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }

hystrix状态模型
public enum HystrixStatus { CLOSE,//关闭状态 调用目标方法 OPEN,//开启状态 不调用目标方法 HALF_OPEN//半开状态 少量请求执行方法 }

断路器模型
/** * 断路器模型 */ @Data public class Breaker {//窗口时间 public static final Integer WINDOW_TIME = 20; //最大失败次数 public static final Integer MAX_FAIL_COUNT = 3; //断路器自己的状态 private HystrixStatus status = HystrixStatus.CLOSE; //当前断路器失败次数 会进行i++操作AtomicInteger 可以保证线程安全 private AtomicInteger currentFailCount = new AtomicInteger(0); /** * 线程池 */ private ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor( 4, 8, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); private Object lock = new Object(); { poolExecutor.execute(()->{ //定期删除20s一个周期清零窗口滑动 while(true){ try { //等待一个窗口时间 TimeUnit.SECONDS.sleep(WINDOW_TIME); } catch (InterruptedException e) { e.printStackTrace(); } //如果断路器是开的 请求不会去调用就不会失败 就不会记录次数 if (this.status.equals(HystrixStatus.CLOSE)){ //清零 this.currentFailCount.set(0); }else{ //半开或者开 不需要记录次数 这个线程可以不工作 synchronized(lock){ try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }); }//记录失败次数 public void addFailCount(){ int i = currentFailCount.incrementAndGet(); //i++ if(i >= MAX_FAIL_COUNT){ //说明失败次数到达阈值 //修改当前状态为open this.setStatus(HystrixStatus.OPEN); //断路器打开以后就不能访问了,需要变成半开,等待一个时间窗口,让断路器变成半开 //new Thread(() -> { //try { //TimeUnit.SECONDS.sleep(WINDOW_TIME); //} catch (InterruptedException e) { //e.printStackTrace(); //} //this.setStatus(HystrixStatus.HALF_OPEN); ////重置失败次数,不然下次请求进来直接变为open状态 //this.currentFailCount.set(0); //}).start(); //线程池写法 poolExecutor.execute(()->{ try { TimeUnit.SECONDS.sleep(WINDOW_TIME); } catch (InterruptedException e) { e.printStackTrace(); } this.setStatus(HystrixStatus.HALF_OPEN); //重置失败次数,不然下次请求进来直接变为open状态 this.currentFailCount.set(0); }); } }}

controller代码:
@RestController public class Controller {@Autowired private RestTemplate restTemplate; @GetMapping("doRpc") @HystrixAnno public String doRpc(){ String result = restTemplate.getForObject("http:localhost:8989/abc", String.class); return result; } }

aop的注解和相关类:
/** * 熔断器切面注解,想切哪个方法在哪个方法加@HystrixAnno */ @Target(ElementType.METHOD) //切在方法上 @Retention(RetentionPolicy.RUNTIME)//作用域 运行时有效 @Documented @Inherited public @interface HystrixAnno { }

@Component @Aspect public class HystrixAspect { //public static final String POINT_CUT = "execution (com.example.hystrixrealized.controller.Controller.doRpc(..))"; // 这种方式不太灵活,选用注解的形式//一个消费者可以调用多个提供者,每个提供者都有自己的断路器创建一个断路器集合 public static Map breakerMap = new HashMap<>(); static { //假设调用order-service的服务 breakerMap.put("order-service",new Breaker()); }Random random = new Random(); /** * 判断当前断路器的状态 决定是否发起调用(执行目标方法) * @param joinPoint * @return */ @Around(value = "https://www.it610.com/article/@annotation(com.example.hystrixrealized.anno.HystrixAnno)") public Object HystrixAround(ProceedingJoinPoint joinPoint){ Object result = null; //获取当前提供者的断路器 Breaker breaker = breakerMap.get("order-service"); HystrixStatus status = breaker.getStatus(); switch(status){ case CLOSE: //正常情况 执行方法 try { result = joinPoint.proceed(); //执行目标方法 return result; } catch (Throwable e) { //调用失败记录次数 breaker.addFailCount(); return "备用方案"; } case OPEN: //不能调用 return "备用方案"; case HALF_OPEN: //少许流量调用 int i = random.nextInt(5); //取0-4的随机数 System.out.println(i); if (i == 1){ //调用目标方法 try { result = joinPoint.proceed(); //成功断路器关闭 breaker.setStatus(HystrixStatus.CLOSE); synchronized (breaker.getLock()){ breaker.getLock().notifyAll(); //断路器关闭状态 唤醒定时删除任务 } } catch (Throwable e) { return "备用方案"; } } default: return "备用方案"; } } }

这个controller调用随便写得,实际没有这个提供者代码,测试访问后,断路器的变化,一开始应该是关闭状态,请求直接进到方法里,请求在一个时间窗口内(Breaker内的时间窗口属性 为20s)达到四次失败后,断路器变为开启状态 请求不会进入方法,再等待一个时间窗口,断路器变为半开状态,根据随机数结果部分请求会进入方法,如果调用成功状态变为关闭。
定期删除任务可以进行优化,断路器开的情况 请求不会进入方法调用 没有必要清零,可以不执行定期删除任务,相关代码就是断路器多线程 同步那块 还有aop里断路器变为关闭状态 唤醒定时删除任务。
  1. 【springcloud|Hystrix快速入门】Hystrix常用配置
    hystrix: command: default:# 全局控制 ,可以换成方法名 单个方法控制 circuitBreaker: enable: true requestVolumeThreshold: 30 # 失败次数(阈值) sleepWindowInMillIseconds: 20000 # 窗口时间 errorThresholdPercentage: 60 # 失败率 execution: isolation: Strategy: thread# 隔离方式 thread线程隔离 semaphore信号量隔离 thread: timeoutInMilliseconds: 3000 # 调用超时时长 fallback: isolation: semaphore: maxConcurrentRequests; 1000 #信号量隔离最大并发数

    • 线程隔离:每个提供者都有自己的线程组,hystrix是代理feign的请求文成熔断效果,两个提供者之间的线程是不同的,隔离效果比较彻底,缺点:线程切换开销。使用场景:调用第三方服务,并发量大
    • 信号量隔离:有一个原子计数器,请求进来就会++,请求完成就–,提供者用的都是一个计数器。缺点就是如果一个提供者出现问题,可能波及其他提供者,比如a出问题了,大量请求请求的a都做了++,但是一直没–,可能达到信号量最大并发数。 好处是开销小。使用场景:内部调用,并发小

    推荐阅读