赞
踩
小问题
前面讲了一下服务发现和服务注册,其实业务都比较简单,无非就是多了一层东西,就像我们学Spring,不是去new一个对象而是使用一些注解来支持,我们微服务的学习也是这样的,只不过我们的模块拆分了。
之前写的小demo有个小问题,那就是:
这是我们8001端口的方法,程序停了3秒钟,没什么问题
@GetMapping("timeout_success")
public ResultData timeoutSuccess(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new ResultData(200,"理论上应该成功的超时测试方法_成功",null);
}
测试一下
方法本身没有问题,模拟的是一种长时间服务的方法,但是如果此时我们另一个服务去调用该服务,会报错!
@Component
@FeignClient(value = "CLOUD-ORDER-SERVICE")
public interface ConsumerService {
@GetMapping("ok")
ResultData success();
@GetMapping("timeout_success")
ResultData timeoutSuccess();
}
500异常,说超时了!
这里简单说一下,就是我们的服务之间的调度有个默认时间,默认是1秒。如果在该时间内方法没有执行完,会报一个超时异常。这是springcloud的自我保护机制,但是有的时候确实有一些执行比较慢的方法,比如我们刚刚的例子,这样没有问题的方法就被springcloud误杀了!这里我们可以更改一下配置文件
在我们的调用者,也就是consumer添加如下配置,表示五秒之内都算正常,于是我们跑一下之前的程序。
#设置feign客户端超时时间(openFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况是正常的情况,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接之后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
这次执行通过了。
上面的例子简单的说明了一下我们服务之前调度的一种异常检测:超时
但是我们的服务调用时除了超时以外还可能是:机房着火,抛出异常
所以超时只是一种错误,这些错误直接影响到的就是客户了,界面展示一个友好的404和500。
所以服务降级就是为了解决这些平时服务调度之间遇到的问题而生的技术,简单的说就是我们为方法准备一个兜底的方法,如果出现问题,我们应该提示用户服务器繁忙,然后赶紧去机房把火给灭了。
服务降级也不是像配置中心一样需要写个服务,我们在可能发生错误的任何方法的地方才去准备我们的服务降级策略,所以我们要对order这个服务进行加强。这里使用hystrix作为服务降级的组件。
cloud-consumer
pom添加新的依赖
<!-- 服务熔断服务降级 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
主启动类添加注解
@EnableCircuitBreaker表示开启服务降级/熔断。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableEurekaClient
@EnableCircuitBreaker
public class ConsumerMainApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerMainApplication.class, args);
}
}
这是我们consumer调用order的接口,每个方法我们都准备一个兜底的策略,虽然很冗余,但是我们实际开发应该就是做好每一个业务流程,我们写的每个方法都应该健壮。
在**@FeignClient注解中添加fallback属性,属性值是一个类,表示我们的调用CLOUD-ORDER-SERVICE
服务的时候,如果出现任何问题,我们都HystrixConsumerService.class**去执行我们的备用方法。
@Component
@FeignClient(value = "CLOUD-ORDER-SERVICE",fallback = HystrixConsumerService.class)
public interface ConsumerService {
@GetMapping("ok")
ResultData success();
@GetMapping("timeout_success")
ResultData timeoutSuccess();
}
这个类是我们的接口的实现类,我们为每个方法都进行一个简单的服务降级,降级降级就是高级的用不了,用低级的。当然你问我低级的用不了怎么办,如果return一个对象都会报错了,那么这个服务器应该崩了。
@Component
public class HystrixConsumerService implements ConsumerService{
@Override
public ResultData success() {
return new ResultData(500,"好像出来点问题,服务降级一手",null);
}
@Override
public ResultData timeoutSuccess() {
return new ResultData(500,"好像出来点问题,服务降级一手",null);
}
}
application.yml添加这个配置,不然500错误。
feign:
hystrix:
enabled: true
我们启动eureka注册中心,再启动order然后是consumer,然后关闭order。模拟一个机房着火的案例,此时order服务已经着火了,所以我们的consumer会降级一手。
success方法一般不会出问题的,但是这里由于order炸了,这里是由于机房着火导致的服务降级。
现在我们对降级有个简单的理解了,就是一个方法写两套呗。平时处理异常以外,做版本升级的时候也可以用,新方案上线,但是可能出现BUG,当出现问题的时候,我们还可以使用兜底方案,比如说使用原来的方案,类似于一个开关,新方法不行走旧方法,旧方法不行,呵呵你这个项目有问题啊,这就是一些线上事故了!
我们上述的服务降级是很简答的一种,A调用B,调用不了就兜底,但是到底是什么问题呢?机房着火,抛异常,超时?所以,我们应该对服务降级进行更加细粒化的操作,我们把服务方法的生产者order进行加强。
order的pom.xml类
添加pom.xml依赖
<!-- 服务熔断 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
主启动类
添加@EnableCircuitBreaker注解表示开启熔断。
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableCircuitBreaker
public class OrderMainApplication {
public static void main(String[] args) {
SpringApplication.run(OrderMainApplication.class, args);
}
}
别害怕,听我说:
@HystrixCommand(fallbackMethod = "timeoutErrorHandler",commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="2000") }) @GetMapping("timeout_error") public ResultData timeoutError(){ try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } return new ResultData(200,"理论上应该失败的超时测试方法_成功",null); } @HystrixCommand(fallbackMethod = "timeoutSuccessHandler",commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="4000") }) @GetMapping("timeout_success") public ResultData timeoutSuccess(){ try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } return new ResultData(200,"理论上应该成功的超时测试方法_成功",null); } //降级走这里 public ResultData timeoutSuccessHandler(){ return new ResultData(500,"理论上应该成功的超时测试方法_服务降级",null); } //降级走这里 public ResultData timeoutErrorHandler(){ return new ResultData(500,"理论上应该失败的超时测试方法_服务降级",null); }
消费者consumer那边添加方法的调用,Controller层也相应的写上!
service
@Component
@FeignClient(value = "CLOUD-ORDER-SERVICE",fallback = HystrixConsumerService.class)
public interface ConsumerService {
@GetMapping("ok")
ResultData success();
@GetMapping("timeout_error")
ResultData timeoutError();
@GetMapping("timeout_success")
ResultData timeoutSuccess();
}
controller
@RestController public class ConsumerController { @Resource ConsumerService consumerService; @GetMapping("success") public ResultData ok(){ return consumerService.success(); } @GetMapping("timeout_success") public ResultData timeoutSuccess(){ return consumerService.timeoutSuccess(); } @GetMapping("timeout_error") public ResultData timeoutError(){ return consumerService.timeoutError(); } }
跑一手程序,开启三个微服务,先开启eureka再开启剩下两个(注册中心也是服务,只不过不显示了):
我们先测试order的8001,先测试理论上可行的超时方法
这是理论上失败的超时方法,睡了3秒,两秒钟是超时检测,然后走了降级的方法。
这时候我们的去访问81端口
我们consumer如果掉用order报错了,那么走consumer的兜底方法。而order报错了,自己也有一手兜底方法。
假如consumer设置2秒超时使用兜底方法,而order设置3秒超时才开启兜底方法,order没出错,但是consumer这里认为order报错了,走了一手兜底方法,有这种情况吧。
对于order是被调用者,掌握的是服务的细节。对于每个方法的超时检测更加精确,所以我们一般情况下consumer的超时检测时间可以放长一些,如果order达到自己的超时时间都没有使用兜底方法,那可能是服务出问题了,这时过来1秒达到了consumer的超时阈值了,再执行consumer的兜底方法。order可能会炸,consumer可会炸,但时consumer炸一般页面404,500比较严重了,而order炸了我们察觉不到,所以consumer的兜底方法是必要的,order也可以要,充分而不必要。
服务熔断是在服务降级的基础上了,是一个保险丝的作用。
举个例子,consumer调用order如果经常失败,说明order出现问题,当然一般不是代码写错了,这种一般上线前都跑过好几次的业务。就比如机房着火吧,如果order出现问题,而consumer疯狂的调用有问题的服务,这是不可取的。解决方法就是添加一个检测机制,某某环境下,失败率达到某某百分比时,我们把服务关闭,或者说直接走我们的兜底方法,或者说服务降级的方法。这样既给我们排查问题的时间,也不会把问题不断的堆积,直接降级!
为了演示熔断机制,我们添加一个测试方法:
别害怕,听我说:
breaker/{id}
,我们获取uri路径的id值,如果id是小于0,抛出异常,否则正常通过。//降级走这 public ResultData circuitBreakerHandler(@PathVariable("id")Integer id){ if (id>0){ return new ResultData(500,"输入的id:{"+id+"}为正数,但是服务熔断了",null); } return new ResultData(500,"输入的id:{"+id+"}为负数,抛出一个异常"+id,null); } @HystrixCommand(fallbackMethod = "circuitBreakerHandler",commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled",value = "true"), @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //当报错的概率大于等于60,则熔断服务,只有报错率降低到60以下才开启服务 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), }) @GetMapping("breaker/{id}") public ResultData circuitBreaker(@PathVariable("id")Integer id){ if (id<0){ throw new RuntimeException("输入的id:{"+id+"}为负数,抛出一个异常"); } return new ResultData(200,"输入的id:{"+id+"}为正数,方法测试成功",null); }
测试:
熔断和降级是一起的,所以@Enable注解pom都不用改了!这个得靠录屏演示了!
看完动图很明显的知道熔断的意思了吧,我们一开始输入错误的去增加错误率并且保证请求发送次数大于10次来开启熔断。然后后面输入正确的,但是方法还是走了降级方法,随着我们多试几次,让错误率低于60%,我们的服务又自动恢复了,这就是熔断。
这篇的东西比较好理解的,但是@HystrixCommand的注解配置有点吓人,网上有讲这个的详细配置,这里我只演示了一个简单的服务熔断,看完之后,希望你对微服务不要那么害怕了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。