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类也行,这样可以少覆盖一个类。当然大体思路是一致的。

猜你喜欢

转载自blog.csdn.net/qq1309664161/article/details/128054626