服务雪崩
微服务架构中,系统通常包括多个服务层。微服务之间通过网络进行通信,从而支撑起整个应用系统,因此,服务间的依赖就难免会存在。而每个微服务都不能保证自己时时刻刻百分百可用。当一个微服务挂掉之后,其他微服务调用这个挂掉微服务的都不可用了。把这种基础服务故障导致级联故障的现象称为雪崩效应。
简单点来说就是服务A 调用服务B ,而服务B又去调用服务C,服务D(这样的调用过程就是服务扇出)。而在某条扇出的服务调用链路中有一个服务,由于响应时间过程或者抛出异常,导致服务调用者被占用越来越多资源,从而导致整个系统奔溃。
解决方案
要想防止雪崩效应,就要一个强大的容错机制。容错机制一般需要实现以下两点。
为网络请求设置超时:为每个网络请求设置超时时间,超时不再等待,让资源尽快释放。
使用断路器模式:断路器就相当于家里的自动跳闸的。当请求错误失败数较多,或者有大量超时的请求,就会自动停止对该微服务的请求。
Hystrix简介
Hystrix是一个容错管理工具,设计目的是将应用中的远程系统访问、服务调用、第三方依赖包的调用入口,通过资源控制的方式隔离开,避免了在分布式系统中失败的级联塌方式传递,提升系统的弹性和健壮性。
Hystrix 容错机制
包裹请求: 使用HystrixCommand注解包裹对依赖的调用逻辑,每个命令都在独立的线程中执行(命令模式)。
跳闸机制: 当某服务的错误率超过了一定的阈值,Hystrix可以自动或者手动跳闸,停止对该服务的访问。
资源隔离: Hystrix为每个历来都维护了一个小型的线程池。如果该线程池已满,发往该依赖的请求就会被拒绝,而不是排队等候。从而加速失败的判定。
监控: Hystrix可以实时的监控运行指标和配置的变化。
回退机制: 当请求失败、超时、被拒绝时,或者当断路器被打开时执行回退的逻辑,回退的逻辑可以自己提供。
自我修复: 断路器打开一段时间后,会自动进入半开状态。
Hystrix入门
<!--1. 配置pom文件,引入spring-cloud-starter-netflix-hystrix包--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!- 2. 在服务消费方继承HystrixCommand,实现Hystrix的服务降级-> public class OrderHystrixCommand extends HystrixCommand<List<OrderVo>> { private String userId; private RestTemplate restTemplate; public OrderHystrixCommand(String commandGroupKey, RestTemplate restTemplate, String userId) { super(HystrixCommandGroupKey.Factory.asKey(commandGroupKey)); this.restTemplate = restTemplate; this.userId = userId; } @Override protected List<OrderVo> run() throws Exception { ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://MS-PROVIDER-ORDER/order/queryOrderByUserId/"+userId, List.class); List<OrderVo> orderVoList = responseEntity.getBody(); return orderVoList; } @Override protected List<OrderVo> getFallback() { OrderVo orderVo = new OrderVo(); orderVo.setOrderId(-1); orderVo.setUserId(-1); orderVo.setOrderMoney(new BigDecimal(0)); List<OrderVo> orderVos = new ArrayList<>(); orderVos.add(orderVo); return orderVos; } } <!- 3. 在服务消费方调用 -> OrderHystrixCommand ohc = new OrderHystrixCommand("orderGroupKey", restTemplate, userId); List<OrderVo> orderVoList = ohc.execute();
备注:我们可以做几个简单的测试,来对Hystrix有的服务降级有一个初步的了解:
宕机跳闸:关闭一个MS-PROVIDER-ORDER服务提供者,尝试看会出现什么结果,能不能调用我们的getFallback方法;
超时跳闸:在服务提供方的被调用方法中设置线程睡眠时间,同时我们通过ribbon参数将服务消费者的超时,让超时时间小于睡眠时间,尝试看会出现什么结果。
异常跳闸:查询一个不存在的订单模拟抛出异常,尝试看会出现什么结果。
超时跳闸时一些常用配置
#设置全局的超时时间 ribbon.ReadTimeout=1000 ribbon.ConnectTimeout=1000 #出现异常的时候 ,当前实例进行重试 ribbon.MaxAutoRetries=1 #切换实例重试的次数 ribbon.MaxAutoRetriesNextServer=1 #对有所的的操作进行重试(all是对有所方法进行重试,true对有所的重试,false对get重试) #如果为True需要保证接口的幂等性 ribbon.OkToRetryOnAllOperations=true #开启重试机制(默认是开启的) spring.cloud.loadbalancer.retry.enabled=true #局部配置 微服务实例名称.ribbon.ConnectTimeout=1000 微服务实例名称.ribbon.ReadTimeout=1000 微服务实例名称.ribbon.OkToRetryOnAllOperations=true 微服务实例名称.ribbon.MaxAutoRetriesNextServer=1 微服务实例名称.ribbon.MaxAutoRetries=1
通过 @HystrixCommand实现服务降级
fallbackMethod方法需要和主方法有一样的入参,不然会报错。一般来说只对调用频率高的接口配置服务降级。
@HystrixCommand 虽然比继承HystrixCommand要方便,但在实际中用的并不多,因为配置的过多会带来难以维护的问题。
@HystrixCommand(fallbackMethod="queryOrderFallBack") @RequestMapping("testFallback/{userId}") public EmployeeInfo testHystrixFallback(@PathVariable("userId") String userId){ Employee employee = employeeService.findEmployeeById(userId); ResponseEntity<List> responseEntity; responseEntity = restTemplate.getForEntity("http://MS-PROVIDER-ORDER/order/queryOrderbyUserId/" + userId, List.class); List<OrderVo> orderVoList = responseEntity.getBody(); EmployeeInfo employeeInfo = new EmployeeInfo(); employeeInfo.setEmployeeName(employee.getName()); employeeInfo.setOrderVoList(orderVoList); return employeeInfo; } protected EmployeeInfo queryOrderFallBack(String userId) { EmployeeInfo employeeInfo = new EmployeeInfo(); employeeInfo.setEmployeeName("FallBack"); employeeInfo.setOrderVoList(null); return employeeInfo; }
Hystrix整合Feign
<!--1. 配置pom文件,引入jar包 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- 2. 配置属性文件 --> #开启feign对hystrix的支持,默认是关闭的。 feign.hystrix.enabled=true <!-- 3. 在Spring的启动入口添加@EnableFeignClients注解 --> @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class HystrixProvider8006Application { public static void main(String[] args) { SpringApplication.run(HystrixApplication.class, args); } } <!-- 4. 定义Feign的接口类,通过FeignClient中的fallback属性实现Hystrix的服务降级 --> @FeignClient(name = "ms-provider-order", fallback = CustomFeignOrderApiFallBack.class, path = "/order") public interface CustomFeignOrderApi { @RequestMapping("/queryOrdersByUserId/{userId}") List<OrderVo> queryOrdersByUserId(@PathVariable("userId") String userId); @RequestMapping("/getRegisterInfo") String getRegisterInfo(); } @Component public class CustomFeignOrderApiFallBack implements MsCustomFeignOrderApi { @Override public List<OrderVo> queryOrdersByUserId(String userId) { List<OrderVo> orderVoList = new ArrayList<>(); OrderVo orderVo = new OrderVo(); orderVo.setUserId(userId); orderVo.setOrderId(-1); orderVoList.add(orderVo); return orderVoList; } @Override public String getRegisterInfo() { return "服务异常"; } }
备注:我们可以同上实验一下
Hystrix的一些常用配置
1. 关闭Hystrix对feign的支持 全局关闭:feign.hystrix.enabled=false(默认是关闭的) 局部关闭: 写一个配置类,在配置类中写一个FeignBulider的类: public class CustomeFeignApiWithoutHystrixConfg { @Scope("prototype") @Bean public Feign.Builder feignBuilder() { return Feign.builder(); } } 在FeignClient中指配置文件 @FeignClient(name = "MS-PROVIDER-ORDER",configuration = MsCustomeFeignApiWithoutHystrixConfg.class, path = "/order") 2. 关闭熔断 全局关闭:hystrix.command.default.circuitBreaker.enabled=false 局部关闭:hystrix.command.<HystrixCommandKey>.circuitBreaker.enabled: false 其中的HystrixCommandKey是个变量,可以打开服务的hystrix.stream端点即可看到,也可在Hystrix Dashboard中查看。 3. 设置超时 全局超时:hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 1000 局部关闭:hystrix.command.<HystrixCommandKey>.execution.isolation.thread.timeoutInMilliseconds: 1000 4. 关闭超时 全局关闭:hystrix.command.default.execution.timeout.enabled: false 局部关闭:hystrix.command.<HystrixCommandKey>.execution.timeout.enabled: false