You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Webflux中使Mono缓存值失效的最佳方案是什么?

现有实现的可行性判定

你当前基于AtomicBoolean+cacheInvalidateIf的实现能正常跑通,不存在线程安全问题:AtomicBoolean本身保证了多线程环境下标记位的内存可见性,cacheInvalidateIf会在每次订阅缓存Mono时执行判定,标记位为true时确实会清空旧缓存、触发新的令牌请求。

但这个方案有几个明显的短板:

  • 存在竞态窗口:从调用setCacheInvalidated()把标记位设为true,到新令牌请求返回、标记位被重置为false的这段时间里,所有订阅令牌Mono的请求都会触发缓存失效,极端高并发场景下会短时间内发出多个重复的令牌请求,没有做请求合并。
  • 状态耦合松散:缓存失效标记和实际缓存的Mono是两个独立维护的状态,靠逻辑隐式联动,后续如果要加令牌自动过期、异常回滚之类的逻辑,很容易出状态不一致的bug。
  • 仅支持被动失效:没有利用OAuth2令牌自带的expires_in过期时间做主动刷新,必须等业务请求碰到401才会触发更新,平白多了一次失败重试的开销。
更优实现方案

推荐直接用AtomicReference原子持有当前缓存的令牌Mono,失效时直接替换引用为新的令牌请求Mono,从根源上避免独立状态标记带来的问题,核心逻辑:

  1. 不用额外的布尔标记位,缓存本身就是原子持有的Mono实例,失效直接替换,不存在状态不一致。
  2. 新生成的令牌Mono自带cache()效果,同一时间多个订阅者只会共享同一个令牌请求结果,不会重复调用令牌接口。
  3. 支持令牌过期前主动刷新,减少业务请求碰到401的概率。

参考实现代码:

@Component
public class AccessTokenService {

    private final WebClient accessTokenClient;
    private final AtomicReference<Mono<Token>> tokenCacheRef;
    // 令牌过期前30秒提前触发刷新,避免拿到刚好过期的令牌
    private static final Duration REFRESH_ADVANCE = Duration.ofSeconds(30);

    public AccessTokenService() {
        this.accessTokenClient = WebClient.builder()
                .baseUrl("example-token-uri")
                .build();
        // 初始化时直接加载第一个令牌请求Mono
        this.tokenCacheRef = new AtomicReference<>(buildTokenRequestMono());
    }

    private Mono<Token> buildTokenRequestMono() {
        return accessTokenClient.post()
                .uri(uriBuilder -> uriBuilder
                        // 填入客户端凭证等参数
                        .build())
                .retrieve()
                .bodyToMono(Token.class)
                .doOnNext(token -> {
                    // 令牌请求成功后,计算提前刷新的时间点,自动替换缓存为新请求
                    Duration delayBeforeRefresh = Duration.ofSeconds(token.getExpiresIn())
                            .minus(REFRESH_ADVANCE);
                    Mono.delay(delayBeforeRefresh)
                            .subscribe(ignore -> tokenCacheRef.set(buildTokenRequestMono()));
                })
                // 同一个Mono被多订阅时共享结果,不重复发请求
                .cache();
    }

    public Mono<Token> getAccessTokenMono() {
        return tokenCacheRef.get();
    }

    // 收到401时直接调用,替换缓存为新的令牌请求即可
    public void invalidateTokenCache() {
        tokenCacheRef.set(buildTokenRequestMono());
    }
}

这个方案相比原有实现有几个明确的好处:

  • 没有竞态重复请求:新的令牌Mono在替换进原子引用时就已经是可共享的缓存实例,哪怕并发触发失效,也只会有一个新的令牌请求在执行,后续订阅者直接复用结果。
  • 逻辑无隐式依赖:缓存状态和引用本身绑定,不需要靠外部变量联动判断是否要失效,后续加异常重试、降级逻辑都更简单。
  • 主动刷新减少无效请求:不用等业务请求报401才更新令牌,大部分场景下令牌会在过期前自动完成刷新,业务请求无感知。
  • 线程安全完全由AtomicReference保证,不需要额外维护独立标记位,不会出现标记位和缓存实际状态不一致的问题。

如果令牌接口稳定性一般,可以在buildTokenRequestMono()里加简单的重试逻辑,比如对5xx类服务端错误重试2次,避免临时网络波动导致所有拿令牌的请求直接失败。

内容的提问来源于stack exchange,提问作者nailer_boxer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 22:12:23