Consul
版本
v1.19.0-windows-386
启动
consul agent -dev
访问
端口:8500
http://localhost:8500/ui/dc1/services
配置-服务注册与发现
yaml
spring:
application:
name: cloud-provider-order
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
pom
<!--SpringCloud consul discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- web + actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
依赖记得加 spring-boot-starter-actuator
不然 consul 不显示服务信息
主启动类加 @EnableDiscoveryClient
配置-服务配置与刷新
新增配置文件 bootstrap.yaml
spring:
application:
name: cloud-service-name # 按自己的服务名称来
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
config:
profile-separator: '-' # default value is ",",we update '-'
format: YAML
pom
<!--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>
consul 服务器 key/value 配置填写
config/{level}/data
${spring.application.name} 这部分填 服务名称
${level} 这部分填 作用域,如 dev、prod ...
动态刷新
主启动类加 @RefreshScope
Load Balance
负载均衡
pom
<!--loadbalancer-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
Spring RestTemplate 作为负载均衡器客户端
@Configuration
public class MyConfiguration {
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
public class MyClass {
@Autowired
private RestTemplate restTemplate;
public String doOtherStuff() {
String results = restTemplate.getForObject("http://stores/stores", String.class);
return results;
}
}
负载均衡算法切换
默认使用的 ReactiveLoadBalancer
实现是 RoundRobinLoadBalancer
。要切换到不同的实现,可以为选定的服务或所有服务使用自定义负载均衡器配置机制。
例如,可以通过 @LoadBalancerClient
注释传递以下配置以切换到使用 RandomLoadBalancer
:
public class CustomLoadBalancerConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
OpenFeign
pom
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
-
创建一个 Rest 接口,并添加注解
@FeignClient
即可。@FeignClient(value = "service-name") public interface BarFeignApi { @GetMapping("/hello") String sayHello(); }
-
主启动类添加
@EnableFeignClients(basePackages = "io.purexua.api")
,记得添加包扫描路径。如需配置多个请使用
basePackages = {"com.example.module1", "com.example.module2"}
也可以明确列出它们:
@EnableFeignClients(clients = BarFeignApi.class)
-
服务调用方 controller
@RestController public class Test { @Resource private BarFeignApi barFeignApi; @GetMapping(value = "/hello") public String sayHello() { return "hello,purexua"; } }
超时控制
spring:
cloud:
openfeign:
client:
config:
default:
connectTimeout: 20000
readTimeout: 20000
service-name:
connectTimeout: 20000
readTimeout: 20000
OpenFeign 使用两个超时参数:
connectTimeout
防止由于服务器处理时间过长而阻止呼叫者。readTimeout
是从连接建立时开始应用的,并在返回响应时间过长时触发。
重试机制
默认情况下会创建一个类型为 Retryer
的 Retryer.NEVER_RETRY
bean,这将禁用重试。
请注意,此重试行为与 Feign 的默认行为不同,Feign 会自动重试 IOException,将其视为瞬态网络相关异常,并重试从 ErrorDecoder 抛出的任何 RetryableException。
新增配置类
@Configuration
public class FeignConfig
{
@Bean
public Retryer customRetryer()
{
return new Retryer.Default(100,1,3);
}
}
Default 参数说明
maxAttempts
:最大重试次数,表示在放弃之前尝试请求的最大次数。period
:重试周期,即每次重试之间的初始时间间隔(单位通常为毫秒)。maxPeriod
:重试最大周期,限制了重试间隔时间的上限。当计算得到的重试间隔时间超过这个最大值时,将使用最大周期作为重试间隔。
在 OpenFeign 的默认重试策略retryer.default
中,重试间隔时间并非固定,而是会逐渐增大。具体来说,它会根据一定的规则(例如每次增加一定的倍数)来计算下一次重试的间隔时间。然而,这个间隔时间不能无限增大,maxPeriod
就是用来限制它的最大值。
例如,第一次重试间隔可能是 100 毫秒,第二次是 200 毫秒,第三次是 300 毫秒,以此类推。但如果按照计算规则得到的某次重试间隔时间超过了maxPeriod
,那么就会使用maxPeriod
作为该次的重试间隔时间,而不会使用更大的值。
HttpClient
如果不做特殊配置,OpenFeign 默认使用JDK自带的 HttpURLConnection
发送 HTTP 请求。
从 Spring Cloud OpenFeign 4 开始,不再支持 Feign Apache HttpClient 4。我们建议改用 Apache HttpClient 5。
配置开启
spring:
cloud:
openfeign:
httpclient:
hc5:
enabled: true
请求压缩
可以考虑为您的 Feign 请求启用请求或响应 GZIP 压缩。您可以通过启用以下属性之一来实现这一点:
spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.response.enabled=true
Feign 请求压缩为您提供的设置类似于您为web服务器设置的设置:
spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.request.mime-types=text/xml,application/xml,application/json
spring.cloud.openfeign.compression.request.min-request-size=2048
yaml
spring:
cloud:
openfeign:
compression:
request:
enabled: true
min-request-size: 2048 #最小触发压缩的大小
mime-types: text/xml,application/xml,application/json #触发压缩数据类型
response:
enabled: true
日志打印功能
Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节,说白了就是对 Feign 接口的调用情况进行监控和输出。
为每个创建的 Feign 客户端创建一个日志记录器。 默认情况下,日志记录器的名称是用于创建 Feign 客户端的接口的完整类名。Feign 日志记录仅响应 DEBUG
级别。
yaml 配置
# feign日志以什么级别监控哪个接口
logging:
level:
io:
purexua:
api:
BarFeignApi: debug
您可以为每个客户端配置的 Logger.Level
对象告诉 Feign 要记录多少日志。选择如下:
NONE
,无日志记录(默认)。BASIC
,仅记录请求方法和 URL 以及响应状态码和执行时间。HEADERS
,记录基本信息以及请求和响应头。FULL
,记录请求和响应的头部、正文和元数据。
例如,以下内容将把 Logger.Level
设置为 FULL
:
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
Circuit Breaker
Spring Cloud Circuit breaker provides an abstraction across different circuit breaker implementations. It provides a consistent API to use in your applications allowing you the developer to choose the circuit breaker implementation that best fits your needs for your app.
Circuit Breaker 的目的是保护分布式系统免受故障和异常,提高系统的可用性和健壮性。
当一个组件或服务出现故障时,CircuitBreaker 会迅速切换到开放 OPEN 状态(保险丝跳闸断电),阻止请求发送到该组件或服务从而避免更多的请求发送到该组件或服务。这可以减少对该组件或服务的负载,防止该组件或服务进一步崩溃,并使整个系统能够继续正常运行。同时,CircuitBreaker 还可以提高系统的可用性和健壮性,因为它可以在分布式系统的各个组件之间自动切换,从而避免单点故障的问题。
支持的实现
下面主要介绍 Resilience4J
断路器是通过具有三个正常状态:CLOSED、OPEN 和 HALF_OPEN 以及两个特殊状态 DISABLED 和 FORCED_OPEN 的有限状态机实现的。
CircuitBreaker 使用滑动窗口来存储和聚合调用的结果。您可以选择基于计数的滑动窗口或基于时间的滑动窗口。基于计数的滑动窗口聚合最后 N 次调用的结果。基于时间的滑动窗口聚合最近 N 秒内的调用结果。
CircuitBreaker 断路器
<!--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:
openfeign:
circuitbreaker:
enabled: true
group:
enabled: true #没开分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认最后
配置断路器
- 基于计数 ( 推荐 )
resilience4j:
timelimiter:
configs:
default:
timeout-duration: 20s
circuitbreaker:
configs:
default:
failureRateThreshold: 50
slidingWindowType: COUNT_BASED
slidingWindowSize: 6
minimumNumberOfCalls: 6
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
permittedNumberOfCallsInHalfOpenState: 2
recordExceptions:
- java.lang.Exception
instances:
service-name:
baseConfig: default
- 基于时间
resilience4j:
circuitbreaker:
configs:
default:
failureRateThreshold: 50
slowCallDurationThreshold: 2s
slowCallRateThreshold: 30
slidingWindowType: TIME_BASED
slidingWindowSize: 2
minimumNumberOfCalls: 2
permittedNumberOfCallsInHalfOpenState: 2
waitDurationInOpenState: 5s
recordExceptions:
- java.lang.Exception
instances:
service-user:
baseConfig: default
Example
@RestController
public class BarCircuitController
{
@Resource
private BarFeignApi barFeignApi;
@GetMapping(value = "/hello")
@CircuitBreaker(name = "service-name", fallbackMethod = "myCircuitFallback")
public String sayHello()
{
return "Hello,purexua!";
}
//myCircuitFallback 就是服务降级后的兜底处理方法
public String myCircuitFallback(Integer id,Throwable t) {
return "myCircuitFallback,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
}
}
构建器配置以下属性(Config property - Default Value - Description )
Config property 配置属性 | Default Value 默认值 | Description 描述 |
---|---|---|
failureRateThreshold | 50 | 配置故障率阈值百分比。 当故障率等于或大于阈值时,断路器转换为打开状态并开始短路调用。 |
slowCallRateThreshold | 100 | 配置一个百分比阈值。当调用持续时间大于 slowCallDurationThreshold 时,断路器将认为调用是缓慢的 当慢调用的百分比等于或大于阈值时,断路器会转换为打开状态并开始短路调用。 |
slowCallDurationThreshold | 60000 [ms] | 配置持续时间阈值,超过该阈值的呼叫被视为缓慢,并增加缓慢呼叫的速率。 |
permittedNumberOfCalls | 10 | 配置断路器处于半开状态时允许的调用次数。 |
maxWaitDurationInHalfOpenState | 0 [ms] | 配置最大等待持续时间,控制断路器在半开状态下可能停留的最长时间,然后切换到打开状态。 值为 0 意味着断路器将无限等待在半开状态,直到所有允许的调用完成为止。 |
slidingWindowType | COUNT_BASED | 配置用于记录当断路器关闭时呼叫结果的滑动窗口的类型。 滑动窗口可以是基于计数或基于时间的。 如果滑动窗口是基于计数的,最后 slidingWindowSize 个调用将被记录并聚合。 如果滑动窗口是基于时间的,则记录和聚合最后 slidingWindowSize 秒的调用。 |
slidingWindowSize | 100 | 配置用于记录当断路器关闭时呼叫结果的滑动窗口的大小。 |
minimumNumberOfCalls | 100 | 配置所需的最小呼叫次数(每个滑动窗口期)之前,断路器可以计算错误率或慢速调用率。 例如,如果 minimumNumberOfCalls 是 10,则必须记录至少 10 通电话,然后才能计算故障率。 如果只记录了 9 个调用,即使所有 9 个调用都失败,断路器也不会转换为打开状态。 |
waitDurationInOpenState | 60000 [ms] | 断路器在从打开状态转换到半开状态之前应等待的时间。 |
automaticTransitionFromOpenToHalfOpenEnabled | false | 如果设置为 true,则表示断路器将自动从打开状态过渡到半开状态,无需调用触发过渡。创建一个线程来监视所有断路器的实例,一旦 waitDurationInOpenState 过去,就将它们过渡到 HALF_OPEN 状态。而如果设置为 false,则只有在进行调用后,即使 waitDurationInOpenState 已经过去,也会过渡到 HALF_OPEN 状态。这里的优势是没有线程监视所有断路器的状态。 |
recordExceptions | empty | 记录为失败并因此增加失败率的异常列表。 任何与列表中的一个匹配或继承的异常都算作失败,除非通过 ignoreExceptions 明确忽略。 如果您指定了一组异常列表,除非它们被 ignoreExceptions 明确忽略,否则所有其他异常都将被视为成功。 |
ignoreExceptions | empty | 被忽略的异常列表,既不算作失败也不算作成功。 任何与列表中的一个匹配或继承的异常都不会被视为失败或成功,即使异常是 recordExceptions 的一部分。 |
recordFailurePredicate | throwable -> true 默认情况下,所有异常都被记录为失败。 | 一个自定义的谓词,用于评估异常是否应记录为失败。 如果异常应被视为失败,则谓词必须返回true。如果出现异常,谓词必须返回 false。 应该算作成功,除非 ignoreExceptions 明确忽略异常。 |
ignoreExceptionPredicate | throwable -> false 默认情况下,不会忽略任何异常。 | 一个自定义的谓词,用于评估是否应忽略异常,既不算作失败也不算作成功。 谓词必须返回 true,如果异常应该被忽略。 谓词必须返回 false,如果异常应视为失败。 |
Bulkhead 隔舱
Resilience4j 提供了两种批头模式的实现,可用于限制并发执行的数量:
- 使用信号量的
SemaphoreBulkhead
- 使用有界队列和固定线程池的
FixedThreadPoolBulkhead
SemaphoreBulkhead
应该在各种线程和 I/O 模型上表现良好。它基于信号量,与 Hystrix 不同,不提供“影子”线程池选项。客户端需要确保正确的线程池大小,以保持与船舱配置一致。
- 信号量
信号量舱壁(SemaphoreBulkhead)原理
- 当信号量有空闲时,进入系统的请求会直接获取信号量并开始业务处理。
- 当信号量全被占用时,接下来的请求将会进入阻塞状态,SemaphoreBulkhead提供了一个阻塞计时器,如果阻塞状态的请求在阻塞计时内无法获取到信号量则系统会拒绝这些请求。
- 若请求在阻塞计时内获取到了信号量,那将直接获取信号量并执行相应的业务处理。
pom
<!--resilience4j-bulkhead-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</dependency>
yaml
resilience4j:
bulkhead:
configs:
default:
maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量
maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback
instances:
service-user:
baseConfig: default
Example
@RestController
public class BarCircuitController
{
@Resource
private BarFeignApi barFeignApi;
@GetMapping(value = "/hello")
@Bulkhead(name = "service-name",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.SEMAPHORE)
public String sayHello()
{
return "Hello,purexua!";
}
//myBulkheadFallback 就是限制并发后的兜底处理方法
public String myBulkheadFallback(Throwable t){
return "myBulkheadFallback,隔板超出最大数量限制,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
}
}
您可以使用构建器来配置以下属性。
Config property | Default value | Description |
---|---|---|
maxConcurrentCalls | 25 | 并行执行的最大数量由防护壁允许 |
maxWaitDuration | 0 | 尝试进入饱和舱壁时,线程应被阻塞的最长时间。 |
// Create a custom configuration for a Bulkhead
BulkheadConfig config = BulkheadConfig.custom()
.maxConcurrentCalls(150)
.maxWaitDuration(Duration.ofMillis(500))
.build();
// Create a BulkheadRegistry with a custom global configuration
BulkheadRegistry registry = BulkheadRegistry.of(config);
// Get or create a Bulkhead from the registry -
// bulkhead will be backed by the default config
Bulkhead bulkheadWithDefaultConfig = registry.bulkhead("name1");
// Get or create a Bulkhead from the registry,
// use a custom configuration when creating the bulkhead
Bulkhead bulkheadWithCustomConfig = registry.bulkhead("name2", custom);
- 固定线程池
FixedThreadPoolBulkhead 的功能与 SemaphoreBulkhead 一样也是 用于限制并发执行的次数 的,但是二者的实现原理存在差别而且表现效果也存在细微的差别。FixedThreadPoolBulkhead 使用一个固定线程池和一个等待队列来实现舱壁。
- 当线程池中存在空闲时,则此时进入系统的请求将直接进入线程池开启新线程或使用空闲线程来处理请求。
- 当线程池中无空闲时时,接下来的请求将进入等待队列,若等待队列仍然无剩余空间时接下来的请求将直接被拒绝,在队列中的请求等待线程池出现空闲时,将进入线程池进行业务处理。
另外:ThreadPoolBulkhead 只对 CompletableFuture 方法有效,所以我们必创建返回 CompletableFuture 类型的方法。
yaml
resilience4j:
thread-pool-bulkhead:
configs:
default:
max-thread-pool-size: 1
core-thread-pool-size: 1
queue-capacity: 1
instances:
service-name:
baseConfig: default
Example
@RestController
public class BarCircuitController
{
@Resource
private BarFeignApi barFeignApi;
@GetMapping(value = "/hello")
@Bulkhead(name = "service-name",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<String> sayHello()
{
return CompletableFuture.supplyAsync(() -> "Hello,purexua!" + "\t" + " Bulkhead.Type.THREADPOOL");
}
//myBulkheadFallback 就是限制并发后的兜底处理方法
public CompletableFuture<String> myBulkheadPoolFallback(Integer id,Throwable t)
{
return CompletableFuture.supplyAsync(() -> "Bulkhead.Type.THREADPOOL,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~");
}
}
您可以使用该构建器来配置以下属性。
Config property | Default value | Description |
---|---|---|
maxThreadPoolSize | Runtime.getRuntime() .availableProcessors() | 配置最大线程池大小。 |
coreThreadPoolSize | Runtime.getRuntime() .availableProcessors() - 1 | 配置核心线程池大小 |
queueCapacity | 100 | 配置队列的容量。 |
keepAliveDuration | 20 [ms] | 当线程数大于核心数时,这是多余空闲线程在终止之前等待新任务的最长时间。 |
writableStackTraceEnabled | true | 当抛出批头异常时,输出堆栈跟踪错误。 如果为假,则输出一行带有防波堤异常。 |
ThreadPoolBulkheadConfig config = ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(10)
.coreThreadPoolSize(2)
.queueCapacity(20)
.build();
// Create a BulkheadRegistry with a custom global configuration
ThreadPoolBulkheadRegistry registry = ThreadPoolBulkheadRegistry.of(config);
// Get or create a ThreadPoolBulkhead from the registry -
// bulkhead will be backed by the default config
ThreadPoolBulkhead bulkheadWithDefaultConfig = registry.bulkhead("name1");
// Get or create a Bulkhead from the registry,
// use a custom configuration when creating the bulkhead
ThreadPoolBulkheadConfig custom = ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(5)
.build();
ThreadPoolBulkhead bulkheadWithCustomConfig = registry.bulkhead("name2", custom);
RateLimiter 速率限制器
Rate limiting is an imperative technique to prepare your API for scale and establish high availability and reliability of your service. But also, this technique comes with a whole bunch of different options of how to handle a detected limits surplus, or what type of requests you want to limit. You can simply decline this over limit request, or build a queue to execute them later or combine these two approaches in some way.
速率限制是一种必不可少的技术,可以为您的 API 做好扩展准备,并确保您的服务具有高可用性和可靠性。但是,这种技术也带来了一系列不同的选项,用于处理检测到的限制超额,或者您想要限制的请求类型。您可以简单地拒绝超过限制的请求,或者构建一个队列以稍后执行它们,或者以某种方式结合这两种方法。
pom
<!--resilience4j-ratelimiter-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
</dependency>
yaml
resilience4j:
ratelimiter:
configs:
default:
limitForPeriod: 2
limitRefreshPeriod: 1s
timeout-duration: 1
instances:
service-name:
baseConfig: default
Example
@RestController
public class BarCircuitController
{
@Resource
private BarFeignApi barFeignApi;
@GetMapping(value = "/hello")
@TimeLimiter(name = "yourTimeLimiterName", fallbackMethod = "myTimeLimiterFallback")
public String sayHello()
{
return "Hello,purexua!";
}
//myTimeLimiterFallback 就是超时后的兜底处理方法
public void myTimeLimiterFallback(Throwable t) {
// 超时后的回退处理逻辑
}
}
您可以使用该构建器来配置以下属性。
Config property | Default value | Description |
---|---|---|
timeoutDuration | 5 [s] | 线程等待权限的默认等待时间 |
limitRefreshPeriod | 500 [ns] | 限制刷新的周期。每个周期结束后,速率限制器将其权限计数重置为限制值。 |
limitForPeriod | 50 | 在一个限制刷新周期内可用的权限数量 |
例如,您希望限制某些方法的调用速率不高于 10 req/ms。
RateLimiterConfig config = RateLimiterConfig.custom()
.limitRefreshPeriod(Duration.ofMillis(1))
.limitForPeriod(10)
.timeoutDuration(Duration.ofMillis(25))
.build();
// Create registry
RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.of(config);
// Use registry
RateLimiter rateLimiterWithDefaultConfig = rateLimiterRegistry
.rateLimiter("name1");
RateLimiter rateLimiterWithCustomConfig = rateLimiterRegistry
.rateLimiter("name2", config);
TimeLimiter 时间限制器
就像断路器模块一样,该模块提供了一个内存中的 TimeLimiterRegistry
,您可以使用它来管理(创建和检索)TimeLimiter 实例。
TimeLimiterRegistry timeLimiterRegistry = TimeLimiterRegistry.ofDefaults();
pom
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-timelimiter</artifactId>
</dependency>
yaml
resilience4j:
timelimiter:
configs:
default:
timeout-duration: 20s
cancel-running-future: false
您可以使用该构建器进行配置:
- the timeout duration 超时持续时间
- whether cancel should be called on the running future 是否应该在运行中的未来上调用取消
Retry 重试
pom
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
</dependency>
resilience4j:
retry:
configs:
default:
maxAttempts: 3 # 总尝试次数
waitDuration: 10s # 重试间隔时间
enableExponentialBackoff: false # 是否允许使用指数退避算法进行重试间隔时间的计算
exponentialBackoffMultiplier: 2 # 指数退避算法的乘数
enableRandomizedWait: false # 是否允许使用随机的重试间隔
randomizedWaitFactor: 0.5 # 随机因子
retryExceptions:
- im.marlinlm.exception.RetryException # 触发重试的异常类
ignoreExceptions:
- im.marlinlm.exception.RetryIgnoredException # 不会触发重试的异常类
instances:
service-name:
baseConfig: default
waitDuration: 1 # 1 毫秒,最小等待时间为 1 毫秒
@RestController
public class BarCircuitController
{
@Resource
private BarFeignApi barFeignApi;
// name 参数的值必须与配置文件中的 instances 下的名称一致
@Retry(name = "myRetry", fallbackMethod = "doSomethingFallback")
public int doSomething(String someInput){
// 写一些会抛异常的代码
}
// 方法名必须与 @Retry 注解的 fallbackMethod 参数的值一致
// 返回类型必须与 retry 注解的方法的返回类型一致
// 必须传入 Throwable 参数
private int doSomethingFallback(Throwable t){
// fallback 逻辑,只有重试次数用完了才会运行此代码
}
// 可以用更加明确的异常类型,retry 会优先使用匹配得更加准确的 fallback 方法。
private int doSomethingFallback(RuntimeException e){
// fallback 逻辑
}
// 可以传入注解了 @Retry 的方法的参数
private int doSomethingFallback(String someInput, Throwable t){
// fallback 逻辑
}
}
您可以使用该构建器进行配置:
Config property 配置属性 | Default value 默认值 | Description 描述 |
---|---|---|
maxAttempts | 3 | 尝试次数的最大值(包括初始呼叫作为第一次尝试) |
waitDuration | 500 [ms] | 重试尝试之间的固定等待持续时间 |
intervalFunction | numOfAttempts -> waitDuration | 修改失败后的等待间隔的功能。 默认情况下,等待持续时间保持不变。 |
intervalBiFunction | (numOfAttempts, Either<throwable, result>) -> waitDuration | 根据尝试次数和结果或异常修改失败后的等待间隔的函数。 与 intervalFunction 一起使用时会抛出 IllegalStateException。 |
retryOnResultPredicate | result -> false | 配置一个谓词,用于评估是否应重试结果。 如果结果应该重试,则谓词必须返回 true,否则必须返回 false。 |
retryExceptionPredicate | throwable -> true | 配置一个谓词,用于评估是否应重试异常。 如果异常应该重试,则谓词必须返回 true,否则必须返回 false。 |
retryExceptions | empty | 配置一个 Throwable 类的列表,这些类被记录为失败,因此会被重试。 此参数支持子类型。 注意:如果您正在使用已检查的异常,则必须使用 CheckedSupplier |
ignoreExceptions | empty | 配置一个忽略的 Throwable 类列表,因此不会重试。 此参数支持子类型。 |
failAfterMaxAttempts | false | 一个布尔值,用于启用或禁用在重试达到配置的最大尝试次数并且结果仍未通过 retryOnResultPredicate 时抛出 MaxRetriesExceededException |
RetryConfig config = RetryConfig.custom()
.maxAttempts(2)
.waitDuration(Duration.ofMillis(1000))
.retryOnResult(response -> response.getStatus() == 500)
.retryOnException(e -> e instanceof WebServiceException)
.retryExceptions(IOException.class, TimeoutException.class)
.ignoreExceptions(BusinessException.class, OtherBusinessException.class)
.failAfterMaxAttempts(true)
.build();
// Create a RetryRegistry with a custom global configuration
RetryRegistry registry = RetryRegistry.of(config);
// Get or create a Retry from the registry -
// Retry will be backed by the default config
Retry retryWithDefaultConfig = registry.retry("name1");
// Get or create a Retry from the registry,
// use a custom configuration when creating the retry
RetryConfig custom = RetryConfig.custom()
.waitDuration(Duration.ofMillis(100))
.build();
Retry retryWithCustomConfig = registry.retry("name2", custom);
Sleuth(Micrometer) + ZipKin
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。
分布式链路追踪技术要解决的问题,分布式链路追踪(Distributed Tracing),就是将一次分布式请求还原成调用链路,进行日志记录,性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。
Zipkin是一种分布式链路跟踪系统图形化的工具,Zipkin 是 Twitter 开源的分布式跟踪系统,能够收集微服务运行过程中的实时调用链路信息,并能够将这些调用链路信息展示到Web图形化界面上供开发人员分析,开发人员能够从ZipKin中分析出调用链路中的性能瓶颈,识别出存在问题的应用程序,进而定位问题和解决问题。
也就是说:Micrometer - 数据采集 + ZipKin - 图形展示
下载 运行 ZipKin
java -jar zipkin-servr-xxxx.jar
pom
<!--micrometer-tracing指标追踪 1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!--micrometer-observation 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<!--feign-micrometer 4-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>
<!--zipkin-reporter-brave 5-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
yaml
management:
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
tracing:
sampling:
probability: 1.0 #采样率默认为0.1(0.1就是10次只能有一次被记录下来),值越大收集越及时。
最后执行接口调用就可以看到调用信息了,通过 http://localhost:9411/api/v2/spans
GateWay
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(Pre)或之后(Post)执行业务逻辑。
在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;
在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
pom
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
网关和业务无关
yaml 示例
server:
port: 9527
spring:
application:
name: cloud-provider-gateway
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
default-filters:
- AddResponseHeader=X-Request-global-filter, global
routes:
- id: pay_routh1
uri: http://localhost:8001
predicates:
- Path=/pay/gateway/get/**
- id: pay_routh2
uri: http://localhost:8001
predicates:
- Path=/pay/gateway/info/**
- id: pay_routh3
uri: http://localhost:8001
predicates:
- Path=/pay/gateway/filter/**
filters:
- AddRequestHeader=X-Request-gateway-filter1, gateway
- AddRequestHeader=X-Request-gateway-filter2, gateway
ReactiveLoadBalancerClientFilter
示例的 url 不是写死了吗。
Route 以微服务名-动态获取服务 URL
ReactiveLoadBalancerClientFilter
在名为ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR
的交换属性中查找 URI。如果 URL 具有lb
方案(例如lb://myservice
),它将使用 Spring CloudReactorLoadBalancer
来解析名称(在此示例中为myservice
)为实际主机和端口,并替换相同属性中的 URI。未修改的原始 URL 将附加到ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR
属性中的列表中。过滤器还会查看ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR
属性,以查看它是否等于lb
。如果是,则应用相同的规则。以下清单配置了一个ReactiveLoadBalancerClientFilter
:
spring:
cloud:
gateway:
routes:
- id: myRoute
uri: lb://service
predicates:
- Path=/service/**
Route Predicate Factories
Spring Cloud Gateway 将路由匹配作为 Spring WebFlux
HandlerMapping
基础设施的一部分。Spring Cloud Gateway 包含许多内置的路由谓词工厂。所有这些谓词都匹配 HTTP 请求的不同属性。您可以使用逻辑and
语句组合多个路由谓词工厂。
参考 spring 官方文档:https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/request-predicates-factories.html
可自定义断言。按照一定规则实现 RoutePredicateFactory
即可。
Filter Factories
参考 spring 官方文档:https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories.html
提供三种过滤器
- 全局默认过滤器 Global Filters
- 单一内置过滤器 Gateway Filters
- 自定义过滤器