使用Hystrix实现服务熔断

断路器模式

断路器

  • CircuitBreaker, Martin Fowler
    • https://martinfowler.com/bliki/CircuitBreaker.html 文章地址

核心思想

  • 在断路器对象中封装受保护的方法调用,比如做监控,监控调用是成功还是失败的,它发生了什么问题,时间是如何

  • 该对象监控调用和断路情况,如果断路器发生了断路保护,那么我可以对它做一些监控和埋顶操作

  • 调用失败触发阈值后,后续调用直接由断路器返回错误,不再执行实际调用

在这里插入图片描述
客户端(client)通过circuitbreaker调用我的服务的提供者(supplier) 正常的时候 都是能做一个调用的
如果服务的提供方出现了什么问题 出现了超时 那么头几次是以超时来处理的 如果达到一个阈值之后 就不再做一个
超时处理,我直接通过断路器 做一个熔断的保护 我就不会再向后端 做一个请求了

例子

由两部分组成 circuit-break-demo 与 consul-waiter-service
circuit-break-demo
目录
在这里插入图片描述
需要修改的代码:
CustomerController

@RestController
@RequestMapping("/customer")
@Slf4j
public class CustomerController {
    @Autowired
    private CoffeeService coffeeService;
    @Autowired
    private CoffeeOrderService coffeeOrderService;

    @GetMapping("/menu")
    public List<Coffee> readMenu() {
        List<Coffee> list = coffeeService.getAll();
        return list == null ? Collections.emptyList() : list;
    }

    @PostMapping("/order")
    public CoffeeOrder createOrder() {
        NewOrderRequest orderRequest = NewOrderRequest.builder()
                .customer("Li Lei")
                .items(Arrays.asList("capuccino"))
                .build();
        CoffeeOrder order = coffeeOrderService.create(orderRequest);
        log.info("Order ID: {}", order != null ? order.getId() : "-");//如果是null 打印-
        return order;
    }
}

CircuitBreakerAspect

@Aspect
@Component
@Slf4j
public class CircuitBreakerAspect {//使用aop实现
    private static final Integer THRESHOLD = 3;
    private Map<String, AtomicInteger> counter = new ConcurrentHashMap<>();//记录失败的次数
    private Map<String, AtomicInteger> breakCounter = new ConcurrentHashMap<>();//被断路保护的次数

    @Around("execution(* spring.springbucks.customer.integration..*(..))")//拦截这个包下的 所有的方法的调用
    public Object doWithCircuitBreaker(ProceedingJoinPoint pjp) throws Throwable {
        String signature = pjp.getSignature().toLongString();//取出方法调用的Signature
        log.info("Invoke {}", signature);
        Object retVal;
        try {
            if (counter.containsKey(signature)) {  //在调用之前 判断 counter计数器是否包含signature 如果不包含 做一个初始化
                if (counter.get(signature).get() > THRESHOLD &&//如果包含 做一个检查 查看失败的次数是否大于阈值并查看断路保护的次数是否小于阈值
                        breakCounter.get(signature).get() < THRESHOLD) {
                    log.warn("Circuit breaker return null, break {} times.",
                            breakCounter.get(signature).incrementAndGet());//直接进入断路保护 返回一个null breakCounter做一个增加的动作
                    return null;
                }
            } else {
                counter.put(signature, new AtomicInteger(0));
                breakCounter.put(signature, new AtomicInteger(0));
            }
            retVal = pjp.proceed();//在try中 做真实的调用 如果调用成功 计数器归0
            counter.get(signature).set(0);
            breakCounter.get(signature).set(0);
        } catch (Throwable t) {
            log.warn("Circuit breaker counter: {}, Throwable {}",
                    counter.get(signature).incrementAndGet(), t.getMessage()); //如果 抛出异常 counter 做一个加的动作 同时 把breakCounter归0 下次就可以继续做一个熔断
            breakCounter.get(signature).set(0);
            throw t;
        }
        return retVal;
    }
}

CustomerServiceApplication

@SpringBootApplication
@Slf4j
@EnableDiscoveryClient
@EnableFeignClients
@EnableAspectJAutoProxy//开启aop
public class CustomerServiceApplication {

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

	@Bean
	public CloseableHttpClient httpClient() {
		return HttpClients.custom()
				.setConnectionTimeToLive(30, TimeUnit.SECONDS)
				.evictIdleConnections(30, TimeUnit.SECONDS)
				.setMaxConnTotal(200)
				.setMaxConnPerRoute(20)
				.disableAutomaticRetries()
				.setKeepAliveStrategy(new CustomConnectionKeepAliveStrategy())
				.build();
	}
}

consul-waiter-service
引自:https://blog.csdn.net/weixin_43790623/article/details/104545593

结果分析

首先 开启 CustomerServiceApplication 使用 psotman 进行 测试 在 postman 中输入http://localhost:8090/customer/menu
在这里插入图片描述
结果报错 同时 控制台显示 断路保护次数在这里插入图片描述
反复刷新 直到 出现[ ]
在这里插入图片描述
控制台 输出 Circuit breaker return null, break 1 times.
在这里插入图片描述
并将 breakCounter 清0

开启 WaiterServiceApplication 之后 输出的结果正常
在这里插入图片描述

在这里插入图片描述

Netflix Hystrix

在这里插入图片描述
当 service B 不可用时 它会做一个熔断 它就会调用 一个fallack的降级方法 这和上面aop的做法一样 不过 aop返回的null
我们可以将它作为一个默认的falback的方法 我们可以对各种各样的情况 做一个相应的处理

  • 实现了断路器模式
  • 在需要断路保护的方法上加入@HystrixCommand
    • 配置 fallbackMethod / commandProperties
    • @HystrixProperty(name=“execution.isolation.strategy”, value=“SEMAPHORE")
    fallbackMethod 和当前添加注解的Method 签名是一样的 也就是说 参数返回值是一样的 是可以兼容的
    当 我们HystrixCommand 标注的方法发生问题的时候 就可以fallback到另一个方法上 另外 通过HystrixProperty
    去添加一些属性 默认 在另一个线程执行的 这样它可以做一些超时的处理 如果 要改变这个策略 我们可以通过
    HystrixProperty 里面 指定 execution.isolation.strategy 把它变成一个信号量
    • https://github.com/Netflix/Hystrix/wiki/Configuration 官方配置地址

Spring Cloud 支持

  • 引入 spring-cloud-starter-netflix-hystrix 依赖

  • 在配置类上 加入 @EnableCircuitBreaker

Feign 支持

  • 在配置中加入 feign.hystrix.enabled=true

  • 操作 @FeignClient 中的属性
    • fallback / fallbackFactory
    fallback:fallback 到一个指定类
    fallbackFactory :在fallback的时候 获取到抛出的异常 然后 在Factory中创建出我要fallback的对象

例子

由两部分组成:consul-waiter-service 与 hystrix-customer-service
hystrix-customer-service
在这里插入图片描述
需要修改的代码:
CustomerController

@RestController
@RequestMapping("/customer")
@Slf4j
public class CustomerController {
    @Autowired
    private CoffeeService coffeeService;
    @Autowired
    private CoffeeOrderService coffeeOrderService;

    @GetMapping("/menu")
    public List<Coffee> readMenu() {
        List<Coffee> list = coffeeService.getAll();
        log.info("Read Menu: {} coffee", list.size());
        return list;
    }

    @PostMapping("/order")
    @HystrixCommand(fallbackMethod = "fallbackCreateOrder")//断路保护时 转到fallbackCreateOrder方法 如果方法调用失败 输出失败日志
    public CoffeeOrder createOrder() {
        NewOrderRequest orderRequest = NewOrderRequest.builder()
                .customer("Li Lei")
                .items(Arrays.asList("capuccino"))
                .build();
        CoffeeOrder order = coffeeOrderService.create(orderRequest);
        log.info("Order ID: {}", order != null ? order.getId() : "-");
        return order;
    }

    public CoffeeOrder fallbackCreateOrder() {
        log.warn("Fallback to NULL order.");
        return null;
    }
}

CoffeeService

@FeignClient(name = "waiter-service", contextId = "coffee",
        qualifier = "coffeeService", path="/coffee",
        fallback = FallbackCoffeeService.class)//调用方法失败之后  调用FallbackCoffeeService
// 如果用了Fallback,不要在接口上加@RequestMapping,path可以用在这里
public interface CoffeeService {
    @GetMapping(path = "/", params = "!name")
    List<Coffee> getAll();

    @GetMapping("/{id}")
    Coffee getById(@PathVariable Long id);

    @GetMapping(path = "/", params = "name")
    Coffee getByName(@RequestParam String name);
}

FallbackCoffeeService

@Slf4j
@Component
public class FallbackCoffeeService implements CoffeeService {
    @Override
    public List<Coffee> getAll() {
        log.warn("Fallback to EMPTY menu.");
        return Collections.emptyList();
    }

    @Override
    public Coffee getById(Long id) {
        return null;
    }

    @Override
    public Coffee getByName(String name) {
        return null;
    }
}

CustomerServiceApplication

@SpringBootApplication
@Slf4j
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker  //开启 hystrix 支持
public class CustomerServiceApplication {

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

	@Bean
	public CloseableHttpClient httpClient() {
		return HttpClients.custom()
				.setConnectionTimeToLive(30, TimeUnit.SECONDS)
				.evictIdleConnections(30, TimeUnit.SECONDS)
				.setMaxConnTotal(200)
				.setMaxConnPerRoute(20)
				.disableAutomaticRetries()
				.setKeepAliveStrategy(new CustomConnectionKeepAliveStrategy())
				.build();
	}
}

application.properties

server.port=8090

management.endpoint.health.show-details=always

feign.client.config.default.connect-timeout=500
feign.client.config.default.read-timeout=500
feign.hystrix.enabled=true 
#开启feign的hystrix支持

spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.prefer-ip-address=true

pom文件

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-consul-discovery</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>
		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-httpclient</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
		</dependency>

		<dependency>
			<groupId>org.joda</groupId>
			<artifactId>joda-money</artifactId>
			<version>1.0.1</version>
		</dependency>

		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>

		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>4.5.7</version>
		</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>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

consul-waiter-service
引自:https://blog.csdn.net/weixin_43790623/article/details/104545593

结果分析

启动 CustomerServiceApplication 打开postman 输入 http://localhost:8090/customer/menu 进行了错误处理 返回[ ] 在控制台 我们可以看到 http请求与Fallback 是在不同的线程上处理的 Hystrix是在另外的一个线程池上做的
在这里插入图片描述
在这里插入图片描述

输入 http://localhost:8090/customer/order 返回为null 是通过 @HystrixCommand 注解完成
在这里插入图片描述
在这里插入图片描述
开启 WaiterServiceApplication 输入 http://localhost:8090/customer/order 与 http://localhost:8090/customer/menu 之后 成功响应

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值