简单说下好了,这个问题其实在springCloud移除ribbon之后就出现的
之前我用的版本是SpringCloud Hoxton.SR8,具体这个版本里还有没有ribbon也没有去看了,反正这会在gateway里使用feign是没有任何问题的
也可能依赖中单独添加了ribbon没注意,这不因为springCloudGateway爆出来的漏洞嘛(某台生产服务器已经被黑过,
直接往redis里搞了一条gateway的路由信息,导致整个应用进不去,还好这个大哥只是试试水,别的并没有干什么),所以无奈必须升级
SpringCloud : 2021.0.1 对应gateway是3.1.1
alibabaNacos: 2021.1
springBoot: 2.6.6 (这个也是因为漏洞问题,虽然没说非war包会不会咋样,一并升了吧,之前用的2.5.1版本并不低,升级不算太困难,如果是2.3.0以下的小伙伴可就有的折腾了)
这里只说明升级之后SpringCloudGateway的使用openFeign(3.1.1)的问题
首先,可以完全确认,上面版本中无论是springCloud还是nacos,都已经没有集成任何默认的负载均衡组件,所以升级后第一件事就是添加一个负载均衡组件,否则启动不了或者是无法路由服务,ribbon已被抛弃,直接引入spring-cloud-loadbalancer即可(每个服务都要依赖)
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-loadbalancerartifactId>
<version>3.1.1version>
dependency>
然后就是openFeign在网关过滤器中的使用问题,我想应该大多数人用gateway处理鉴权应该都是实现的GlobalFilter这个接口来做的过滤器吧,问题就出在这里了,其他版本我不清楚,没有一一尝试了
但是3.1.1这个版本,无论你用webClient也好,还是把feign单独用异步Future包裹一道也罢你不管作何处理,在GlobalFilter的实现类中,要么一直503,要么每次启动第一次可以正常使用feign调取到其他服务数据,后面继续503,没有任何出路,不用费心研究添加Decoder的bean,改写服务接口,@Autowired的时候加@Lazy,甚至使用冷门的reactive-feign组件,以上现象并不会得到任何改变。虽然官方的git的issure里好像是提过一下webClient可以解决第一次可用,后续不可用的问题,但是亲身实践,并不可行,别说用webClient取代feign去调用服务,就算你服务也是web-flux的,都不行。
唯一解法只有一个,修改实现接口,改成WebFilter,这个接口和GlobalFilter基本一致,除了参数有一个不一样,还有ServerWebExchange中某些gateway封装的请求头不及GlobalFilter全面以外,其他暂时没有发现太大区别
// 这是GlobalFiter的filter方法
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
// 这是WebFilter的filter方法
Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);
// 基本没什么变化,内部代码也几乎不用更改,除了部分固定请求头,比如获取一些固定请求头在WebFilter里可能不存在
exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
// 这两个可能比较常用一点,这两个反正是获取不到的,没有挨个试过,暂且理解为ServerWebExchangeUtils里的几个常量请求头都没法获取吧
// 毕竟GlobalFiter和WebFilter是异步两条线程在执行的,这些请求头多数是默认GlobalFilter里定义的
这里改了之后,也还是不能直接@Autowired一个Feign来使用,倒是没出现其他文章里说的无法启动网关之类的问题,但是调用的时候会直接报下图异常
所以还差一步,把需要使用的feign,包裹一层Future。
@Component
public class FeignHolder {
@Autowired
@Lazy
private AuthFeign authFeign;
@Async
public Future<AuthenDTO> isLogin(String token){
AuthenDTO dto = FeignReturnDataGzip.Unzip(authFeign.isLogin(token), AuthenDTO.class);
return new AsyncResult<>(dto);
}
@Async
public Future<CurrentUser> getLoginUserInfo(String token){
CurrentUser currentUser = FeignReturnDataGzip.Unzip(authFeign.getLoginUserInfo(token), CurrentUser.class);
return new AsyncResult<>(currentUser);
}
}
然后可以在网关过滤器中直接注入FeignHolder
@Component
public class GatewayAuthorizationFilter implements WebFilter, Ordered {
@Autowired
private FeignHolder feignHolder;
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
...
Future<AuthenDTO> authFuture = feignHolder.isLogin(token);
AuthenDTO authenDTO;
try {
authenDTO = authFuture.get();
} catch (InterruptedException | ExecutionException e) {
authenDTO = new AuthenDTO();
log.error("[鉴权中心]调用鉴权中心Feign失败,失败原因:{},失败信息:{}", e.getClass().getSimpleName(), e.getMessage());
}
...
}
}
这样来处理之后,即可正常使用,网关建议都单独添加一个类似FeignHolder的配置类来统一处理openFeign调用,当然也可以直接使用
com.playtika.reactivefeign这个组件(国内外范围内参考资料都超级少,官方git说明也不是很全面,上手难度很高.),这个版本的网关中凡是使用openFeign的地方,必须要异步处理,不能同步调用feign接口,否则都会抛出异常
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking,
which is not supported in thread xxxx-xxxx-xxxx
另外不要相信使用SpringApplicationContext来获取bean/service就能不使用异步,只要你请求了其他服务,并且使用的是spring-cloud-loadbalancer作负载均衡,就必须异步调用,使用ribbon情况会不会有所不同就说不准了,有兴趣的自己试下吧