Hystrix快速入门

发布时间:2022-10-05 13:00

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: 8080
    
    spring:
      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 = "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<String, Breaker> breakerMap = new HashMap<>();

    static {
        //假设调用order-service的服务
        breakerMap.put("order-service",new Breaker());
    }

    Random random = new Random();
    /**
     * 判断当前断路器的状态 决定是否发起调用(执行目标方法)
     * @param joinPoint
     * @return
     */
    @Around(value = "@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. 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都做了++,但是一直没–,可能达到信号量最大并发数。 好处是开销小。使用场景:内部调用,并发小

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号