SpringCloud请看:https://blog.csdn.net/qq_52681418/article/details/113247805
微服务-服务容错
再次之前已经探讨了服务注册、服务调用,它们只是解决了整个应用系统的各个服务的通信问题。
在微服务架构中,所需要应对的大型应用场景,因此需要使服务在大访问量下依然”坚挺“,因此需要进行服务容错的处理,防止过大的访问量导致服务瘫痪。
基本概念:服务降级、服务限流、服务隔离、服务熔断
- 服务熔断:牺牲局部,保全全局,服务出问题时,切断该服务与系统的联系。
- 服务降级:服务不可用时(如熔断后),提供一个低级服务返回信息。
- 服务隔离:使服务之间相互隔离,防止出现雪崩效应。
- 服务限流:使某服务一段时间内只接收指定数量的请求,通过上面的三种手段,即可达到服务的限流。
雪崩效应:服务多级调用时,一个服务拥堵,从而导致多级服务都拥堵。
Hystrix
18年足够稳定,已不再更新。
1.服务熔断
Hystrix内部已经实现了熔断,并提供了默认的熔断配置。我们只需要修改配置即可。
超时设置:默认超过1s没获取到数据,就发生熔断。
hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 2000 #修改为2s后熔断
服务熔断后,往要为该服务配置一个降级服务。
hystrix熔断器
断路器:即被@HystrixCommand注解的方法。
熔断器有3种状态:(默认关闭)
- OPEN:打开:所有请求都进入降级方法。
- CLOSED:关闭:可访问全部请求,统计失败次数,超过给定时间时半开,超过指定时间内失败次数上限时打开。
- HALF_OPEN:半开:打开5s后,进入半开(尝试释放一个请求到服务,访问成功时关闭,否则保持打开)
原理:熔断器默认关闭,但统计对服务请求失败的次数,并设置一个阀值(假设10s内不能失败100次),如果达到这个阀值,熔断器就会进入打开状态,将所有对服务的请求都进行降级操作。但服务不能一直被降级,熔断器每隔5s会尝试释放一个请求到服务,此时为半开状态,如果这个请求访问成功,则关闭断路器。
测试熔断器:修改默认阀值。
#超时多久后熔断 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 2000 #修改为2s后熔断 circuitBreaker: requestVolumeThreshold: 20 #触发熔断的最小请求次数,默认20次/10s sleepWindowInMilliseconds: 10000 #熔断几ms后请求尝试,默认5s errorThresholdPercentage: 50 #触发熔断的失败请求最小占比,默认50%
查看结果需要打开Hystrix实时监控平台并访问:http://localhost:8080/actuator/hystrix.stream
实时监控平台是什么,请参考第4小节。
2.服务降级
服务降级即服务不可用(被熔断)时,将一个请求的处理方法改为一个低级的处理方法,这样做可以在服务不可用时,提供一个低级服务返回信息。Hystrix可以对失败、拒绝、超时的请求进行统一降级处理。
服务降级需要为调用者服务添加依赖:
<!--引入hystrix依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!-- 含@HystrixCommand--> <dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-javanica</artifactId> <version>1.5.12</version> </dependency>
启动类启动 @EnableCircuitBreaker 注解
@SpringBootApplication @EnableFeignClients //激活Fegin (这里通过feign服务调用) @EnableCircuitBreaker//激活Hystrix public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class,args); } }
进行服务降级,需要写降级方法,在服务不可用时去调用它,配置方式有2种:
- 单接口降级:为每个接口单独设置降级方法(优先级较高)。
- 统一降级:为整个类的所有接口设置一个降级方法。(防止写一大堆降级方法)
单接口降级:使用降级方法保护一个接口, @HystrixCommand注解
@Autowired private ImgFeignClient ifc; //配置熔断保护,fallbackMethod:指定熔断后的降级方法 @HystrixCommand(fallbackMethod = "himg") @RequestMapping("/img4/{id}") public Img fignimg(@PathVariable long id){ Img img=ifc.findid(id); return img; } //降级方法,和需要受到保护的方法参数、返回值一致 public Img himg(long id){ Img img=new Img(); img.setName("触发降级方法"); return img; }
这种方法的好处是可以精准地为某接口降级,但接口往往很多,逐一保护,需要写大量降级方法。
统一降级:使用降级方法保护整个类的全部接口, @DefaultProperties注解
@RestController @RequestMapping("/main") @DefaultProperties(defaultFallback = "tyimg")//指定公共的熔断降级方法 public class MainController { @Autowired private ImgFeignClient ifc; //指定统一降级方法,不允许有参数 public Img tyimg(){ Img img=new Img(); img.setName("触发统一降级方法"); return img; } @HystrixCommand//统一指定时使用 @RequestMapping("/img/{id}") public Img fignimg(@PathVariable long id){ Img img=ifc.findid(id); return img; } ...... }
到此服务降级的2种方法已经说完了,不过对于fegin,服务降级可以写在接口实现类种。
Feign的调用方式为在调用者添加接口,对接口使用注解来实现服务调用,然后在控制层调用接口。这和一般的调用相比多了一层接口,因此在feign里,可以对调用的服务接口设置降级。
Feign已经集成了Hystrix,因此无需再导入依赖,只需要添加配置:
feign: hystrix: #开启对hystrix的支持 enabled: true
实现要保护的接口,并将实现类注册到容器:假定调用服务创建的接口为ImgFeignClient 。
@Component public class ImgFeignClientCallBack implements ImgFeignClient { @Override // 熔断降级的方法 public Img findid(long id) { Img img=new Img(); img.setName("触发降级方法"); return img; } }
调用服务的接口上添加注解,指定用来降级的实现类(因为此接口实现不止一个):
// fallback:指定服务降级方法,即实现类 @FeignClient(name="img-service",fallback= ImgFeignClientCallBack.class) public interface ImgFeignClient { // 配置需要调用的微服务接口 @RequestMapping(value = "/img/findimg/{id}",method = RequestMethod.GET) public Img findid( @PathVariable long id); }
为什么说此接口实现类不止一个呢,通过接口加注解来调用服务,不就相当于内置提供了一个实现吗。
3.服务隔离
服务隔离通常有2种方式:
- 信号量隔离
- 线程池隔离
信号量隔离
使用一个原子计数器记录当前运行的线程数,如果超过指定数量,丢弃请求。此方式严格控制线程且立即返回,无法应对突发流量。
信号量隔离配置:
hystrix: command: default: execution: isolation: strategy: ExecutionIsolationStrategy: SEMAPHORE #信号量隔离,线程池隔离为THREAD maxConcurrentRequests: 20 #最大信号量上限
线程池隔离
Tomcat以线程池方式处理请求,当某一服务堵塞,并且压力过大时,可能会造成整个系统的崩溃。
为了不影响其他接口的正常使用,需要对每个服务进行隔离:线程池隔离、信号量隔离。
使用一个线程池存储并处理当前请求,设置任务返回超时时间,堆积的请求堆积入线程池队列。此方式要为所有需要的服务请求线程池(资源消耗略高),可以应对突发流量。
实现服务隔离需要如下配置:
引入依赖
<dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-metrics-event-stream</artifactId> <version>1.5.12</version> </dependency> <dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-javanica</artifactId> <version>1.5.12</version> </dependency>
配置线程池:实现HystrixCommand接口,并进行配置
public class OrderCommand extends HystrixCommand<String> { private RestTemplate restTemplate; private Long id; public OrderCommand(RestTemplate restTemplate, Long id) { super(setter()); this.restTemplate = restTemplate; this.id = id; } @Override//隔离的服务 protected String run() throws Exception { return restTemplate.getForObject("http://localhost/product/"+id, String.class); } @Override//降级方法 protected String getFallback(){ return null; } //服务配置 private static Setter setter() { // 服务分组、//服务标识 、// 线程池名称 HystrixCommandGroupKey gk= HystrixCommandGroupKey.Factory.asKey("order_product"); HystrixCommandKey ck=HystrixCommandKey.Factory.asKey("product"); HystrixThreadPoolKey tpk=HystrixThreadPoolKey.Factory.asKey("order_product_pool"); HystrixThreadPoolProperties.Setter tpp= HystrixThreadPoolProperties.Setter() .withCoreSize(50) //线程池大小 .withKeepAliveTimeMinutes(15) //线程存活时间15s .withQueueSizeRejectionThreshold(100); //队列等待的阈值为100,超过100执行拒绝 策略 // 命令属性配置Hystrix 开启超时 HystrixCommandProperties.Setter cp= HystrixCommandProperties.Setter() // 服务隔离方式:线程池隔离 .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD) .withExecutionTimeoutEnabled(false);// 禁止 return HystrixCommand.Setter .withGroupKey(gk) .andCommandKey(ck) .andThreadPoolKey(tpk) .andThreadPoolPropertiesDefaults(tpp) .andCommandPropertiesDefaults(cp); } }
控制层,进行服务调用
@Autowired private RestTemplate restTemplate; @GetMapping("/buy/{id}") public String order(@PathVariable Long id) throws Exception { return new OrderCommand(restTemplate,id).execute(); }
4.实时监控平台
HystrixCommand与HystrixObservableCommand在执行时,会生成执行结果和运行指标,比如每秒请求数等。这些状态会暴露在Actuator提供的/health的端点中。
导入依赖,并开启hystrix:
<!--引入actuator依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--引入hystrix依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!-- dashboard--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
暴露所有actuator监控的端点:
management: endpoints: web: exposure: include: '*'
此时访问:http://localhost:8080/actuator/hystrix.stream 即可。
仪表板:
但上面获取到的状态为文字,不方便观察,hystrix官方提供了图形化的dashboard(仪表板)监控平台,可以显示每个熔断器的状态。
在启动类上添加注解开启仪表板:@EnableHystrixDashboard
@SpringBootApplication @EnableFeignClients //激活Fegin @EnableCircuitBreaker//激活Hystrix @EnableHystrixDashboard//激活仪表板 public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class,args); } }
访问:http://localhost:8080/hystrix 即可打开仪表板。
输入监控的服务地址:http://localhost:8080/actuator/hystrix.stream,点击按钮即可进入:
上面的界面已经很好地解决了问题,可新问题出现了,此时只对一个服务进行了监控,如果服务很多时,如何进行统一监控呢?
断路器聚合监控Turbine
引入依赖:<!--引入turbine依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-turbine</artifactId> </dependency> <!--引入hystrix依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!-- dashboard--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
添加配置:
turbine: appConfig: img-service #监控多个时用,分隔 clusterNameExpression: "'default'"
开启Turbine:
@SpringBootApplication @EnableFeignClients //激活Fegin @EnableCircuitBreaker//激活Hystrix @EnableHystrixDashboard//激活仪表板 @EnableTurbine//激活turbine public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class,args); } }
在图表工具页面输入:http://localhost:8185/turbine.stream
Sentinel
sentinel是阿里的开源项目,项目地址:https://github.com/alibaba/Sentinel/wiki
与Hystrix相比:Sentinel支持的熔断降级维度更多,可对多种指标进行流控、熔断,且提供了实时监控和控制面板,功能更为强大。
Sentinel使用比较简单,基本上都是UI界面进行设置,使用ui就意味着你需要配置好才能使用。
1.管理面板
下载:https://github.com/alibaba/Sentinel/releases/download/1.6.3/sentinel-dashboard-1.6.3.jar
启动面板:
#启动面板 java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel->dashboard -jar sentinel-dashboard-1.6.3.jar
-Dserver.port指定控制台端口,启动成功后访问:http://localhost:8080/
从版本1.6.0开始,控制台引入登录功能,默认用户名+密码都为sentinel,jdk最低为1.8。
注意:如果服务出不来刷新页面就可以了。
配置sentinel:
引入依赖:
<!--引入sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
为服务提供者添加配置:
spring: cloud: sentinel: transport: dashboard:localhost: 8080
2.服务熔断、服务降级
服务熔断或服务异常后,就应当对接口做降级处理,此时应定义降级方法来处理请求。
sentinel中有两种降级方法:
- 熔断降级
- 抛出异常降级
//blockHandler = "熔断降级方法",fallback = "抛出异常降级方法" //此注解还有value属性:自定义资源名(控制台看着顺眼) @SentinelResource(blockHandler = "funa",fallback = "funb") @RequestMapping("/img/{id}") public Img findimg(@PathVariable long id){ ...... }
可以看出,上面的资源保护只针对了一个接口,如果接口很多时,如何设置通用的降级方法来保护大量接口呢?
对于使用了RestTemplate对象的调用者,可以在RestTemplate对象注册时开启保护:
此时@SentinelResource的属性:
- blockHandler :熔断降级方法
- fallback:异常降级方法
- blockHandlerClass :熔断降级方法所在类
- fallbackClass :异常降级方法所在类
@SpringBootApplication public class MainApplication { //假设降级方法写在了Derated类中 @SentinelResource(blockHandler = "funa",fallback = "funb",fallbackClass = Derated.class,blockHandlerClass = Derated.class) @Bean//将RestTemplate注册到容器 public RestTemplate RestTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(MainApplication.class,args); } }
此时默认资源名格式为:协议+主机+端口+[路径],降级的方法只需写在注解指定的位置即可。
通用降级方法示例:
//统一降级配置类 public class Derated{ //统一熔断降级方法 public Img funa(){ return new Img.setName("熔断降级"); } //统一异常降级方法 public Img funb(){ return new Img.setName("异常降级"); } }
接口的保护设置会在服务重启后被清空,不过可以设置一个默认值:
默认降级配置:
spring: cloud: sentinel: eager: true #立即刷新,比较带劲 datasource: ds1: file: file: classpath:flowrule.json #从本地资源找到flowrule.json data-type: json rule-type: flow
默认的配置是保存在json文本中,json内容如下:
{ { "resource":"Myname", //资源名 "controlBehavior":0, //流量效果 "count":1, //流量阈值 "grade":1, //流量类型 "limitApp":"default", //策略 "strategy":0 } } //具体有哪些属性,可在源码的RuleConstant.java中找到
OpenFeign中虽然内置了,Hystrix,但我们仍能够在使用OpenFeign的同时使用Sentinel来对请求进行服务降级,并且使用方式和Hystrix是一样的。
实现要保护的接口,并将实现类注册到容器:假定调用服务创建的接口为ImgFeignClient 。
@Component public class ImgFeignClientCallBack implements ImgFeignClient { @Override // 熔断降级的方法 public Img findid(long id) { Img img=new Img(); img.setName("触发降级方法"); return img; } }
调用服务的接口上添加注解,指定用来降级的实现类(因为此接口实现不止一个):
// fallback:指定服务降级方法,即实现类 @FeignClient(name="img-service",fallback= ImgFeignClientCallBack.class) public interface ImgFeignClient { // 配置需要调用的微服务接口 @RequestMapping(value = "/img/findimg/{id}",method = RequestMethod.GET) public Img findid( @PathVariable long id); }
为什么说此接口实现类不止一个呢,通过接口加注解来调用服务,不就相当于内置提供了一个实现吗。
接口中方法XXX资源名为:GET:http://服务名/路径/{str}。
SpringBoot Admin 服务监控
Admin用于监控服务,分为服务端可客户端,客户端即服务,因此创建一个Admin server,并在需要监控的服务上添加依赖及简单配置,就可以实现监控了。
服务端
项目依赖,注意版本
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
<version>2.2.2</version>
</dependency>
添加依赖后,只需在启动类添加注解 @EnableAdminServer,并启动即可。(它也是服务,也可注册到注册中心)
客户端
客户端即要监控的服务,在需要被监控的服务上添加依赖和配置即可。
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.2.2</version>
</dependency>
进行简单配置yml
spring: boot: admin: client: url: http://localhost:40000 #admin服务地址 management: endpoints: web: exposure: include: "*"
启动服务端、客户端,访问服务端地址就可以访问UI界面了。