微服务是目前业界使用的最重要的实现方面。通过使用微服务架构,开发人员可以消除他们以前在单体应用程序中遇到的许多问题。展望未来,人们开始在微服务中搜索和采用各种模式。大多数时候,新模式的产生是为了解决另一个模式中出现的常见问题。就这样,随着时间的推移,大量的模式进入了实践。您可以从这里获得完整的摘要: https://microservices.io/patterns/microservices.html
一、断路器介绍
1.1 什么是断路器模式?
您可能已经听说过我们在电子产品中发现的断路器。它的主要目的是什么?简单地说,在意想不到的情况下切断电流。与此相同,这种微服务模式也因其具有相同的性质而得名。
在这种情况下,我们可以使用这种断路器模式来解决问题。它为我们提供了一种在不打扰最终用户或应用程序资源的情况下处理这种情况的方法。
1.2 模式如何运作?💥
以下是一些常见概念讲解帮助大家来理解断路器。😎
1.3 模式状态的生命周期💥
- CLOSED
- OPEN
- HALF OPEN
让我们简要了解一下状态……
CLOSED State
OPEN State
一旦远程 API 调用失败百分比超过给定阈值,断路器就会将其状态更改为 OPEN 状态。调用微服务会立即失败,返回异常。也就是说,流量中断了。
HALF OPEN State
1.4 什么 Resilience4j?
Resilience4j 是一个轻量级、易于使用的容错库,其灵感来自于 Netflix Hystrix。它提供各种功能如下:
-
断路器 — 容错
- 速率限制器 — 阻止太多请求
- 时间限制器 — 调用远程操作时的限制时间
- 重试机制 — 失败操作自动重试
- 隔板 — 限制并发请求数
- 缓存 — 存储远程操作的结果
二、代码讲解
2.1 创建 2️⃣ 个微服务
我将使用名为 loan-service(贷款) 和 rate-service(利率) 的两个服务来实现一个简单的服务间通信场景。
技术细节:
脚本:
贷款服务可以获取保存在数据库中的贷款,每个贷款对象都有贷款类型。根据贷款类型,有单独的利率百分比。因此,利率服务的名称包含那些利率对象的详细信息。
- 我将从贷款服务调用利率服务,请求给定贷款类型的利率。
- 然后我必须根据贷款类型计算贷款的总利息价值。
- 然后我将使用从利率服务获得的利率更新所有贷款对象的利息金额。
使用 POM 文件下方提供的依赖项创建一个新的 Spring Boot 项目。我将其命名为费率服务。https://github.com/SalithaUCSC/spring-boot-circuit-breaker/blob/main/rate-service/pom.xml
Controller:
@RestController
@RequestMapping("api"
public class RateController {
@Autowired
private RateService rateService;
@GetMapping(path = "/rates/{type}"
public ResponseEntity<Rate> getRateByType(@PathVariable("type" String type {
return ResponseEntity.ok(.body(rateService.getRateByType(type;
}
}
Service:
@Service
public class RateService {
@Autowired
private RateRepository repository;
public Rate getRateByType(String type {
return repository.findByType(type.orElseThrow(( -> new RuntimeException("Rate Not Found: " + type;
}
}
Repository:
@Repository
public interface RateRepository extends JpaRepository<Rate, Integer> {
Optional<Rate> findByType(String type;
}
Entity:
@Builder
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "rates"
public class Rate {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY
Integer id;
String type;
@Column(name = "rate"
Double rateValue;
}
Configuration:
server:
port: 9000
spring:
application:
name: rate-service
datasource:
url: jdbc:h2:mem:cb-rate-db
username: root
password: 123
driverClassName: org.h2.Driver
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
h2:
console:
enabled: true
启动类:主类将在服务即将到来时添加两种类型的贷款利率。
@SpringBootApplication
public class RateServiceApplication {
@Autowired
private RateRepository rateRepository;
public static void main(String[] args {
SpringApplication.run(RateServiceApplication.class, args;
}
@PostConstruct
public void setupData( {
rateRepository.saveAll(Arrays.asList(
Rate.builder(.id(1.typ并检查我们需要的 APIe("PERSONAL".rateValue(10.0.build(,
Rate.builder(.id(2.type("HOUSING".rateValue(8.0.build(
;
}
}
现在我们可以启动 rate-service 并检查我们需要的 API。转到 http://localhost:9000/api/rates/PERSONAL 并查看结果。你应该得到这个回应。
{"id": 1,"type": "PERSONAL","rateValue": 10}
2.2 贷款服务添加断路器
现在我需要实施贷款服务。 loan-service 内部需要断路器,因为它正在调用 rate-service。因此,需要 Resilience4j 库。我需要检查断路器的状态。为此,我需要在贷款服务中启用 Actuator。
https://github.com/SalithaUCSC/spring-boot-circuit-breaker/blob/main/loan-service/pom.xml
Controller:
@RestController
@RequestMapping("api"
public class LoanController {
@Autowired
private LoanService loanService;
@GetMapping(path = "/loans"
public ResponseEntity<List<Loan>> getLoansByType(@RequestParam("type" String type {
return ResponseEntity.ok(.body(loanService.getAllLoansByType(type.toUpperCase(;
}
}
Repository:
public interface LoanRepository extends JpaRepository<Loan, Integer> {
List<Loan> findByType(String type;
}
DTO:这用于转换来自费率服务 API 调用的响应。因为它是 Rate 的类型。同rate-service Rate实体类(只是省略了ORM相关的东西)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class InterestRate {
Integer id;
String type;
Double rateValue;
}
Entity:
@Builder
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "loans"
public class Loan {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY
Integer id;
String type;
Double amount;
Double interest;
}
启动类:
- 主类将在服务即将到来时添加 3 个贷款对象。利息金额已设置为零,因为我们后来通过远程调用 rate-service 对其进行了更新。
- 我们需要一个 RestTemplate 类的 Bean 来执行远程 API 调用。如果您不知道,请从此处阅读: https://salithachathuranga94.medium.com/rest-template-with-spring-boot-e2001a8219e6
@SpringBootApplication
public class LoanServiceApplication {
@Autowired
private LoanRepository loanRepository;
public static void main(String[] args {
SpringApplication.run(LoanServiceApplication.class, args;
}
@Bean
public RestTemplate restTemplate( {
return new RestTemplate(;
}
@PostConstruct
public void setupData( {
loanRepository.saveAll(Arrays.asList(
Loan.builder(.id(1.type("PERSONAL".amount(200000.0.interest(0.0.build(,
Loan.builder(.id(2.type("HOUSING".amount(6000000.0.interest(0.0.build(,
Loan.builder(.id(3.type("PERSONAL".amount(100000.0.interest(0.0.build(
;
}
}
Service:
这是我们执行远程调用的最重要的地方。我们需要在利率服务中使用 RestTemplate: http://localhost:9000/api/rates/{type} 调用此 API 以获取贷款类型的百分比。然后我们计算利息金额为贷款金额*(利率/100)并更新贷款利息金额。
@Service
public class LoanService {
@Autowired
private LoanRepository loanRepository;
@Autowired
private RestTemplate restTemplate;
private static final String SERVICE_NAME = "loan-service";
private static final String RATE_SERVICE_URL = "http://localhost:9000/api/rates/";
public List<Loan> getAllLoansByType(String type {
HttpHeaders headers = new HttpHeaders(;
headers.setContentType(MediaType.APPLICATION_JSON;
HttpEntity<InterestRate> entity = new HttpEntity<>(null, headers;
ResponseEntity<InterestRate> response = restTemplate.exchange(
(RATE_SERVICE_URL + type,
HttpMethod.GET, entity,
InterestRate.class
;
InterestRate rate = response.getBody(;
List<Loan> loanList = new ArrayList<>(;
if (rate != null {
loanList = loanRepository.findByType(type;
for (Loan loan : loanList {
loan.setInterest(loan.getAmount( * (rate.getRateValue( / 100;
}
}
return loanList;
}
}
Configuration:
server:
port: 8000
spring:
application:
name: loan-service
datasource:
url: jdbc:h2:mem:cb-loan-db
username: root
password: 123
driverClassName: org.h2.Driver
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
h2:
console:
enabled: true
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: health
health:
circuitbreakers:
enabled: true
我们需要在执行器暴漏断路器详细信息(暴漏端点)。稍后我们将在此处添加断路器配置。目前,不需要。
http://localhost:8000/api/loans?type=personal 并查看结果。你应该得到这个回应。
[
{"id": 1,"type": "PERSONAL","amount": 200000,"interest": 20000},
{"id": 3,"type": "PERSONAL","amount": 100000,"interest": 10000}
]
2.3 添加回退方法💥
现在我们必须用注释来丰富我们的 Loan 服务方法。它被称为 “@CircuitBreaker”。在这里,SERVICE_NAME 被视为“贷款服务”。然后我们必须提供一个 fallbackMethod。这样做的目的是当下游服务(速率服务)无法响应时默认调用它。
@CircuitBreaker(name = SERVICE_NAME, fallbackMethod = "getDefaultLoans"
public List<Loan> getAllLoansByType(String type {
...............
}
我已经设置了当费率服务没有响应时默认返回空列表的方法。
public List<Loan> getDefaultLoans(Exception e {
return new ArrayList<>(;
}
2.4 添加断路器配置💥
让我们添加 Resilience4j 断路器配置。将此添加到贷款服务中的 application.yml。
resilience4j:
circuitbreaker:
instances:
loan-service:
registerHealthIndicator: true
failureRateThreshold: 50
minimumNumberOfCalls: 5
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
permittedNumberOfCallsInHalfOpenState: 3
slidingWindowSize: 10
slidingWindowType: COUNT_BASED
- failureRateThreshold — 失败阈值的预期百分比。我将其设置为 50%。这意味着,当失败的远程调用总数 % 等于或大于 50% 时,断路器将处于活动状态以停止进一步的请求。
- minimumNumberOfCalls — 决定启用断路器的失败百分比的 API 调用总数的最小值。我将其设置为 5。假设前 5 个 API 调用中有 3 个 API 调用失败。这意味着 failureRateThreshold = (3/5 * 100 = 60%。
- automaticTransitionFromOpenToHalfOpenEnabled — 我已将其设置为 true。当转换的正确时间到来时,它会自动将 OPEN 状态转换为 HALF OPEN 状态
- waitDurationInOpenState — 从 OPEN 状态进入 HALF OPEN 状态之前的超时时间。 5 秒后,断路器就将更改状态。
- permittedNumberOfCallsInHalfOpenState — 在 HALF OPEN 状态下应发送的 LIMITED API 调用数。我将其设置为 3。因此,在 3 次 API 调用之后,如果失败,则断路器将再次进入 OPEN 状态。否则断路器将关闭,因为 rate-service 已启动。
- slidingWindowType:我在这里设置了类型以根据请求计数保持断路器行为。
启动这两个服务。现在转到贷款服务,输入监控端点 URL:http://localhost:8000/actuator/health。断路器的详细信息会在响应中突出显示。
{
"status": "UP",
"components": {
"circuitBreakers": {
"status": "UP",
"details": {
"loan-service": {
"status": "UP",
"details": {
"failureRate": "-1.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "-1.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 1,
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 0,
"notPermittedCalls": 0,
"state": "CLOSED"
}
}
}
},
......................................
}
}
- bufferedCalls — 从贷款服务到利率服务的总 API 调用
- failedCalls - 从贷款服务到利率服务的失败 API 调用总数
- failureRate — (failedCalls/bufferedCalls * 100%
2.5 测试断路器💥
我们必须遵循一些有序的步骤才能准确地看到变化。在每一步中,我们都必须查看监控端点,并通过更改其状态查看断路器的行为方式。开始!💪
- 启动两个微服务。贷款服务在 8000 上运行,利率服务在 9000 上运行。
- 现在点击此 API 2 次:http://localhost:8000/api/loans?type=personal。然后去检查监控端点 http://localhost:8000/actuator/health。现在 bufferedCalls 计数已按预期更新为 2。由于费率服务已启动,断路器仍处于关闭状态。
{
"loan-service": {
"status": "UP",
"details": {
"failureRate": "-1.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "-1.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 2,
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 0,
"notPermittedCalls": 0,
"state": "CLOSED"
}
}
}
- 现在停止费率服务!然后点击贷款服务 API URL 3次:http://localhost:8000/api/loans?type=personal。你应该得到一个我们设置为后备的空数组!这将使 bufferedCalls 计数为 5(前 2 个和这个 3)。同时,failedCalls 计数更新为 3。现在 failureRate 变为 60%( (3/5 * 100% 。然后它已经超过了我们的阈值:50%。😀然后断路器将其状态更改为 OPEN!😍
{
"loan-service": {
"status": "CIRCUIT_OPEN",
"details": {
"failureRate": "60.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "0.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 5,
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 3,
"notPermittedCalls": 0,
"state": "OPEN"
}
}
}
- 然后等待 5 秒钟。它应该在 5 秒后转换为半开状态,根据我们的配置,我们将 waitDurationInOpenState 设置为 5s 这是超时时间。在这段时间之后,请求计数也将被重置。
{
"loan-service": {
"status": "CIRCUIT_HALF_OPEN",
"details": {
"failureRate": "-1.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "-1.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 0,
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 0,
"notPermittedCalls": 0,
"state": "HALF_OPEN"
}
}
}
- 在 HALF OPEN 状态下,有限数量的请求将被允许通过。在我们的例子中,它在配置中为 3,相关值已设置为 permittedNumberOfCallsInHalfOpenState: 3。
由于 rate-service 仍然关闭,只需再次尝试 loan-service API 3次!http://localhost:8000/api/loans?type=personal 发生了什么事? 3次调用全部失败!那么 failureRate 就是 100%。我们的断路器将再次打开。
{
"loan-service": {
"status": "CIRCUIT_OPEN",
"details": {
"failureRate": "100.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "0.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 3,
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 3,
"notPermittedCalls": 0,
"state": "OPEN"
}
}
}
- 超时 5 秒后,它将再次变为半开状态!使用执行器再次检查。你应该得到一个用于贷款服务 API 调用的空数组...
- 现在开始收费服务!😎然后再次尝试此 API 3次:http://localhost:8000/api/loans?type=personal 发现断路器已关闭!😍因为成功执行了预期的有限 API 调用计数。
{
"loan-service": {
"status": "UP",
"details": {
"failureRate": "-1.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "-1.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 0,
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 0,
"notPermittedCalls": 0,
"state": "CLOSED"
}
}
}
自此,我们完成了断路器的测试工作。是不是很神奇?😎它正在按预期工作!
https://github.com/SalithaUCSC/spring-boot-circuit-breaker
最后感谢大家阅读,希望这篇文章能为你提供价值。公众号【waynblog】分享技术干货、开源项目、实战经验、高效开发工具等,您的关注将是我的更新动力😘。