springCloud/Alibaba常用中间件全集(上)
文章目录
- SpringCloud:
- 一、Consul:服务注册与发现
- 1、下载Consul
- 2、运行Consul
- 3、服务注册
- ①. 导入依赖
- ②. 配置yml
- ③. 启动类添加Consul的启动服务发现注解
- ④. 解决 **硬编码** 问题
- ⑤. 此时便可以将IP地址改为服务名
- 4、服务配置与刷新
- ①. 引入Consul-Config依赖
- ②. 修改bootstrap.yml配置文件<br>
- ③. 根据官方提供的规则(```config/服务名,~/data```)创建Consul的Key/Value <br>
- ④. 将在Consul中配置与服务进行绑定(修改application.yml文件即可)
- ⑤. 测试
- ⑥. 动态的获取配置信息
- 二、LoadBalancer负载均衡服务调用
- 1、启用LoadBalancer
- ①在微服务客户端的配置类上添加@LoadBalancer
- ②切换负载均衡的算法(随机)
- 三、Openfeign远程服务接口调用
- 1、基本使用
- ① 引入依赖
- ③ 启动OpenFeign
- ② 在common公共类创建一个OpenFeignApi接口,配置公共Api
- ③ 测试:
- 2、进阶配置
- ①OpenFeign日志打印功能
- 开启logger
- ②OpenFeign超时控制
- ③OpenFeign重试机制
- ④OpenFeign更改默认HttpClient
- ⑤OpenFeign请求/响应压缩
- 四、CircuitBreaker断路器
- Resilience4j
- CircuitBreaker(熔断降级)
- 代码演示
- Bulkhead(隔离)
- 代码演示
- ratelimit(限流)
- 代码演示
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.yml bootstrap.yml 是用户级的资源配置项 是系统级的,优先级更加高 在bootstrap.yml配置文件中添加一个(这里将springCloud的配置都放到了bootstrap.yml中了) spring: application:name: cloud-payment-service ####Spring Cloud Consul for Service Discovery cloud:consul:host: localhostport: 8500discovery: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();}/*将负载均衡的算法改为随机算法*/@BeanReactorLoadBalancer<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接口*/@Resourceprivate 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这个包下的,别导错了*/@BeanLogger.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,重试间最大间隔时间为1sreturn 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: truegroup: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接口*/@Resourceprivate 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秒,过时不候进舱壁兜底fallbackinstances:cloud-payment-service:base-config: bulkheadConfigtimelimiter: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: 1max-thread-pool-size: 1queue-capacity: 1instances: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刷新一次,将允许处理的最大请求数量重置为limitForPeriodtimeout-duration: 1 # 线程等待权限的默认等待时间instances:cloud-payment-service:baseConfig: default
④测试
一个请求多刷新几次便会:
参考资源:spring官网、尚硅谷