Spring Cloud Gateway通过Feign调用认证服务出现block阻塞错误如何解决
问题根因
Spring Cloud Gateway是基于WebFlux的响应式编程框架,底层依赖Reactor异步非阻塞模型,其核心IO线程(即报错中的reactor-http-epoll-*线程)严格禁止执行阻塞操作。你当前使用的是默认同步阻塞的OpenFeign客户端,调用认证服务时会隐式执行阻塞等待逻辑,触发了Reactor的线程保护规则,所以抛出该异常。
架构本身的设计思路没有问题,问题出在响应式环境下误用了阻塞组件。
解决方案
方案1:改用响应式OpenFeign(优先推荐)
Spring Cloud 3.x及以上版本的OpenFeign已经原生支持响应式返回值,直接修改代码适配响应式规范即可:
步骤1:修改Feign接口返回值
把原来返回AuthenticationResponse的方法改为返回Mono<AuthenticationResponse>:
// 原Feign接口写法 // AuthenticationResponse getValidity(String token); // 修改为响应式写法 Mono<AuthenticationResponse> getValidity(String token);
步骤2:修改业务层返回值
调整AuthenticationService的方法返回值:
@Autowired private AuthenticationFeign auth; public Mono<AuthenticationResponse> isTokenValid(String token) { return auth.getValidity(token); }
步骤3:重写过滤器逻辑为响应式链式调用
调整过滤器的filter方法,避免同步阻塞获取结果:
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); if (routerValidator.isSecured.test(request)) { log.info("Accessing the restricted path"); if (this.isAuthMissing(request)) return this.onError(exchange, "Authorization header is missing in request", HttpStatus.UNAUTHORIZED); final String token = this.getAuthHeader(request); log.info("before authservice call"); // 响应式调用,无阻塞 return authService.isTokenValid(token) .flatMap(user -> { log.info("after authservice call"); if (!user.isValid()) { return this.onError(exchange, "Authorization header is invalid", HttpStatus.UNAUTHORIZED); } log.info("before calling populatedRequest"); this.populateRequestWithHeaders(exchange, user); return chain.filter(exchange); }); } return chain.filter(exchange); }
方案2:调度器包裹阻塞调用(临时快速修复)
如果不想改动现有Feign的同步逻辑,可以把阻塞调用放到Reactor专门处理阻塞任务的弹性线程池中执行,避免阻塞核心IO线程:
仅需修改过滤器中调用认证服务的逻辑即可,其余代码无需调整:
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); if (routerValidator.isSecured.test(request)) { log.info("Accessing the restricted path"); if (this.isAuthMissing(request)) return this.onError(exchange, "Authorization header is missing in request", HttpStatus.UNAUTHORIZED); final String token = this.getAuthHeader(request); log.info("before authservice call"); // 用弹性线程池执行阻塞调用 return Mono.fromCallable(() -> authService.isTokenValid(token)) .subscribeOn(Schedulers.boundedElastic()) .flatMap(user -> { log.info("after authservice call"); if (!user.isValid()) { return this.onError(exchange, "Authorization header is invalid", HttpStatus.UNAUTHORIZED); } log.info("before calling populatedRequest"); this.populateRequestWithHeaders(exchange, user); return chain.filter(exchange); }); } return chain.filter(exchange); }
额外注意点
你原有代码中的populateRequestWithHeaders方法存在逻辑缺陷,修改后的请求没有赋值回exchange,会导致新增的header不生效,调整为如下写法即可:
private void populateRequestWithHeaders(ServerWebExchange exchange, AuthenticationResponse authRes) { log.info("About to mutate the request->{}",exchange); ServerHttpRequest newRequest = exchange.getRequest().mutate() .header("id",Integer.toString(authRes.getUserId())) .build(); // 将修改后的请求更新到exchange exchange.mutate().request(newRequest).build(); }
内容的提问来源于stack exchange,提问作者Gulshan Kumar
相关产品推荐
相关产品推荐

