关于 Hystrix
-
熔断
- 熔断是 consumer 角色一侧的功能。
- 当请求某一 provider 的失败情况达到某一阀值后,断路器会切换到 open 状态,请求将不再发往这个 provider 。
- 断路器打开一段时间后,会切换到 half-open 状态,此时断路器的状态将取决于下一次请求是否成功。如果成功,则切换到 closed 状态;如果失败,则切换到 open 状态。
- 服务降级 Fallback
当处理出现异常,或不具备处理能力时(负载过高、线程池用完),则会快速返回一个预先指定的值。 - 资源隔离
资源隔离主要用来预防雪崩的发生,限制某一资源不可用引起的全局资源挂起。可选的隔离方式有 线程隔离、信号量隔离。
熔断 和 降级
在一般方法上应用
- 创建模块
spring-cloud.s05.store-hystrix
-
在
pom
中添加依赖<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <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> </dependencies>
-
添加配置文件
server: port: 31003 spring: application: name: store-hystrix eureka: client: serviceUrl: defaultZone: http://user:123123@localhost:34001/eureka/ instance: instance-id: ${spring.cloud.client.ip-address}:${server.port} prefer-ip-address: true lease-renewal-interval-in-seconds: 10 lease-expiration-duration-in-seconds: 30 info: app.name: ${spring.application.name} compony.name: me.xhy build.artifactId: $project.artifactId$ build.modelVersion: $project.modelVersion$
-
创建启动类
..StoreHystrix
@SpringBootApplication @EnableEurekaClient // 开启降级 @EnableHystrix public class StoreHystrix { public static void main(String[] args) { SpringApplication.run(StoreHystrix.class, args); } @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } }
如果启动类中
不写
@EnableHystrix
注解,那么当方法失败时,是不会执行降级方法的;但是注解了@HystrixCommand
的方法是可以有熔断功能的,默认值也依然是3次
, 当失败次数达到3次时,在 ribbon 中剔除该资源。 -
创建程序访问入口
..ConsumerCircuitController
@RestController @RequestMapping("/hystrix") public class CircuitFallbackController { @Autowired RestTemplate restTemplate; /* 新增了 @HystrixCommand @HystrixCommand(fallbackMethod = "getMoviesError") 默认键为 fallbackMethod 表示当调用 provider 失败,调用的补偿方法 默认情况下,发生3次错误(补偿方法调用了3次),该 provider 将被熔断 当被熔断的 provider 重新上线,将被重新加回到服务列表 */ @RequestMapping("/cf/ribbon") @HystrixCommand(fallbackMethod = "getServerPortError") public String getServerPort() { return restTemplate.getForObject("http://album-provider/album/server-port", String.class); } public String getServerPortError() { return "ribbon method getMovies occur hystrix"; } @RequestMapping("/cf/always-exception") @HystrixCommand(fallbackMethod = "getServerPortError") public String alwaysException() { if(true) throw new RuntimeException(); return "hi"; } }
这个 Controller 中有两个访问入口, @RequestMapping("/circuit/always-exception") 始终都会发生异常;@HystrixCommand(fallbackMethod = "getServerPortError") 则需要在正常访问所有 provider 后,停掉至少一个 provider 来观察结果。
-
验证熔断和降级
-
http://localhost:31003/hystrix/circuit/always-exception
由于该 Controller 始终报错,所以始终使用降级方法的返回值。 -
http://localhost:31003/hystrix/circuit/ribbon
使用 ribbon 的负载均衡访问 provider ,下线至少一个 provider 来观察效果。
启动类注解了
@EnableHystrix
服务降级才会生效。@HystrixCommand
只有在应用ribbon
的方法上,才会有熔断效果。 -
在 Feign 上应用
Feign 是自带断路器的,但是它没有默认打开。需要在配置文件中配置打开它
-
pom
中引入 Feign<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
在配置文件中打开断路器
feign: hystrix: enabled: true # 这是默认值,如果觉得每个 Feign 方法都加上 Hystrix 影响效率,可以在这里关闭。
-
启动类增加注解
@EnableFeignClients // Feign 需要
-
创建 Feign 接口
// 直接在 FeignClient 注解中使用 fallback 指定降级方法是有弊端的,这种方式的弊端在于,无论是`原方法`还是`降级方法`都无法获取异常信息 @FeignClient(value = "album-provider", // fallback = CircuitFallbackFeignServiceFallback.class, fallbackFactory = CircuitFallbackFeignServiceFallbackFactory.class) public interface CircuitFallbackFeignService { @RequestMapping("/album/server-port") String getServerPort(); } /* 该类提供发生异常时的补偿方法 必须要有 @Component , 可以是外部类 */ /* @Component 弃用,存在无法获取异常信息的弊端 */ class CircuitFallbackFeignServiceFallback implements CircuitFallbackFeignService { @Override public String getServerPort() { return "feign method getServerPort occur hystrix fallback"; } } @Component class CircuitFallbackFeignServiceFallbackFactory implements FallbackFactory<CircuitFallbackFeignService> { // 原方法异常时,调用该方法 @Override public CircuitFallbackFeignService create(Throwable throwable) { return new CircuitFallbackFeignService() { @Override public String getServerPort() { String exMsg = throwable.getMessage(); return "feign method getServerPort occur hystrix fallback, exception is " + exMsg; } }; } }
-
创建访问程序入口
..CircuitFallbackFeignController
@RestController @RequestMapping("/hystrix") public class CircuitFallbackFeignController { @Autowired CircuitFallbackFeignService service; @RequestMapping("/cf/feign") public String getServerPort() { return service.getServerPort(); } }
-
访问
http://localhost:31003/hystrix/cf/feign
。如果配置文件中不开启断路器,降级方法是不会执行的
如果配置文件中不设置重重试次数,feign 默认会重试 1 次
熔断配置
circuitBreaker:
enabled: true # 是否开启熔断,默认值为 true
requestVolumeThreshold: 20 # 设置在一个滚动窗口中,打开断路器的最少请求数。 在已给窗口期失败多少个才会打开断路器。窗口设置属性 metrics.rollingStats.timeInMilliseconds
sleepWindowInMilliseconds: 5000 # 同上面 hystrix 中的配置
errorThresholdPercentage: 50 # 开启熔断的失败率,默认值为 50%
metrics:
rollingStats:
timeInMilliseconds: 10000 # 滚动窗口,默认为 10000 ms
熔断器的状态
当在滚动时间窗口内达到失败次数,熔断状态为 熔断开启
;在经过休眠期后,熔断状态变为 熔断半开
,此时该方法可以再次被调用;如果熔断半开状态下,下一个请求调用成功,熔断状态变为 关闭
,如果下一个请求
隔离
隔离可以防止雪崩的出现。当 provider 没有足够的处理能力时, consumer 的请求无法返回导致线程挂起,逐层影响,最终雪崩。如果限制 consumer 请求某个 provider 的 量
, consumer 就不会因为 provider 丧失处理能力而挂起过多的线程, consumer 仍然可以保持服务能力,去做其他任务。
线程方式牺牲空间,信号量方式牺牲时间。
使用线程隔离
-
创建业务实现类
..IsolationServiceImpl
@Component public class IsolationServiceImpl { /* 在调用 isolationConsume 方法是,先要执行 HystrixCommand 逻辑,通过后才会调用 isolationConsume */ @HystrixCommand( fallbackMethod = "isolationFallback", // 降级方法 commandKey = "isolationByThread", // 配置属性的参数(该值将作为配置 key 的一部分,出现在配置文件中,起到定位局部配置的作用) groupKey = "isolationByThreadGroup", // threadPoolKey 和 groupKey 的作用都是用来指定线程池的。 /* 先参考 threadPoolKey 后参考 groupKey。 threadPoolKey 相同的方法,使用同一线程池; threadPoolKey 没有设置, groupKey 相同的方法,使用同一线程池。 */ commandProperties = { @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"), // 线程池模式, 默认值是 THREAD /*@HystrixProperty(name = "execution.timeout.enable", value = "true"), // HystrixCommand.run()的执行是否启用超时时间,默认为 true。 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000" ), // hystrix 超时时间 @HystrixProperty(name = "circuitBreaker.enabled", value = "true" ), //开启熔断器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "3" ), // 窗口时间内多少个请求失败了,会打开断路器*/ }, threadPoolKey = "isolationByThreadPool", threadPoolProperties = { @HystrixProperty(name = "coreSize", value = "10"), // 线程池和信号量的默认值都是 10 /*@HystrixProperty(name = "maxQueueSize", value = "10"), @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"), @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"), @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"), @HystrixProperty(name = "metrics.rollingStats.time", value = "1440")*/ } ) public String isolationByThread() { System.out.println("Service Thread -> " + Thread.currentThread().getName()); return "this is a tagged method by Hystrix"; } @HystrixCommand( fallbackMethod = "isolationFallback", // 降级方法 commandKey = "isolationBySemaphore", groupKey = "isolationBySemaphoreGroup", commandProperties = { @HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE"), // 线程池模式, 默认值是 THREAD @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"), /*@HystrixProperty(name = "execution.timeout.enable", value = "true"), // HystrixCommand.run()的执行是否启用超时时间,默认为 true。 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000" ), // hystrix 超时时间 @HystrixProperty(name = "circuitBreaker.enabled", value = "true" ), //开启熔断器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "3" ), // 窗口时间内多少个请求失败了,会打开断路器*/ } ) public String isolationBySemaphore() { System.out.println("Service Thread -> " + Thread.currentThread().getName()); return "this is a tagged method by Hystrix"; } public String isolationFallback() { System.out.println("Service Thread fallback -> " + Thread.currentThread().getName()); return "function isolationConsume is isolated"; } }
该类中有两个方法,一个是业务方法,标记了
HystrixCommand
;另外一个是降级方法 -
创建测试类
..IsolationServiceImplTest
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = StoreHystrix.class) @WebAppConfiguration public class IsolationServiceImplTest { @Autowired IsolationServiceImpl isolationService; private int count = 11; private CountDownLatch cdl = new CountDownLatch(count); @Test public void testHystrix() { for (int i = 0; i < count; i++) { new Thread(() -> { try { cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "==>" + isolationService.isolationConsume()); } , "TestThread-" + i).start(); cdl.countDown(); } try { Thread.currentThread().join(); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } @After public void after() { } }
业务方法用线程池模式隔离,同时行该方法的线程最多为
10
个。在测试类中使用闩锁发起11
个线程并发访问业务方法。 - 测试结果
使用信号量隔离
大同小异,把隔离策略改成 信号量,降级方法可以使用同一个。
-
业务方法
@HystrixCommand( fallbackMethod = "isolationFallback", // 降级方法 commandKey = "isolationByThread", groupKey = "isolationByThreadGroup", commandProperties = { @HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE"), // 线程池模式, 默认值是 THREAD @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"), /*@HystrixProperty(name = "execution.timeout.enable", value = "true"), // HystrixCommand.run()的执行是否启用超时时间,默认为 true。 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000" ), // hystrix 超时时间 @HystrixProperty(name = "circuitBreaker.enabled", value = "true" ), //开启熔断器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "3" ), // 窗口时间内多少个请求失败了,会打开断路器*/ } ) public String isolationBySemaphore() { System.out.println("Service Thread -> " + Thread.currentThread().getName()); return "this is a tagged method by Hystrix"; }
-
测试方法
@Test public void testHystrixSemaphore() { for (int i = 0; i < count; i++) { new Thread(() -> { try { cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " ==> " + isolationService.isolationBySemaphore()); } , "Test Thread-" + i).start(); cdl.countDown(); } try { Thread.currentThread().join(); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }
- 测试结果
可以发现两种隔离策略, Hystrix 的线程名称是有区别的。
降级方法也可以使用 Hystrix ,只是要考虑有没有必要这样做。
Hystrix 的设置
官方文档 ,有些参数在 annotation 中设置是不起作用的,需要在配置文件中指定。application.yml
hystrix:
command:
default: # 全局配置
execution:
isolation:
thread:
interruptOnTimeout: false # 是否开启超时,默认false,不建议开启
timeoutInMilliseconds: 1000 # hystrix超时时间,默认 1000 ms
isolationByThread: # <commandKey> 局部配置
circuitBreaker:
sleepWindowInMilliseconds: 5000 # 熔断休眠时间,默认 5000 ms。设置在回路被打开后,拒绝请求到再次尝试请求的时间。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。