基于Cache-Control头实现Spring WebClient响应缓存的最优方案咨询
我正在尝试实现基于Cache-Control(及相关)请求头的Spring WebClient响应缓存功能,清楚缓存与反应式编程并非天然适配,计划使用内存缓存。
我梳理了四种可行方案:
方案1:基于Project Reactor的Mono缓存方法
Project Reactor为Mono提供了多种cache方法:
return webClient.get() .uri("/hello") .retrieve() .bodyToMono(HelloWorld.class) .cache(Duration.of(3600, ChronoUnit.SECONDS)) .block();
但这种方式是在ResponseSpec转换为实体Mono后缓存,会丢失所有请求头信息。可以改用toEntity来保留响应头:
return webClient.get() .uri("/hello") .retrieve() .toEntity(HelloWorld.class) .cache(Duration.of(3600, ChronoUnit.SECONDS)) .block().getBody();
但我不确定能否在cache方法中直接访问ResponseEntity,比如下面的写法是否可行:
return webClient.get() .uri("/hello") .retrieve() .toEntity(HelloWorld.class) .flatMap(response -> Mono.just(response).cache(getTtlFromHeader(response.getHeaders())) ) .block().getBody();
根据cache方法的JavaDoc:
- ttlForValue:源产生有效值时调用的TTL生成函数,用于缓存响应
- ttlForError:源产生错误时调用的TTL生成函数,这类情况不应缓存
- ttlForEmpty:源为空时调用的TTL生成提供者,此时无内容可缓存
不过基于Mono的这类方案存在一个问题:无法重新评估缓存的响应是否仍然有效。
方案2:通过过滤器实现缓存
通过过滤器实现缓存功能,缓存对象是响应的Mono而非响应本身。我还没尝试过这种方式,但它显然不是最优解——Mono在首次调用后就已经解析完成,从缓存获取时仍需重复解析,而且缓存数据的有效性重新评估逻辑不清晰,实现起来会相当复杂。
方案3:WebClient调用外部处理缓存
由于我的所有调用都使用阻塞订阅者,外围逻辑是非反应式的,因此可以借助Spring缓存实现,但需要返回ResponseEntity而非仅响应对象。
我不太喜欢这个方案,因为ResponseEntity会脱离WebClient调用流程,我希望把所有和调用相关的逻辑都封装在WebClient内部。
方案4:基于Apache Async客户端的缓存实现
我目前使用Apache Async客户端作为底层连接器,可以将原有的客户端构建代码:
private CloseableHttpAsyncClient httpClient(int maxTotalConnections, int maxConnectionsPerRoute) { return HttpAsyncClients.custom() .setConnectionManager(connectionManager(maxTotalConnections, maxConnectionsPerRoute)) .disableCookieManagement() .evictExpiredConnections() .disableRedirectHandling() .build(); }
替换为支持缓存的版本:
private CloseableHttpAsyncClient cachableHttpClient(int maxTotalConnections, int maxConnectionsPerRoute) { CacheConfig cacheConfig = CacheConfig.DEFAULT; return CachingHttpAsyncClients.custom() .setCacheConfig(cacheConfig) .setConnectionManager(connectionManager(maxTotalConnections, maxConnectionsPerRoute)) .disableCookieManagement() .evictExpiredConnections() .disableRedirectHandling() .build(); }
可能还需要进一步配置CacheConfig。这个方案看起来是最佳选择,但通过自动化测试验证其有效性的难度较大。
现在我有两个问题:
- 是否还有我忽略的其他可行方案?
- 哪种是最优的实现方式?
内容的提问来源于stack exchange,提问作者hotzst

