SpringCloud:
微服务的中间件先容与利用
一、Consul:服务注册与发现
当我们要想做到俩个微服务之间的哀求时,会将定义一个变量来存储想要哀求的另外一个微服务的 IP所在和端口号
,但是此时的这个变量是定死在这里的,会存在非常多的问题
列如:
- 有一个微服务的端口号或IP所在发生改变,则调用它的所有微服务都会受到影响
- 假如体系中提供了多个微服务之间的哀求时,则无法实现微服务的负载均衡功能。
- 体系必要支持更高的并发,必要摆设更多的微服务之间的哀求时,硬编码 微服务则后续的维护会变得非常复杂。
这时必要引入服务治理功能,实现微服务之间的动态注册与发现。
1、下载Consul
根据本身的体系下载即可
下载链接:https://developer.hashicorp.com/consul/install
2、运行Consul
- 将解压好的Consul文件放到一个没有中文的目次下,防止运行时出现报错
- 利用终端打开文件目次并输入 Consul -version(参看是否可以识别到Consul)
- 输入启动下令:```Consul agent -dev````
- 访问Consul页面:http://localhost:8500(默认端口8500,实际跟你们的运行时出现的端口号为准)
3、服务注册
①. 导入依赖
- <!--consul服务发现与配置管理中间件-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-consul-discovery</artifactId>
- </dependency>
复制代码 ②. 设置yml
- ## 配置应用名称
- spring:
- application:
- name: cloud-payment-service
- ####Spring Cloud Consul for Service Discovery
- cloud:
- consul:
- host: localhost #输入Consul的IP地址
- port: 8500 #Consul端口号
- discovery:
- service-name: ${spring.application.name} #动态设置服务注册到服务发现组件时的服务名称
复制代码 ③. 启动类添加Consul的启动服务发现注解
@EnableDiscoveryClient
启动代码之后,可以在Consul图形化界面举行查看是否有绑定
④. 解决 硬编码 问题
在设置微服务的RestTemplateConfig类中添加注解@LoadBalanced,解决报错 java.net.UnknownHostException:cloud-payment-service
⑤. 此时便可以将IP所在改为服务名
4、服务设置与刷新
当拆分过多个微服务时,每一个服务都要举行设置,会特殊的麻烦,而Consul就解决了此痛点,
①. 引入Consul-Config依赖
- <!--SpringCloud consul config-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-consul-config</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-bootstrap</artifactId>
- </dependency>
复制代码 ②. 修改bootstrap.yml设置文件
bootstrap与application设置对比:
applicaiton.ymlbootstrap.yml是用户级的资源设置项是体系级的,优先级更加高在bootstrap.yml设置文件中添加一个(这里将springCloud的设置都放到了bootstrap.yml中了) - spring:
- application:
- name: cloud-payment-service
- ####Spring Cloud Consul for Service Discovery
- cloud:
- consul:
- host: localhost
- port: 8500
- discovery:
- service-name: ${spring.application.name}
- ######ConsulConfigYml#####
- config:
- profile-separator: '-' # default value is ",",we update '-'
- format: YAML
复制代码 ③. 根据官方提供的规则(config/服务名,~/data)创建Consul的Key/Value
注意:这里由于我们在上面的profile-separator设置中已经将",“改为”-"所以在创建的时候可以直接 config/服务名- ~~ /data`
④. 将在Consul中设置与服务举行绑定(修改application.yml文件即可)
spring.profiles.active: dev多情况设置加载内容dev/prod,不写就是默认default设置
注意: 这里的可以不消写服务名直接写服务名后面的即可:
⑤. 测试
(这里可以改一下active中的设置多测试几次)
⑥. 动态的获取设置信息
在启动类上加上注解@RefreshScope
设置bootstrap.yml文件规定刷新时间(不设置默认是55秒)
spring.cloud.consul.config.watch.wait-time:1 单位:秒
这里就不做演示了,各人可以本身试一下修改Consul的设置,输出的信息是否会跟着发生改变(修改完之后要重新哀求一次)
参考文档:Spring-cloud-consul
二、LoadBalancer负载均衡服务调用
负载均衡是应对高并发的有效方案之一。
其焦点原理是,当存在多个服务端时,假如大量客户端集中哀求某一个服务端(如服务端 1),
会使其承受巨大压力,而其他服务端(如服务端 2)哀求量相对较少,
此时通过负载均衡机制,将服务端 1 的部门客户端哀求分配至服务端 2 ,以此均衡各服务端的负载。
在分布式体系中,负载均衡后多个服务端之间的数据一致性是一个紧张考量。
根据 CAP 理论,
差别业务场景需在 一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)之间举行衡量。
若业务对数据一致性要求极为严格,常采用满足 CP 理论的架构。
例如,ZooKeeper 和 Consul 便是实现了 CP 理论的组件,它们在包管数据一致性(Consistency)的同时分身分区容错性(Partition
tolerance) ,
能为负载均衡后的分布式体系提供数据一致性保障。
这里不太懂CAP理论的可以看一下这张图:
1、启用LoadBalancer
①在微服务客户端的设置类上添加@LoadBalancer
就是在上面设置consul的RestTemplateConfig的注解,这个就是开启(轮询负载)负载均衡的作用
- /**
- * RestTemplate的配置类,其作用就是在将此配置写入到IoC容器中,这样就只需要注入的方式便可创建RestTemplate
- * 它是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集
- * */
- @Configuration
- public class RestTemplateConfig {
- @Bean
- @LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
- public RestTemplate restTemplate() {
- return new RestTemplate();
- }
- }
复制代码 ②切换负载均衡的算法(随机)
- /**
- * RestTemplate的配置类,其作用就是在将此配置写入到IoC容器中,这样就只需要注入的方式便可创建RestTemplate
- * 它是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集
- *
- * */
- @Configuration
- @LoadBalancerClient(
- //下面的value值大小写一定要和consul里面的名字一样,必须一样
- value = "cloud-payment-service", configuration = RestTemplateConfig.class)
- public class RestTemplateConfig {
- @Bean
- @LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
- public RestTemplate restTemplate() {
- return new RestTemplate();
- }
- /*将负载均衡的算法改为随机算法*/
- @Bean
- ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
- String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
- return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
- }
- }
复制代码 nginx负载均衡和LoadBalancer负载均衡的对比:
Nginx是 服务器负载均衡,客户端所有哀求都会交给nginx,
然后由nginx实现转发哀求,即负载均衡是由服务端实现的。
loadbalancer 当地负载均衡 ,在调用微服务接口时候,会在注册中央上获取注册信息服务列表之后缓存到JVM当地,
从而在当地实现RPC远程服务调用技术。
三、Openfeign远程服务接口调用
概念:openfeign就是将原本服务端要向客户端暴漏的API接口,给它封装为一个接口(OpenFeignApi),这时客户端只必要向这个接口举行访问即可。
优点阐明声明式调用通过注解定义 HTTP 哀求,无需手动处理哀求构建和响应剖析服务发现集成结合服务注册中央(如 consul、Nacos),通过服务名自动路由到具体实例负载均衡内置客户端负载均衡(如 Ribbon 或 Spring Cloud LoadBalancer)熔断与容错可集成 Hystrix、Resilience4J 等实现熔断降级协议透明性支持 RESTful、HTTP/2 等协议,客户端无需关注底层通信细节 1、根本利用
① 引入依赖
- <!--openfeign-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
复制代码 ③ 启动OpenFeign
在启动类上添加一个 @EnableFeignClients即可
- @SpringBootApplication
- @EnableFeignClients
- public class WebApplication {
- public static void main(String[] args) {
- SpringApplication.run(WebApplication.class, args);
- }
- }
复制代码 ② 在common公共类创建一个OpenFeignApi接口,设置公共Api
这里可以分为两步去看:
1、利用OpenFeign:也就是在接口上添加一个注解 @FeignClient(value = "服务名"),标记我要利用OpenFeign,指定 **服务名
**
2、设置公共API
- /*注意这里的服务名,一定要和你的服务名相同,也就是在consul中所配置的那个 */
- @FeignClient(value = "cloud-payment-service")/*标记为OpenFeign接口,value为服务名*/
- public interface openFeignApi {
- @PostMapping("/pay/add")/*这里的网址要与实际调用服务的API接口网址相同*/
- ResultData add(@RequestBody PayDTO payDTO);
- @GetMapping("/pay/getAll")
- ResultData getAll();
- @GetMapping("/pay/getInfo")
- String getInfo();
- }
复制代码 ③ 测试:
此时这三个便是公共Api,可以让客户端举行访问的Api,我们可以在创建一个Model模拟客户端访问
这里只将公共模块的依赖导入,并创建Controller这里的controller大抵也可以分为两步:
1、注入公共APi接口
2、对调用公共APi,并返回
- @RestController
- @RequestMapping("/consumer")
- public class OrderController {
- /*注入公共APi接口*/
- @Resource
- private openFeignApi openFeignApi;
- /*对调用公共APi,并返回*/
- @PostMapping("/add")
- public ResultData add(@RequestBody PayDTO payDTO) {
- return openFeignApi.add(payDTO);
- }
- @GetMapping("/getAll")
- public ResultData getAll() {
- return openFeignApi.getAll();
- }
- @GetMapping("/getInfo")
- public ResultData getInfo() {
- return ResultData.success(openFeignApi.getInfo());
- }
- }
复制代码 2、进阶设置
①OpenFeign日志打印功能
概念:
在OpenFeign中有个一日志功能(logger)
作用是:记载哪些 HTTP 哀求/响应细节(如 Headers、Body 等)
同时它与springBoot中的logging一样也有日志等级(logger.Level)分别:
级别阐明NONE默认的,不显示任何日志;BASIC仅记载哀求方法、URL、响应状态码及执行时间;HEADERS除了 BASIC 中定义的信息之外,另有哀求和响应的头信息;FULL除了 HEADERS 中定义的信息之外,另有哀求和响应的正文及元数据。 但是要注意别和springBoot的logging日志给搞混了,springBoot日志等级分别:
级别阐明TRACE这是最具体的日志级别,用于记载非常过细的信息,在调试极其复杂的问题时大概会用到。例如,记载方法内部每一步的变量值变革等具体信息。DEBUG用于开发调试阶段,记载有助于排查问题的具体信息,但不像 TRACE 那样过于过细。比如记载方法的入参、中间盘算结果等。INFO用于记载应用运行过程中的紧张信息,比如服务启动、关闭信息,关键业务流程的进展等。一样平常用于了解应用团体运行状况。WARN表示出现了一些潜伏问题,但应用还能继续运行。例如,应用利用了一个不推荐的 API,或者某个设置大概存在风险等情况。ERROR用于记载应用运行过程中的错误信息,当发生非常导致应用部门功能无法正常运行时,会记载此级别的日志,方便定位和解决问题。 两者对比:
Feign Client
│
│ 生成 HTTP 哀求/响应日志(内容由 Logger.Level 决定)
▼
SLF4J Logger (Logger 名称为 Feign 接口的全限定名,如 project.user.UserClient)
│
│ 根据 logging.level 设置决定是否输出
▼
控制台/日志文件
另有一个区别就是,springBoot的日志是可以输入到控制台中的,而Feign的日志并不可以必要借助springBoot的日志才可以
开启logger
1、创建Bean对象
- @Configuration/*标记为配置类*/
- public class openFeignConfig {
- /*注意这里的logger是feign.Logger这个包下的,别导错了*/
- @Bean
- Logger.Level openFeignLogLevel() {
- /*修改logger等级*/
- return Logger.Level.FULL;
- }
- }
复制代码 2、修改设置客户端的设置(application.yml)
- #设置springBoot的日志等级,用于输出Feign的日志
- #springboot日志等级设为DEBUG就可以输出Feign的所有日志等级了
- logging:
- level:
- #这里是你要监听哪一个包(例如:com.chyb.cloud.apis)下的那一个接口(例如:openFeignApi)
- com.chyb.cloud.apis.openFeignApi: DEBUG
复制代码 此时变已经设置好了,我们可以先往下看,输出下面的设置的日志
②OpenFeign超时控制
在OpenFeign中可以设置客户端哀求的超时时间
为了演示效果设置一个sleep:在服务端的一个APi接口中设置sleep
- @GetMapping("/getAll")
- @Operation(summary = "查询全部")
- public ResultData<List<Pay>> getAll() {
- try {
- /*阻塞62秒,这里设置62秒的原因是,在Feign中默认的超时时间是60秒,所以设置60秒以上即可*/
- TimeUnit.SECONDS.sleep(62);
- } catch (InterruptedException e) {
- ResultData.fail(ReturnCodeEnum.RC500.getCode(), e.getMessage());
- }
- return ResultData.success(payService.getAll());
- }
复制代码 在客户端调用APi前后输出时间,方便查看
- @GetMapping("/getAll")
- public ResultData getAll() {
- System.out.println("-------支付微服务远程调用,按照id查询订单支付流水信息");
- ResultData resultData = null;
- try {
- System.out.println("调用开始-----:" + DateUtil.now());
- resultData = openFeignApi.getAll();
- } catch (Exception e) {
- /*输出报错信息*/
- e.printStackTrace();
- System.out.println("调用结束-----:" + DateUtil.now());
- ResultData.fail(ReturnCodeEnum.RC500.getCode(), e.getMessage());
- }
- return resultData;
- }
复制代码 在客户端的application.yml中设置超时时间:
- spring:
- cloud:
- openfeign:
- client:
- config:
- #配置默认超时时间
- #default:
- #连接超时时间
- #connectTimeout: 3000
- #读取超时时间
- #readTimeout: 3000
- #配置指定访问某一个服务时的超时时间
- cloud-payment-service:
- #连接超时时间
- connectTimeout: 2000
- #读取超时时间
- readTimeout: 2000
复制代码 如图:
③OpenFeign重试机制
在正常的情况下:当一个哀求超时时,要想再次哀求就必要重新举行发送哀求,这一段时间也是听浪费资源的
这是就可以用到OpenFeign中的一个 重试机制 原理就是当这个哀求超时时并不会直接返回,而是在此基础上向服务端在此发起Api哀求资源,直到哀求成功或者规定的重试次数用完,才举行返回
设置重试机制:
在客户端中的设置类中添加一个Bean对象,即可
- @Bean
- public Retryer myRetryer() {
- // return Retryer.NEVER_RETRY; //Feign默认配置是不走重试策略的
- // 最大请求次数为3(1+2),初始间隔时间为100ms,重试间最大间隔时间为1s
- return new Retryer.Default(100, 1, 3);
- }
复制代码 启动之后如图:
④OpenFeign更改默认HttpClient
HttpClient是什么:
- HttpClient 是 Java 领域功能最全面的 HTTP 客户端库,适合必要精致控制 HTTP 哀求的场景。
- 在微服务架构中,通常不会直接利用 HttpClient,而是通过更高层的工具(如 OpenFeign、RestTemplate)封装调用,但其底层大概依赖
HttpClient 实现通信。
- 公道设置连接池和超时参数,可显著提升性能,制止资源浪费。
在OpenFeign中默认HttpURLConnection没有连接池、性能和服从比力低,假如采用默认,性能上不是最牛B的,所以必要采用HttpClient5(又称hc5),这个可以变相的提升哀求的速度
在 OpenFeign 中 启用 Apache HttpClient 5.x 可以提升性能,尤其是在高并发、高延迟或必要 HTTP/2 的场景下。
这里可以查看官网
所说的:
OpenFeign启用HttpClient5
1、导入依赖
- <!-- httpclient5-->
- <dependency>
- <groupId>org.apache.httpcomponents.client5</groupId>
- <artifactId>httpclient5</artifactId>
- <version>5.3</version>
- </dependency>
- <!-- feign-hc5-->
- <dependency>
- <groupId>io.github.openfeign</groupId>
- <artifactId>feign-hc5</artifactId>
- <version>13.1</version>
- </dependency>
复制代码 2、修改客户端的yml设置文件
- # Apache HttpClient5 配置开启
- spring:
- cloud:
- openfeign:
- httpclient:
- hc5:
- enabled: true
复制代码 启动客户端查看日志
⑤OpenFeign哀求/响应压缩
在OpenFeign中可将哀求或者响应的内容压缩为gzip,举行哀求/响应,这个样子可以提高哀求响应的速度
设置客户端yml设置文件
- spring:
- cloud:
- openfeign:
- #开启对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。
- compression:
- request:
- enabled: true #开启请求压缩
- min-request-size: 2048 #最小触发压缩的大小
- mime-types: text/xml,application/xml,application/json #触发压缩数据类型
- response:
- enabled: true #开启响应压缩
复制代码 效果如图可以在日志信息中查看:
四、CircuitBreaker断路器
先容:CircuitBreaker(断路器)是一套关于断路器的一种规范,其实现者是:Resilience4j、Spring
Retry。(这里我们利用Resilience4j)
CircuitBreaker(断路器)的实现与原理:
在CircuitBreaker中规定了六个状态,
最常用的三个:
状态表明OPEN打开状态,这个时候会进入一个短路的状态,哀求将会有一个降级的机制去执行 兜底 的方案HALF_OPEN半开启状态,会鉴于开启和关闭之间,其作用就是在当由于某一个原因开启了短路时,要想回到关闭状态,并不会直接关闭,而是先去实验几次哀求是否可以成功,假如可以则在关闭,否则相反CLOSED关闭状态,这个时候就是正常的状态不常用的三个:
状态表明METRICS_ONLY:持续收集哀求的各种统计指标DISABLED:强制关闭FORCED_OPEN:强制开启
有了这些状态,就可以更好的控制并发情况下会引发的 雪崩 ,同时提高体系了 可用性 和 健壮性。
Resilience4j
在上述中我们知道了CircuitBreaker(断路器)的这些状态,但是具体要如何举行实现,还是必要利用Resilience4j举行实现
而在Resilience4j中一样平常会利用三大焦点策略:熔断降级、隔离、限流,去控制CircuitBreaker(断路器)
的状态,从而实现了CircuitBreaker(断路器)规范
CircuitBreaker(熔断降级)
概念:当哀求次数的 失败率 或者 慢调用率到达你所设置的值时,便会 熔断 (也就是将状态改为OPEN),此时便会 降级
,并执行 兜底 操纵,过了5s(可以自定义)会从熔断(OPEN) 变为 半开启(HALF_OPEN) 重新实验哀求是否可以 关闭(
CLOSED)
具体流程如下图:
代码演示
这里从演示两个差别的熔断范例(count-based\time-based)
1、count-based演示
①:创建一个服务Api供客户端调用,并利用公共APi接口对外举行暴漏此API
- @RestController
- public class PayCircuitController {
- //=========CircuitBreaker(熔断降级)的演示
- @GetMapping(value = "/pay/circuit/{id}")
- public String myCircuit(@PathVariable("id") Integer id) {
- if (id == -4) throw new RuntimeException("----circuit id 不能负数");
- if (id == 9999) {
- try {
- TimeUnit.SECONDS.sleep(5);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- return "Hello, circuit! inputId: " + id + " \t " + IdUtil.simpleUUID();
- }
- }
复制代码- @FeignClient(value = "cloud-payment-service")/*标记为OpenFeign接口,value为服务名*/
- public interface openFeignApi {
- @GetMapping(value = "/pay/circuit/{id}")
- public String myCircuit(@PathVariable("id") Integer id);
- }
复制代码 ②导入依赖、修改YML设置文件
- <!--resilience4j-circuitbreaker:熔断-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
- </dependency>
- <!-- 由于断路保护等需要AOP实现,所以必须导入AOP包 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
复制代码- spring:
- cloud:
- consul:
- circuitbreaker:
- # 开启circuitbreaker和分组激活
- enabled: true
- group:
- enabled: true #没有分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认(default)最后
- # #################熔断
- # Resilience4j CircuitBreaker 按照次数:COUNT_BASED 的例子
- # 6次访问中当执行方法的失败率达到50%时CircuitBreaker将进入开启OPEN状态(保险丝跳闸断电)拒绝所有请求。
- # 等待5秒后,CircuitBreaker 将自动从开启OPEN状态过渡到半开HALF_OPEN状态,允许一些请求通过以测试服务是否恢复正常。
- # 如还是异常CircuitBreaker 将重新进入开启OPEN状态;如正常将进入关闭CLOSE闭合状态恢复正常处理请求。
- resilience4j:
- circuitbreaker:
- configs:
- #修改默认配置
- default:
- #配置失败率的阀值
- failureRateThreshold: 50 #设置50%的调用失败时打开断路器,超过失败请求百分⽐CircuitBreaker变为OPEN状态。
- #设置滑动窗口(熔断)类型
- slidingWindowType: COUNT_BASED # 滑动窗口的类型为:计数类型
- # 设置实时关注的请求数/时间
- slidingWindowSize: 6 #滑动窗⼝的⼤⼩配置COUNT_BASED表示6个请求,配置TIME_BASED表示6秒
- # 设置最小样本数
- minimumNumberOfCalls: 6 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。如果minimumNumberOfCalls为10,则必须最少记录10个样本,然后才能计算失败率。如果只记录了9次调用,即使所有9次调用都失败,断路器也不会开启。
- #启用半开启状态
- automaticTransitionFromOpenToHalfOpenEnabled: true # 是否启用自动从开启状态过渡到半开状态,默认值为true。如果启用,CircuitBreaker将自动从开启状态过渡到半开状态,并允许一些请求通过以测试服务是否恢复正常
- # OPEN与HALF_OPEN的切换间隔
- waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间
- #半开起状态的最大请求数
- permittedNumberOfCallsInHalfOpenState: 2 #半开状态允许的最大请求数,默认值为10。在半开状态下,CircuitBreaker将允许最多permittedNumberOfCallsInHalfOpenState个请求通过,如果其中有任何一个请求失败,CircuitBreaker将重新进入开启状态。
- #异常记录
- recordExceptions:
- - java.lang.Exception #指定异常并记录
- #这里我又重新写了一个配置,加上default可以更好的理解,注意,若是count_based中没有配置的,则使用默认(default)的配置
- testCount:
- #滑动窗口的大小配置,在count_based表示6个请求,在time_based中表示6s,
- # (若是没有 sliding - window - size 的话,会使判定数据随着时间以及请求次数而增加,且判定的数据也可能会因为以前数据和现在的数据进行结合,使得数据并不会因为最近的数据而发生很大的改变
- # 【也就是两点:数据累积与无时效性(数据没有限制的进行累计)、新旧数据混合影响】)
- sliding-window-size: 500
- # 配置 CircuitBreaker 可以计算错误率或慢速调用率之前所需的最小调用次数 (每个滑动窗口周期)【简单来说就是配置样本的最小采集个数】。例如,如果 minimumNumberOfCalls 为 10,则必须至少记录 10 个调用,然后才能计算失败率,如果只记录了 9 个调用,则即使所有 9 个调用都失败,CircuitBreaker 也不会转换为打开。
- # 这里有一个细节就是,在配置样本次数多时,测试会发现就算是没有到达指定的次数(minimum-number-of-calls)也会只要请求失败的率达到(failure-rate-threshold),也会熔断这是因为Resilience4j有一个类似于"试探性熔断",其作用就是未到达样本次数时,会进行判定是否达到了配置的阀值,若是达到了便会开启熔断
- minimum-number-of-calls: 500
- # 要是还是不理解的话,可以直接将sliding-window-size和minimum-number-of-calls这两个值配置相同即可
- # ---------------配置半开启状态---------------
- automatic-transition-from-open-to-half-open-enabled: true #是否启用自动从开启状态过度到半开启状态,默认为true。如果启用,CircuitBreaker将自动从开启状态过渡到半开状态,并允许一些请求通过以测试服务是否恢复正常
- wait-duration-in-open-state: 5s #从OPEN到HALF_OPEN状态需要等待的时间
- permitted-number-of-calls-in-half-open-state: 2 #在半开启状态允许的最大请求次数,默认值为:10,在半开启状态CircuitBreaker是允许请求的,如果在此期间有有一次请求失败则都会重新进行开启CircuitBreaker
- # --------对指定异常进行记录,且报这个异常的时候也会进行熔断--
- recordExceptions:
- # 当报这个异常的时候熔断
- - java.lang.RuntimeException
- # 忽略某一个异常
- # 忽略某一个异常
- # ignore-exceptions:
- # - java.lang.RuntimeException #当出现此异常时忽略,并不会参与熔断的计算
- # resilience4j.circuitbreaker.instances:精细化配置,对某一个服务(cloud-payment-service)进行指定配置(customer)。
- instances:
- cloud-payment-service:
- base-config: testCount
复制代码 ③在微服务客户端中新建一个类用于测试CircuitBreaker断路器
- @RestController
- public class OrderCircuitController {
- /*注入公共Api接口*/
- @Resource
- private openFeignApi openFeignApi;
- @GetMapping(value = "/feign/pay/circuit/{id}")
- @Operation(summary = "熔断降级的测试请求")
- /*fallbackMethod:是一个设置降级后的兜底方法,当请求熔断之后便会降级,而fallbackMethod会指定一个方法进行兜底操作*/
- @CircuitBreaker(name = "cloud-payment-service", fallbackMethod = "myCircuitFallback")
- public String myCircuitBreaker(@PathVariable("id") Integer id) {
- return openFeignApi.myCircuit(id);
- }
- //myCircuitFallback就是服务降级后的兜底处理方法
- public String myCircuitFallback(Integer id, Throwable t) {
- // 这里是容错处理逻辑,返回备用结果
- return "myCircuitFallback,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
- }
- }
复制代码 测试:这里试着运行几个哀求:
比如:3次id为11的哀求,三次id为-4的哀求,在运行id为11的哀求看一下结果,会发现也会报错了。
这就是由于我们设置的失败率为50%,到达这个值便会直接熔断(OPEN),只有等待到半开启状态才可以正常哀求

2、time-based演示
这里仍旧是利用公共APi所以直接设置yml即可
- resilience4j:
- circuitbreaker:
- configs:
- testTime:
- # 这两个阀值:分别管理失败请求的阀值、慢调用请求的阀值,所以在这里并不冲突
- failureRateThreshold: 50 #设置50%的调用失败时打开断路器,超过失败请求百分⽐CircuitBreaker变为OPEN状态。
- slowCallRateThreshold: 30 #慢调用百分比峰值,断路器把调用时间⼤于slowCallDurationThreshold,视为慢调用,当慢调用比例高于阈值,断路器打开,并开启服务降级
- slowCallDurationThreshold: 2s #慢调用时间阈值,高于这个阈值的视为慢调用并增加慢调用比例。
- slidingWindowType: TIME_BASED # 滑动窗口的类型
- slidingWindowSize: 2 #滑动窗口的大小配置,配置TIME_BASED表示2秒
- minimumNumberOfCalls: 2 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。
- permittedNumberOfCallsInHalfOpenState: 2 #半开状态允许的最大请求数,默认值为10。
- waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间
- recordExceptions:
- - java.lang.Exception
- # 精细化配置,对某一个服务(cloud-payment-service)进行指定配置(customer)
- instances:
- cloud-payment-service:
- base-config: testTime
复制代码 注意:设置这个的时候记得要把设置OpenFeign中设置的超时时间,改的比公共APi中设置的sleep时间少,或者直接注释掉,否则会影响测试
测试,这里当id为9999的时候由于要sleep5秒,且设置2s便为慢调用,所以id=9999是会被视为慢调用,
这时我们可以测试三次9999,三次!=999&&!=-4,的哀求,在此哀求正常的哀求就会发现会报错,这就是time_based的发力ー( ̄~ ̄) ξ,开启了熔断(OPEN)
熔断降级是对哀求失败率/慢调用率,举行判断是否 熔断降级(OPEN),也就是防止故障扩散到上游服务
Bulkhead(隔离)
作用:在服务内部对资源(线程、连接)举行隔离,防止单一故障点耗尽所有资源。
在resilience4中有两种隔离机制:SemaphoreBulkhead(信号量舱壁)、FixedThreadPoolBulkhead(固定线程池舱壁)
如官网:
代码演示
1、SemaphoreBulkhead(信号量舱壁)
根本上就是我们JUC信号灯内容的同样头脑
信号量舱壁(SemaphoreBulkhead)原理:
- 当信号量有空闲时,进入体系的哀求会直接获取信号量并开始业务处理。
- 当信号量全被占用时,接下来的哀求将会进入壅闭状态,SemaphoreBulkhead提供了一个壅闭计时器,
- 假如壅闭状态的哀求在壅闭计时内无法获取到信号量则体系会拒绝这些哀求。
- 若哀求在壅闭计时内获取到了信号量,那将直接获取信号量并执行相应的业务处理。
代码演示:
①:创建一个服务Api供客户端调用,并利用公共APi接口对外举行暴漏此API
- //=========Resilience4j bulkhead(隔离) 的例子
- @GetMapping(value = "/pay/bulkhead/{id}")
- public String myBulkhead(@PathVariable("id") Integer id) {
- if (id == -4) throw new RuntimeException("----bulkhead id 不能-4");
- if (id == 9999 || id == 8888) {
- try {
- TimeUnit.SECONDS.sleep(5);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- return "Hello, bulkhead! inputId: " + id + " \t " + IdUtil.simpleUUID();
- }
复制代码- /**
- * 测试隔离
- * @param id
- * @return
- */
- @GetMapping(value = "/pay/bulkhead/{id}")
- String myBulkhead(@PathVariable("id") Integer id);
复制代码 ②:导入依赖
- <!--resilience4j-bulkhead:隔离-->
- <dependency>
- <groupId>io.github.resilience4j</groupId>
- <artifactId>resilience4j-bulkhead</artifactId>
- </dependency>
复制代码 ③:设置yml文件
- resilience4j:
- #resilience4j bulkhead(隔离)的型号量的例子
- bulkhead:
- configs:
- bulkheadConfig:
- maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量
- maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback
- instances:
- cloud-payment-service:
- base-config: bulkheadConfig
- timelimiter:
- configs:
- bulkheadConfig:
- timeout-duration: 20s #神坑的位置,timelimiter 默认限制远程1s,超于1s就超时异常,配置了降级,就走降级逻辑
复制代码 测试:一次id=9999,一次id=8888(模拟并情况),最后再哀求id=11
则:
2、FixedThreadPoolBulkhead(固定线程池舱壁)代码演示
FixedThreadPoolBulkhead的功能与SemaphoreBulkhead一样也是用于限定并发执行的次数的,
但是二者的实现原理存在差别而且表现效果也存在眇小的差别。
FixedThreadPoolBulkhead利用一个固定线程池和一个等待队列来实现舱壁。
代码演示
①:修改yml
- ####resilience4j bulkhead(隔离)的固定型号量的例子
- resilience4j:
- thread-pool-bulkhead:
- configs:
- default:
- core-thread-pool-size: 1
- max-thread-pool-size: 1
- queue-capacity: 1
- instances:
- cloud-payment-service:
- baseConfig: default
复制代码 设置表明如下图

②:修改调用的Api方法
- /**
- *(船的)舱壁,隔离[固定型号量]
- * @param id
- * @return
- */
- @GetMapping(value = "/feign/pay/bulkhead/{id}")
- @Bulkhead(name = "cloud-payment-service", fallbackMethod = "myBulkheadFallback", type = Bulkhead.Type.THREADPOOL)/*注意要修改一下类型*/
- /*注意这里的返回值要是:CompletableFuture<T>类型,否则会报错*/
- public CompletableFuture<String> myBulkheadTHREADPOOL(@PathVariable("id") Integer id) {
- System.out.println(Thread.currentThread().getName() + "\t" + "enter the method!!!");
- try {
- TimeUnit.SECONDS.sleep(3);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "\t" + "exist the method!!!");
- return CompletableFuture.supplyAsync(() -> openFeignApi.myBulkhead(id) + "\t" + " Bulkhead.Type.THREADPOOL");
- }
- public CompletableFuture<String> myBulkheadPoolFallback(Integer id, Throwable t) {
- return CompletableFuture.supplyAsync(() -> "Bulkhead.Type.THREADPOOL,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~");
- }
复制代码 测试:任意测试id差别的三个哀求
ratelimit(限流)
概念:ratelimit(限流)就是对指定时间段哀求次数过多,从而对他举行限流,防止恶意哀求【频率控制】
实现的算法有:
算法表明图形化表明漏桶算法(Leaky Bucket)一个固定容量的漏桶,按照设定常量固定速率流出水滴,雷同医院打吊针,不管你源头流量多大,我设定匀速流出。假如流入水滴超出了桶的容量,则流入的水滴将会溢出了(被丢弃),而漏桶容量是不变的。令牌桶算法(Token Bucket)体系以固定速率生成令牌存入桶中,哀求需获取令牌才能被处理。 (SpringCloud的默认算法)滚动时间窗(tumbling time window)答应固定命目的哀求进入(比如1秒取4个数据相加,超过25值就over)超过数目就拒绝或者列队,等下一个时间段进入。滑动时间窗口(sliding time window)滑动窗口算法是把固定时间片举行分别并且随着时间移动,移动方式为开始时间点变为时间列表中的第2个时间点,结束时间点增长一个时间点,不停重复,通过这种方式可以巧妙的避开计数器的临界点的问题。 对比
维度漏桶算法令牌桶算法滚动时间窗滑动时间窗焦点目标流量整形(恒定速率)答应突发 + 限流固定周期统计动态时间段统计突发处理❌不答应✅ 答应(依赖桶容量)❌ 窗口内固定✅ 窗口内动态实现复杂度低低低高数据一连性❌不实用❌不实用❌窗口间不一连✅ 窗口间一连典型场景严格限定哀求速率容忍短暂突发的限流周期性报表统计实时精准限流 代码演示
①:创建一个服务Api供客户端调用,并利用公共APi接口对外举行暴漏此API
- //=========Resilience4j ratelimit 的例子
- @GetMapping(value = "/pay/ratelimit/{id}")
- public String myRatelimit(@PathVariable("id") Integer id)
- {
- return "Hello, myRatelimit欢迎到来 inputId: "+id+" \t " + IdUtil.simpleUUID();
- }
复制代码- /**
- * Resilience4j Ratelimit 的例子
- * @param id
- * @return
- */
- @GetMapping(value = "/pay/ratelimit/{id}")
- public String myRatelimit(@PathVariable("id") Integer id);
复制代码 ② 导入依赖
- <!--resilience4j-ratelimiter-->
- <dependency>
- <groupId>io.github.resilience4j</groupId>
- <artifactId>resilience4j-ratelimiter</artifactId>
- </dependency>
复制代码 ③设置yml依赖
- ####resilience4j ratelimiter 限流的例子
- resilience4j:
- ratelimiter:
- configs:
- default:
- limitForPeriod: 2 #在一次刷新周期内,允许执行的最大请求数
- limitRefreshPeriod: 1s # 限流器每隔limitRefreshPeriod刷新一次,将允许处理的最大请求数量重置为limitForPeriod
- timeout-duration: 1 # 线程等待权限的默认等待时间
- instances:
- cloud-payment-service:
- baseConfig: default
复制代码 ④测试
一个哀求多刷新几次便会:
参考资源:spring官网、尚硅谷
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |