1
断路器的设计
2
断路器的状态说明以及状态转变
关:服务正常调用 A---》B
开:在一段时间内,调用失败次数达到阀值(20s 内失败 3 次)
则断路器
打开,直接 return
半开:断路器打开后,过一段时间,让少许流量尝试调用 B 服务,如果成功则断路器关闭,使服务正常调用,如果失败,则继续半开
开始设计断路器模型
1.创建项目,导入依赖
手动导入AOP依赖
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.it</groupId>
<artifactId>interceptor</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>interceptor</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--导入aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.创建拦截器状态类
package com.it.model;
/**
* 拦截器状态开关
*/
public enum InterceptorStatus {
CLOSE,
OPEN,
HALF_OPEN
}
3.创建拦截器模型类
package com.it.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 这个是拦截器的模型
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class InterceptorModel {
/**
* 窗口时间
*/
public static final Integer WINDOW_TIME=20;
//最大失败次数
public static final Integer MAX_FAIL_COUNT=3;
/**
* 熔断器中有它自己的状态
*/
private InterceptorStatus status=InterceptorStatus.CLOSE;
//当前这个断路器失败了几次
private AtomicInteger currentFailCount=new AtomicInteger(0);
//线程池
private ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(
4,
8,
30,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(2000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
private Object lock=new Object();
{
threadPoolExecutor.execute(()->{
//定期删除
while(true){
try {
TimeUnit.SECONDS.sleep(WINDOW_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果断路器是开的,那么不会调用就不会失败,就不会记录次数,没有必要清零,这个线程就可以不执行
if (this.status.equals(InterceptorStatus.CLOSE)){
//清零
this.currentFailCount.set(0);
}else{
//半开或则开就不需要记录次数,这个线程就可以不工作
synchronized (lock){
try {
lock.wait();
System.out.println("我被唤醒了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
}
//记录失败次数
public void addFailCount(){
//++i
int i = currentFailCount.incrementAndGet();
if (i>=MAX_FAIL_COUNT){
//说明失败次数已经到了一个阈值
//修改当前状态为open
this.setStatus(InterceptorStatus.OPEN);
//当短路器打开以后,就不能访问了,需要将它变成半开
//等待一个时间窗口,让断路器变成半开
threadPoolExecutor.execute(()->{
try {
TimeUnit.SECONDS.sleep(WINDOW_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setStatus(InterceptorStatus.HALF_OPEN);
//重置失败次数,不然下次进来会直接打开短路器
this.currentFailCount.set(0);
});
}
}
}
4.创建拦截器注解类
package com.it.anno;
import java.lang.annotation.*;
/**
* 自定义拦截器切面注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface InterceptorAnnotation {
}
5.创建拦截器切面类
package com.it.aspect;
import com.it.model.InterceptorModel;
import com.it.model.InterceptorStatus;
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 InterceptorAspect {
// public static final String POINT_CUT="execution(* com.it.controller.InterceptorController.doRpc(..)"
//因为一个消费者可以调用多个提供者,每个提供者都有自己的断路器
//在消费者里面去创建一个断路器的容器
public static Map<String, InterceptorModel> interceptorModelMap=new HashMap<>();
static {
//假设 是需要调用register-service的服务
interceptorModelMap.put("register-service",new InterceptorModel());
}
Random random=new Random();
/**
* 这个类似于拦截器
* 就是要判断当前断路器的状态,从而决定是否发起调用(执行目标方法)
* @param joinPoint
* @return
*/
@Around(value="@annotation(com.it.anno.InterceptorAnnotation)")
public Object found(ProceedingJoinPoint joinPoint){
Object result=null;
//获得当前提供者的断路器
InterceptorModel interceptorModel = interceptorModelMap.get("register-service");
InterceptorStatus status=interceptorModel.getStatus();
switch(status){
case OPEN:
//不能调用
return "我是备胎";
case CLOSE:
//正常去调用,执行目标方法
try {
result=joinPoint.proceed();
return result;
} catch (Throwable throwable) {
//说明调用失败
interceptorModel.addFailCount();
return "我是备胎";
}
case HALF_OPEN:
//可以少许流量去调用
int i = random.nextInt(5);
System.out.println(i);
if (i==1){
//去尝试调用
try {
result=joinPoint.proceed();
//说明成功了 短路器关闭
interceptorModel.setStatus(InterceptorStatus.CLOSE);
synchronized (interceptorModel.getLock()){
interceptorModel.getLock().notifyAll();
}
return result;
} catch (Throwable throwable) {
return "我是备胎";
}
}
default:
return "我是备胎!";
}
}
}
6.创建拦截器控制类
package com.it.controller;
import com.it.anno.InterceptorAnnotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class InterceptorController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("doRpc")
@InterceptorAnnotation
public String doRpc(){
String result = restTemplate.getForObject("http://localhost:8989/abc", String.class);
return result;
}
}
7.主函数类
package com.it;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class InterceptorApplication {
public static void main(String[] args) {
SpringApplication.run(InterceptorApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
8.功能测试
第一次判断发现该服务无法使用,直接把拦截器设置成打开状态,这个时间窗口(20s)内访问该服务,都直接不会访问。
当20s后,将拦截器设置成半开半关状态,此时有一部分数据尝试访问该服务,如果还是不能访问,则再次设置为拦截器开启状态
半开半关状态下,当为随机取值为1时会尝试访问服务查看其是否恢复正常,为其他数时不访问服务。