SpringCloud——Hystrix(手写断路器思路、测试与优化)

思路

在这里插入图片描述

手写断路器

1.导入lombok依赖、Web依赖

2.创建一个Controller,进行远程调用(无法访问的地址)

在这里插入图片描述
启动类中给RestTemplate加入Bean容器

@SpringBootApplication
public class MyHistrixApplication {

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

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
@Autowired
private RestTemplate restTemplate;

@GetMapping("doRPC")
public String doRPC(){
    String result = restTemplate.getForObject("http://localhost:8888/login",String.class);
    return result;
}

3.创建状态类,在pom中添加AOP依赖,创建切面类和Anno注解

在这里插入图片描述

public enum  Status {
    CLOSE,
    OPEN,
    HALF_OPEN
}

pom中导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

创建Anno注解
在这里插入图片描述

/**
 * 熔断器切面注解
 */
@Target(ElementType.METHOD) //作用在方法
@Retention(RetentionPolicy.RUNTIME) //运行时有效
@Documented
@Inherited
public @interface MyAnno {
}

3.在Controller中添加切点注解,在切面中将需要的方法加入到切点中

在Controller中添加切点注解@MyAnno
在这里插入图片描述
在切面中将方法以注解的形式添加到切点注解中

@Around(value = "@annotation(com.dcits.myhystrix.anno.MyAnno)")
public Object myAround(ProceedingJoinPoint joinPoint) {}

左边出现一个圈说明已经添加
在这里插入图片描述
切面的方法作用:

/**
* 类比拦截器
* 判断当前断路器状态,从而决定是否发起调用,是否执行目标方法
* @param joinPoint
* @return
*/

4.创建一个断路器模型

package com.dcits.myhystrix.pojo;

import lombok.Data;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 断路器模型
 */
@Data
public class Hystrix {
    //最大失败次数
    public static final Integer MAX_FAIL_COUNT = 3;

    //窗口时间
    public static final Integer WINDOW_TIME = 20;

    //断路器中有它自己的状态
    private Status status = Status.CLOSE;

    /**
     * 当前断路器失败次数
     * i++
     * AtomicInteger 可以保证线程安全
     */
    private AtomicInteger currentFailkCount = new AtomicInteger(0);

    private ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
            4,
            8,
            30,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(2000),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy()

    );

    {
        poolExecutor.execute(()->{
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(WINDOW_TIME);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
    }



    /**
     * 记录失败次数
     */
    public void addFailCount() {
        int i = currentFailkCount.incrementAndGet(); // ++i
        if (i >= MAX_FAIL_COUNT){
            //说明失败次数已到了阈值
            //修改当前状态为open
            this.setStatus(Status.OPEN);
            //当断路器打开以后就不能去访问 需要将它变成半开
            //等待一个时间窗口 让断路器变成半开

            poolExecutor.execute(()->{
                try {
                    TimeUnit.SECONDS.sleep(WINDOW_TIME);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                this.setStatus(Status.HALF_OPEN);
                //重置失败次数
                this.currentFailkCount.set(0);
            });

        }
    }
}

5.在切面中补全方法体

package com.dcits.myhystrix.aspect;

import com.dcits.myhystrix.pojo.Hystrix;
import com.dcits.myhystrix.pojo.Status;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

@Component
@Aspect
public class MyAspect {

    //因为一个消费者可以调用多个提供者,所以每个提供者都有自己的断路器
    //在消费者里面去创建一个断路器的容器
    public static Map<String, Hystrix> hmap = new HashMap<>();

    static {
        //假设需要去调用order-service的服务
        hmap.put("order-service",new Hystrix());
    }

    Random random = new Random();


    /**
     * 类比拦截器
     * 判断当前断路器状态,从而决定是否发起调用,是否执行目标方法
     * @param joinPoint
     * @return
     */

    //不推荐这么写,建议使用Anno
//    public static final String POINT_CUT = "execution(* com.dcits.myhystrix.controller.HystrixController.doRPC(..))";
    @Around(value = "@annotation(com.dcits.myhystrix.anno.MyAnno)")
    public Object myAround(ProceedingJoinPoint joinPoint) {
        Object result = null;
        //获取当前提供者的断路器
        Hystrix hystrix = hmap.get("order-service");
        Status status = hystrix.getStatus();
        switch (status) {
            case OPEN:
                return "我是备胎";
            case HALF_OPEN:
                int i = random.nextInt(5);
                System.out.println(i);
                if (i == 1) {
                    try {
                        result = joinPoint.proceed();
                        hystrix.setStatus(Status.CLOSE);
                    } catch (Throwable throwable) {
                        return "我是备胎";
                    }
                }
            case CLOSE:
                //正常 去调用 执行目标方法
                try {
                    result = joinPoint.proceed();
                    return result;
                } catch (Throwable throwable) {
                    //调用失败
                    hystrix.addFailCount();
                    return "我是备胎";
                }
            default:
                return "我是备胎";
        }
    }
}

测试

在这里插入图片描述

优化

对断路器模型代码进行优化

private Object lock = new Object();
{
    poolExecutor.execute(()->{
        while (true){
            try {
                TimeUnit.SECONDS.sleep(WINDOW_TIME);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            if(this.status.equals(Status.CLOSE)){
                //清零
                this.currentFailkCount.set(0);
            }else {
                //半开或者开 不需要记录次数 这个线程可以不工作
                synchronized (lock){
                    try {
                        lock.wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }
    });
}

修改切面处代码

case HALF_OPEN:
  int i = random.nextInt(5);
  System.out.println(i);
  if (i == 1) {
      try {
          result = joinPoint.proceed();
          hystrix.setStatus(Status.CLOSE);
          synchronized (hystrix.getLock()){
              hystrix.getLock().notifyAll();
          }
      } catch (Throwable throwable) {
          return "我是备胎";
      }
  }

Hystrix常用配置

hystrix: #hystrix 的全局控制
  command:
    default: #default 是全局控制,也可以换成单个方法控制,把 default 换成方法名即可
      fallback:
        isolation:
          semaphore:
            maxConcurrentRequests: 1000 #信号量隔离级别最大并发数
      circuitBreaker:
        enabled: true #开启断路器
        requestVolumeThreshold: 3 #失败次数(阀值)
        sleepWindowInMilliseconds: 20000 #窗口时间
        errorThresholdPercentage: 60 #失败率
      execution:
        isolation:
          Strategy: thread #隔离方式 thread 线程隔离集合和 SEMAPHORE 信号量隔离
        thread:
          timeoutInMilliseconds: 3000 #调用超时时长
#隔离方式 两种隔离方式 thread 线程池 按照 group(10 个线程)划分服务提供者,用户请求的线程 和做远程的线程不一样 
# 好处 当 B 服务调用失败了 或者请求 B 服务的量太大了 不会对 C 服务造成影响 用户访问比较大的情 况下使用比较好 异步的方式
 # 缺点 线程间切换开销大,对机器性能影响 
 # 应用场景 调用第三方服务 并发量大的情况下 
 # SEMAPHORE 信号量隔离 每次请进来 有一个原子计数器 做请求次数的++ 当请求完成以后 -- 
 # 好处 对 cpu 开销小
  # 缺点 并发请求不易太多 当请求过多 就会拒绝请求 做一个保护机制 
  # 场景 使用内部调用 ,并发小的情况下
   # 源码入门 HystrixCommand AbstractCommand HystrixThreadPool
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值