Spring WebFlux网关:直接返回Redis缓存响应时的异常与链路问题
问题分析与解决方案
你的问题存在两个核心原因:
- 使用同步Redis操作破坏了Reactive流的非阻塞特性,导致过滤器流程异常,后续仍会触发后端控制器请求
- 返回缓存响应后未标记请求已处理,后续
NettyRoutingFilter仍尝试修改响应头,引发UnsupportedOperationException
以下是针对性的修复步骤:
1. 替换为ReactiveRedisTemplate
Spring Cloud Gateway是Reactive环境,必须使用ReactiveRedisTemplate而非同步的RedisTemplate,避免线程阻塞和流程错乱。
首先注入ReactiveRedisTemplate:
private final ReactiveRedisTemplate<String, String> reactiveRedisTemplate; public YourFilter(ReactiveRedisTemplate<String, String> reactiveRedisTemplate) { this.reactiveRedisTemplate = reactiveRedisTemplate; }
2. 修正缓存Key查询逻辑
将同步的keys/multiGet替换为Reactive方法(生产环境建议用scan替代keys,避免阻塞Redis):
private Flux<String> generateCacheKey(String cacheKey) { return reactiveRedisTemplate.keys("*" + cacheKey + "*"); }
3. 重写过滤器核心逻辑
调整流程为全Reactive操作,并在返回缓存时标记请求已处理,跳过后续路由过滤器:
@Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); String api = apis.keySet().stream() .filter(e -> request.getURI().getPath().endsWith(e)) .findFirst() .orElse(null); if (api == null) { return chain.filter(exchange); } return ReactiveSecurityContextHolder.getContext() .flatMap(sc -> { Authentication authentication = sc.getAuthentication(); if (authentication == null || authentication.getPrincipal() == null) { return chain.filter(exchange); } String cacheKey = apis.get(api); return generateCacheKey(cacheKey) .next() // 取第一个匹配的缓存Key .flatMap(key -> reactiveRedisTemplate.opsForValue().get(key)) .flatMap(cachedResponse -> { if (cachedResponse != null) { // 标记请求已处理,跳过后续路由过滤器 exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ALREADY_ROUTED_ATTR, true); return writeCachedResponse(response, cachedResponse); } else { return chain.filter(exchange); } }) .switchIfEmpty(chain.filter(exchange)); }) .switchIfEmpty(chain.filter(exchange)) .doOnError(e -> log.error("Error during filter execution", e)); }
4. 完善响应写入方法
设置必要的响应头,确保响应正确提交:
private Mono<Void> writeCachedResponse(ServerHttpResponse response, String cachedResponse) { // 根据实际业务调整Content-Type response.getHeaders().setContentType(MediaType.APPLICATION_JSON); response.setStatusCode(HttpStatus.OK); DataBuffer buffer = response.bufferFactory().wrap(cachedResponse.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(buffer)) .doOnSuccess(v -> response.setComplete()); // 标记响应处理完成 }
5. 调整过滤器执行顺序
确保你的过滤器在NettyRoutingFilter之前执行(默认顺序为-1),通过@Order设置优先级:
@Component @Order(0) // 顺序大于-1,保证在路由过滤器之前拦截 public class YourFilter implements GatewayFilter, Ordered { // ... 其他代码 ... @Override public int getOrder() { return 0; } }
修复说明
- 全Reactive操作保证了网关的非阻塞特性,避免同步调用导致的流程异常
GATEWAY_ALREADY_ROUTED_ATTR标记会让Spring Cloud Gateway跳过后续路由逻辑,彻底终止过滤链路- 完善响应头和标记响应完成,避免后续过滤器尝试修改已提交的响应
内容的提问来源于stack exchange,提问作者elhetamy
相关产品推荐
相关产品推荐

