gateway网关转发请求到nacos不同namespace和不同group下服务实例源码改造
Posted 敲代码的小小酥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了gateway网关转发请求到nacos不同namespace和不同group下服务实例源码改造相关的知识,希望对你有一定的参考价值。
问题
gateway转发请求到微服务,报错误页面,错误信息如下所示:
There was an unexpected error (type=Service Unavailable, status=503).
Unable to find instance xxx
报错信息显示找不到应用实例。即gateway无法在nacos实例中获取到路由配置的对应实例。查阅网上资料,大多数写的是由于版本原因,需要手动配置ribbon中loadbalancer的jar包就可以解决问题。但是项目中SpringBoot版本为2.2.X版本,所以SpringCloud Alibaba使用的是2.2.0.RELEASE版本,此版本并不用手动配置ribbon的jar包。
补充:
SpringCloud Alibaba使用版本为2.2.0.RELEASE意思是,我们在定义nacos客户端启动类jar包,gateway启动类jar包时,对应的jar包版本都是2.2.0.RELEASE版本,只需要在父工程pom里定义SpringCloud Alibaba版本即可。其他SpringCloud Alibaba组件在配置启动类jar包时,就无需再定义版本了。
问题定位
查阅了许多资料,其中有一篇博客写道当负载转发的服务实例在nacos中与gateway服务实例在nacos里不是同一个namespace或者不是同一个group,也会报上述错误。项目中要转发的服务实例和gateway服务实例在nacos中属于同一个namespace,但是属于不同group。
于是将gateway与要转发的服务实例改成同一个group,果然可以正确转发了,不再报错了。所以,要想使用gateway进行请求转发,所有的微服务实例与gateway服务实例在nacos中必须是同一个namesapce和同一个group。
但是在本项目中,不同的微服务实例放在不同group中,是提前设计好的,有业务需要的。如果都归属于同一个group,那么会影响到其他业务实现。那么如何让gateway可以转发nacos中不同group甚至不同namespace中的服务实例呢?
解决思路
首先,需要想清楚的是,gateway只能转发在nacos中同namespace和同group中的服务实例,是谁规定的呢?我们知道,在gateway中,通过Ribbon进行负载均衡。那么,进行负载均衡的实例,是在哪里获取到的呢?在gateway中需要依赖nacos注册中心的jar包,如下:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
依赖这个jar包后,将gateway注册到nacos中,同时,这个jar包也定义了nacos中Open Api中方法的调用,可以通过调用Open Api获取到nacos中服务实例等信息。核心类是NacosServerList类。里面的核心方法是getServers()方法,源码如下所示:
private List<NacosServer> getServers()
try
//获取到gateway服务实例所属分组
String group = discoveryProperties.getGroup();
//查询分组下serviceId实例,这里的serviceId就是gateway要负载均衡的服务实例。
List<Instance> instances = discoveryProperties.namingServiceInstance()
.selectInstances(serviceId, group, true);
return instancesToServerList(instances);
catch (Exception e)
throw new IllegalStateException(
"Can not get service instances from nacos, serviceId=" + serviceId,
e);
由上述源码可知,nacos中默认是获取同namespace和同group下的服务实例。因为nacos中namespace和group就是用于服务隔离的,在不同的group和不同namespacae中,可以定义相同服务实例名称。所以nacos这么做无可厚非。只不过在特殊业务场景中,需要获取到不同group甚至不同namespace中服务实例。所以要对上述源码进行修改。
下面还需要分析一个问题,即我们自定义实现了NacosServerList,如何加入到源码执行的流程中呢?即在nacos执行流程中,要使用我们定义的NacosServerList类,而不是jar包中原有的NacosServerList类,应该从哪儿切入呢?在idea中,点击NacosServerList类,就会查找哪里调用这个类了,好在就一个地方初始化了这个类,那么这个类就是NacosServerList的切入,我们在这里定义我们自己的NacosServerList就行。切入类如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnRibbonNacos
public class NacosRibbonClientConfiguration
@Autowired
private PropertiesFactory propertiesFactory;
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config,
NacosDiscoveryProperties nacosDiscoveryProperties)
if (this.propertiesFactory.isSet(ServerList.class, config.getClientName()))
ServerList serverList = this.propertiesFactory.get(ServerList.class, config,
config.getClientName());
return serverList;
NacosServerList serverList = new NacosServerList(nacosDiscoveryProperties);
serverList.initWithNiwsConfig(config);
return serverList;
@Bean
@ConditionalOnMissingBean
public NacosServerIntrospector nacosServerIntrospector()
return new NacosServerIntrospector();
上述源码中,ribbonServerList()方法new出了NacosServerList对象,那么我们可以自己定义一个@Configuration类,覆盖这个类,来new出我们自己改造后的NacosServerList对象。这个想法是最好的解决思路,但是,经过研究发现,在NacosServerList类中,需要有IClientConfig类参与,才可以正常使用,而IClientConfig类,是框架自带的,研究了半天,完全没有搞懂这个类是干啥的。水平有限,只能放弃这种思路了。只能退而求其次,定义源码相同的包路径和类名和类名,来覆盖源码中类了。
解决方法
首先,定义NacosServerList类子类,来重写getServer()方法,获取不同分组下服务实例,代码如下:
public class AppNacosServerList extends NacosServerList
private NacosDiscoveryProperties discoveryProperties;
private String serviceId;
public AppNacosServerList(NacosDiscoveryProperties discoveryProperties)
super(discoveryProperties);
this.discoveryProperties=discoveryProperties;
@Override
public List<NacosServer> getInitialListOfServers()
return getServers();
@Override
public List<NacosServer> getUpdatedListOfServers()
return getServers();
/**
* nacos默认只寻找相同namespace和相同group里的服务实例。本项目需要获取同namespace中不同group
* 里的项目,所以对源码进行改造,能获取不同group下的服务实例。
* @return
*/
private List<NacosServer> getServers()
try
//TODO 本项目中只获取同namesapce中不同group下服务实例。如果要获取不同namespace,可以调用nacos中提供的获取所有namespace的接口,然后循环遍历即可。
//TODO 需要注意的是,在nacos提供的open api中,并没有获取所有分组group的方法,因此,我们只能自己去维护我们的项目里定义了哪些分组,然后手动获取这些分组下的服务实例。
//获取master分组服务实例
List<Instance> instances1 = discoveryProperties.namingServiceInstance()
.selectInstances(serviceId,"master", true);
//获取slave分组服务实例
List<Instance> instances2 = discoveryProperties.namingServiceInstance()
.selectInstances(serviceId,"slave", true);
List<Instance> instances=new ArrayList<>();
instances.addAll(instances1);
instances.addAll(instances2);
//master分组和slave分组实例都加入处理
return instancesToServerList(instances);
catch (Exception e)
throw new IllegalStateException(
"Can not get service instances from nacos, serviceId=" + serviceId,
e);
private List<NacosServer> instancesToServerList(List<Instance> instances)
List<NacosServer> result = new ArrayList<>();
if (CollectionUtils.isEmpty(instances))
return result;
for (Instance instance : instances)
result.add(new NacosServer(instance));
return result;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig)
this.serviceId = iClientConfig.getClientName();
然后要覆盖NacosRibbonClientConfiguration源码类,将我们自己定义的AppNacosServerList加入其中即可。需要注意的是需要在项目中定义与NacosRibbonClientConfiguration相同的jar包路径,才能覆盖,如下图所示:
当然,可以直接覆盖NacosServerList类也行,这样可以少覆盖一个类。当然大体思路是一致的。
以上是关于gateway网关转发请求到nacos不同namespace和不同group下服务实例源码改造的主要内容,如果未能解决你的问题,请参考以下文章
springcloud 通过gateway路由转发调用接口很慢问题
已解决Gateway路由转发-报503 Service Unavailable