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
- 创建一个租车服务的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); }}
- 创建调用者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 "熔断的情况"; } }
- 如果全部启动,会得到租车成功的结果,如果把租车服务停掉,再次访问localhost:8081/customerRent,返回结果就变成了 熔断的情况
- 调用者的controller代码会报错,原因是这个类型有两个实现,一个是feign的代理对象,另一个是hystrix对feign接口的实现类创建的对象
@Autowired private CustomerRentFeign customerRentFeign;
虽然代码报错,但是不影响正常使用,如果想解决报错可以使用@Qualifier注解指定Hystrix对Feign接口的实现类,或者修改高亮提示。
- 简单实现一个断路器:
@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里断路器变为关闭状态 唤醒定时删除任务。
- 【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都做了++,但是一直没–,可能达到信号量最大并发数。 好处是开销小。使用场景:内部调用,并发小
推荐阅读
- mysql|SpringBoot + MyBatis + MySQL 实现读写分离
- SpringCloud|【SpringCloud】10-Nacos服务注册与配置中心
- IT|哪五种人不适合学编程()
- 微服务技术栈|Spring Cloud 网关路由—Spring Cloud Gateway 配置详解
- java|百亿流量API网关的设计记录
- java|Hadoop核心模块——HDFS详解(2)
- 若依|若依分离版—移动端开发公告功能
- Java 设计模式最佳实践(五、函数式模式)
- Java 设计模式最佳实践(四、结构模式)