• 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.cloudgroupId>
    			<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    		dependency>
    
    • 1
    • 2
    • 3
    • 4

    依赖这个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);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    由上述源码可知,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();
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    上述源码中,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();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    然后要覆盖NacosRibbonClientConfiguration源码类,将我们自己定义的AppNacosServerList加入其中即可。需要注意的是需要在项目中定义与NacosRibbonClientConfiguration相同的jar包路径,才能覆盖,如下图所示:
    在这里插入图片描述
    当然,可以直接覆盖NacosServerList类也行,这样可以少覆盖一个类。当然大体思路是一致的。

  • 相关阅读:
    selenium使用
    八、性能测试之磁盘分析
    自制编程语言基于c语言实验记录之五:虚拟机
    Hadoop+Spark大数据技术 第七次作业
    ScienceQA
    Kubernetes 深入理解kubernetes(一)
    CentOS修改root用户密码
    计算机毕业设计Java医疗机构药房管理系统软件开发(源码+系统+mysql数据库+lw文档)
    VRP基础及操作
    django自带的cache无法多进程共享
  • 原文地址:https://blog.csdn.net/qq1309664161/article/details/128054626