Resilience4J 是一个针对 Java 8 应用程序的轻量级容错和弹性库。它设计用于在分布式系统中的服务之间提供弹性和容错性。Resilience4J 的名字来源于它提供的核心功能,即让系统(服务)能够“弹性”(resilient)地应对各种失败情况,包括网络问题、第三方服务故障等。
Resilience4J 提供了以下功能:
Resilience4J 的一大特点是它的轻量级特性,它只使用了 Vavr 库(一个函数式编程库),没有其他外部库依赖。这使得它在集成到现有系统时非常方便,且性能开销小。
Resilience4J 设计上易于配置,支持通过代码、配置文件或运行时参数进行配置。它也支持通过 actuator 模块与 Spring Boot 的监控和管理特性集成。
由于 Resilience4J 的这些特性和优势,它在现代分布式系统和微服务架构中得到了广泛应用,尤其是在需要高可用性和弹性的环境中。
https://resilience4j.readme.io/
https://github.com/resilience4j/resilience4j
https://resilience4j.readme.io/docs/ratelimiter
RateLimiter
的默认实现是 AtomicRateLimiter
,它通过 AtomicReference
管理其状态。 AtomicRateLimiter.State
是完全不可变的。
功能点:
Warm-Up Period: 当启动应用程序或重置后,可能会有一个预热期,在此期间速率限制器逐渐增加允许的请求速率。这是为了防止启动后流量突然激增,从而可能导致系统过载。
Steady State: 预热期结束后,速率限制器进入稳定状态。在此阶段,速率限制器根据配置的速率限制允许请求通过。例如,如果将限制设置为每分钟 100 个请求,则速率限制器将允许大约每 0.6 秒一个请求。
Limit Exceeded: 如果传入请求速率超过配置的限制,速率限制器立即开始拒绝超出的请求。
Replenishing Tokens: 速率限制器以与配置的限制相对应的速率持续补充“Token”。每个允许的请求消耗一个令牌。如果系统未充分利用允许的速率,则未使用的令牌会累积,从而允许偶尔爆发请求。
Cooldown Period: 如果速率限制器因超出速率限制而拒绝请求,则可能存在一个冷却期,在此期间速率限制器会再次逐渐增加允许的请求速率。这是为了防止限制放宽后流量突然激增。
我们的演示有 2 个服务,名为支付服务和支付处理器。
我们将对支付服务实施速率限制,以控制传入付款请求的速率。
首先构建支付处理器,因为它是一个依赖服务.
为了演示的目的,将其简化为显示成功消息
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>3.1.2version>
<relativePath/>
parent>
<groupId>com.artisangroupId>
<artifactId>payment-processorartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>payment-processorname>
<properties>
<java.version>17java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
project>
server:
port: 1010
spring:
application:
name: payment-processor
package com.artisan.paymentprocessor.service;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
public interface PaymentProcessorService {
String processPayment(String paymentInfo);
}
package com.artisan.paymentprocessor.service.impl;
import org.springframework.stereotype.Service;
import com.artisan.paymentprocessor.service.PaymentProcessorService;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@Service
public class PaymentProcessorServiceImpl implements PaymentProcessorService {
@Override
public String processPayment(String paymentInfo) {
// Simulated logic to process payment
return "Payment processed: " + paymentInfo;
}
}
package com.artisan.paymentprocessor.controller;
import com.artisan.paymentprocessor.service.PaymentProcessorService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
/**
* @author artisan
*/
@RestController
@RequestMapping("/api/v1/processor-payment")
@RequiredArgsConstructor
public class PaymentProcessorController {
private final PaymentProcessorService paymentProcessorService;
@PostMapping
public String processPayment(@RequestBody String paymentInfo) {
return paymentProcessorService.processPayment(paymentInfo);
}
}
测试一下:
我们将配置 Rate Limiter,并通过 Actuator 监控其状态 。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>3.1.2version>
<relativePath/>
parent>
<groupId>com.artisangroupId>
<artifactId>payment-serviceartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>payment-servicename>
<properties>
<java.version>17java.version>
<spring-cloud.version>2022.0.4spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4jartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
project>
public interface Type {
}
@Data
public class Success implements Type {
private final String msg;
}
@Data
public class Failure implements Type {
private final String msg;
}
如何调用外部API -------------->我们这里使用 Spring的 RestTemplate
。
package com.artisan.paymentservice.service;
import com.artisan.paymentservice.model.Type;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
public interface PaymentService {
Type submitPayment(String paymentInfo);
}
package com.artisan.paymentservice.service.impl;
import com.artisan.paymentservice.model.Failure;
import com.artisan.paymentservice.model.Success;
import com.artisan.paymentservice.model.Type;
import com.artisan.paymentservice.service.PaymentService;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import lombok.RequiredArgsConstructor;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@Service
@RequiredArgsConstructor
public class PaymentServiceImpl implements PaymentService {
private final RestTemplate restTemplate;
private static final String SERVICE_NAME = "payment-service";
private static final String PAYMENT_PROCESSOR_URL = "http://localhost:1010/api/v1/processor-payment";
@Override
@RateLimiter(name = SERVICE_NAME, fallbackMethod = "fallbackMethod")
public Type submitPayment(String paymentInfo) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(paymentInfo, headers);
ResponseEntity<String> response = restTemplate.exchange(PAYMENT_PROCESSOR_URL,
HttpMethod.POST, entity, String.class);
Success success = new Success(response.getBody());
return success;
}
private Type fallbackMethod(RequestNotPermitted requestNotPermitted) {
return new Failure("服务降级: Payment service does not permit further calls");
}
}
重点关注: @RateLimiter(name = SERVICE_NAME, fallbackMethod = "fallbackMethod")
需要注意这两种方法应该返回相同的数据类型, 所以对两个模型类都使用“Type”来实现。
package com.artisan.paymentservice.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@Configuration
public class RestConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
package com.artisan.paymentservice.controller;
import com.artisan.paymentservice.model.Type;
import com.artisan.paymentservice.service.PaymentService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@RestController
@RequestMapping("/api/v1/payment-service")
@RequiredArgsConstructor
public class PaymentController {
private final PaymentService paymentService;
@PostMapping
public Type submitPayment(@RequestBody String paymentInfo) {
return paymentService.submitPayment(paymentInfo);
}
}
server:
port: 9090
spring:
application:
name: payment-service
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: health
health:
ratelimiters:
enabled: true
resilience4j:
ratelimiter:
instances:
payment-service:
limit-for-period: 5
limit-refresh-period: 15s
timeout-duration: 5s
register-health-indicator: true
这段配置确保了payment-service服务的请求速率不会超过每15秒5次,同时如果请求超过10秒没有响应,则认为请求超时。此外,通过注册健康指标,可以对速率限制器的状态进行监控和管理。
确保两个服务启动成功
访问 http://localhost:9090/actuator/health
查看速率限制器详细信息。
http://localhost:9090/api/v1/ payment-service 请求3次 ,然后刷新执行器链接 http://localhost:9090/actuator/health
等待 15 秒(如果在 API 访问之前开始,时间可能会更短),然后刷新执行器链接 http://localhost:9090/actuator/health,我们将观察到允许的请求重置为 5。
API 访问 6 次 http://localhost:9090/api/v1/ payment-service。第 6 个请求将因超出限制而延迟 5 秒。等待期间,刷新 http://localhost:9090/actuator/health 以获取以下详细信息