Spring Webflux中使Mono缓存值失效的最佳方案是什么?
现有实现的可行性判定
你当前基于AtomicBoolean+cacheInvalidateIf的实现能正常跑通,不存在线程安全问题:AtomicBoolean本身保证了多线程环境下标记位的内存可见性,cacheInvalidateIf会在每次订阅缓存Mono时执行判定,标记位为true时确实会清空旧缓存、触发新的令牌请求。
但这个方案有几个明显的短板:
- 存在竞态窗口:从调用
setCacheInvalidated()把标记位设为true,到新令牌请求返回、标记位被重置为false的这段时间里,所有订阅令牌Mono的请求都会触发缓存失效,极端高并发场景下会短时间内发出多个重复的令牌请求,没有做请求合并。 - 状态耦合松散:缓存失效标记和实际缓存的Mono是两个独立维护的状态,靠逻辑隐式联动,后续如果要加令牌自动过期、异常回滚之类的逻辑,很容易出状态不一致的bug。
- 仅支持被动失效:没有利用OAuth2令牌自带的
expires_in过期时间做主动刷新,必须等业务请求碰到401才会触发更新,平白多了一次失败重试的开销。
更优实现方案
推荐直接用AtomicReference原子持有当前缓存的令牌Mono,失效时直接替换引用为新的令牌请求Mono,从根源上避免独立状态标记带来的问题,核心逻辑:
- 不用额外的布尔标记位,缓存本身就是原子持有的Mono实例,失效直接替换,不存在状态不一致。
- 新生成的令牌Mono自带
cache()效果,同一时间多个订阅者只会共享同一个令牌请求结果,不会重复调用令牌接口。 - 支持令牌过期前主动刷新,减少业务请求碰到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
相关产品推荐
相关产品推荐

