惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 软件心理学工程师

后量子密码学(post-quantum cryptography):为什么重要以及如何使用 密钥管理碎碎念 凤凰项目&DevOps实践指南 OAuth2和OpenID API横向越权问题的分析和解决 初识RAG 基于AWS的密钥管理系统 加密算法的使用场景 Linux透明大页(Transparent Huge Pages)对ES性能对影响 《java8 in action》读书笔记 ES索引的一些长度限制 初识redis Elasticsearch慢查询故障诊断 Elasticsearch搜索调优 elasticsearch基础知识杂记 elasticsearch.yml 常用参数说明 linux log rotate elasticsearch local debug环境搭建 CAP碎碎念 彻底删除kafka topic步骤
SpringBoot集成resilience4j进行熔断和一个响应式编程的问题
软件心理学工程师 · 2026-01-03 · via 博客园 - 软件心理学工程师

Resilience4j提供了熔断、限流、重试等一系列有助于服务稳定性的功能,Springboot项目可以非常简单的进行集成。

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.3.0</version>
</dependency>

Circuitbreaker

在你的服务访问其他某服务返回结果异常, 比如5xx, timeout,可以采用Circuitbreaker(断路器)阻止后续请求继续访问异常服务,同时设置failbackMethod进行降级处理。

断路器的实现模型是一个有限状态机,分为close,open, half-open三个状态。

image

Close: 表示断路器并没有阻断了业务逻辑(方法的执行)。
Open: 表示断路器已打开阻断了业务逻辑向下游的执行,可以指定执行failbackMethod进行降级处理。
Half_open: 表示断路器为半开半闭状态,会尝试放行个别请求向下游执行,如果执行成功则关闭断路器(Close),如果执行失败则打开断路器(Open)。

circuitbreaker的常用配置参数:

minimumNumberOfCalls: 2 #至少2次请求后进行判断
failureRateThreshold: 50f #一半请求失败触发开启断路器
slidingWindowSize: 6 #每次判断的滑动窗口大小是6
slidingWindowType: COUNT_BASED #计算方式count_based,另一个是TIME_BASED
automaticTransitionFromOpenToHalfOpenEnabled: true #自动从open转成half-open
waitDurationInOpenState: 5000 #断路器在open状态的持续时间5秒
maxWaitDurationInHalfOpenState: 0 #在半开状态的等待时间0秒

example:配置一个name="sayHelloWorld"的断路器。

resilience4j:
  circuitbreaker:
    instances:
      sayHelloWorld:
        minimumNumberOfCalls: 2
        failureRateThreshold: 50
        slidingWindowSize: 6
        slidingWindowType: COUNT_BASED
        automaticTransitionFromOpenToHalfOpenEnabled: true
        waitDurationInOpenState: 5s
        maxWaitDurationInHalfOpenState: 0

通过注解的方式在类或者方法上使该断路器

@CircuitBreaker(name = "sayHelloWorld")
public String sayHiNginx()  {
    return webClientBuilder.baseUrl("http://127.0.0.1:8083")
            .build()
            .get()
            .retrieve()
            .bodyToMono(String.class).block(); //采用WebClient的响应式模式发http请求,不用block()的情况下这种代码不work
}

这样当2次访问127.0.0.1:8083,至少1次以上不通会触发断路器。

{
    "status": 500,
    "message": "internal server error",
    "response": "CircuitBreaker 'sayHelloWorld' is OPEN and does not permit further calls"
}

响应式编程遇到的问题
第一版写完代码进行测试的时候发现限流器根本没起作用,以为是配置的不对,后来发现代码中有1处操作导致限流器没有生效。
sayHiNginx方法想借助spring框架响应式触发http请求的结果并返回给API的调用方,但是CircuitBreaker并没有统计到http请求失败。
PS:我的测试环境里127.0.0.1:8083套接字并不存在。

@CircuitBreaker(name = "sayHelloWorld")
public Mono<String> sayHiNginx()  {
    return webClientBuilder.baseUrl("http://127.0.0.1:8083")
            .build()
            .get()
            .retrieve()
            .bodyToMono(String.class); //注意没有block()
}
@GetMapping("/smart/resilience/circuitbreaker")
public Mono<String> testCircuitBreaker() {
return resilienceService.sayHiNginx();
}

原因是项目里之前写了一个HttpRequestLogPrinterFilter类进行HttpServlet request和response的拷贝。问题出在ContentCachingResponseWrapper 与响应式类型不兼容,当 Controller 返回 Mono<String> 时,响应体是异步写入的,而 ContentCachingResponseWrapper 在 finally 中调用 copyBodyToResponse() 时,响应体可能尚未写入,导致复制为空。

public class HttpRequestLogPrinterFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

        try {
            filterChain.doFilter(requestWrapper, responseWrapper);
        } finally {
            responseWrapper.copyBodyToResponse();
        }
    }
}

把这种异步的响应式触发改成了同步等待--加block()--http返回结果之后解决问题,但WebClient作为为响应式编程设计的httpclient,线程比较少,block()操作会占用一个珍贵的线程,这样做显然不是一个好方案。
好在resilience4j也支持了响应式编程,添加依赖:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-reactor</artifactId>
    <version>2.3.0</version>
</dependency>

初始化一个circuitBreaker bean

@Configuration
public class ResilienceConfig {
    @Bean(name="circuitBreaker")
    public CircuitBreaker initCircuitBreaker(CircuitBreakerRegistry cbRegistry) {
        return cbRegistry.circuitBreaker("sayHelloWorld");
    }
}


修改sayHiNginx方法,在末尾用transform方法把响应流转换成另一个响应流并加上CircuitBreakerOperator方法。CircuitBreakerOperator方法天然的支持reactor模式,主要实现一个UnaryOperator的apply方法应用熔断器。

@CircuitBreaker(name = "sayHelloWorld")
public Mono<String> sayHiNginx()  {
    return webClientBuilder.baseUrl("http://127.0.0.1:8083")
            .build()
            .get()
            .retrieve()
            .bodyToMono(String.class)
            .transform(CircuitBreakerOperator.of(circuitBreaker));
}

这样在control层直接返回Mono对象时,spring就能成功触发熔断器的执行了。

@GetMapping("/smart/resilience/circuitbreaker")
public Mono<String> testCircuitBreaker() {
    return resilienceService.sayHiNginx();
}