The gateway gateway forwards the request to nacos service instance source code transformation under different namespaces and different groups

question

The gateway forwards the request to the microservice and reports an error page. The error message is as follows:
insert image description here

There was an unexpected error (type=Service Unavailable, status=503).
Unable to find instance xxx

The error message shows that the application instance cannot be found. That is, the gateway cannot obtain the corresponding instance of the routing configuration in the nacos instance. Check online information, most of the writing is due to version reasons, you need to manually configure the jar package of loadbalancer in the ribbon to solve the problem. However, the SpringBoot version in the project is version 2.2.X, so SpringCloud Alibaba uses version 2.2.0.RELEASE. This version does not need to manually configure the ribbon jar package.

Supplement:
The version used by SpringCloud Alibaba is 2.2.0.RELEASE, which means that when we define the nacos client startup jar package and the gateway startup jar package, the corresponding jar package version is 2.2.0.RELEASE. Just define the SpringCloud Alibaba version in the parent project pom. For other SpringCloud Alibaba components, there is no need to define the version when configuring the startup jar package.

identify the problem

I checked a lot of information, and one blog wrote that when the service instance of load forwarding in nacos is not in the same namespace or the same group as the gateway service instance in nacos, the above error will also be reported. The service instance to be forwarded in the project and the gateway service instance belong to the same namespace in nacos, but belong to different groups.
So the gateway and the service instance to be forwarded are changed to the same group, and it can be forwarded correctly, and no error is reported anymore. Therefore, in order to use gateway for request forwarding, all microservice instances and gateway service instances must be in the same namesapce and same group in nacos.

However, in this project, different microservice instances are placed in different groups, which are designed in advance and have business needs. If they all belong to the same group, it will affect other business implementations. So how to make the gateway forward service instances in different groups or even different namespaces in nacos?

Solutions

First of all, you need to think clearly that the gateway can only forward the service instances in the same namespace and the same group in nacos. Who stipulated it? We know that in the gateway, load balancing is performed through the Ribbon. So, where is the instance for load balancing obtained? In the gateway, you need to rely on the jar package of the nacos registration center, as follows:

        <dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
		</dependency>

After relying on this jar package, register the gateway in nacos. At the same time, this jar package also defines the method call in Open Api in nacos, and you can get information such as service instances in nacos by calling Open Api. The core class is the NacosServerList class. The core method inside is the getServers() method, and the source code is as follows:

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);
		}
	}

It can be seen from the above source code that the default in nacos is to obtain service instances under the same namespace and group. Because namespace and group in nacos are used for service isolation, the same service instance name can be defined in different groups and different namespaces. So there is nothing wrong with nacos doing this. However, in special business scenarios, it is necessary to obtain service instances in different groups or even different namespaces. Therefore, the above source code needs to be modified.

Next, we need to analyze a problem, that is, we have customized NacosServerList, how to add it to the source code execution process? That is, in the nacos execution process, we should use the NacosServerList class we defined instead of the original NacosServerList class in the jar package. Where should we cut in? In the idea, click on the NacosServerList class, and you will find out where to call this class. Fortunately, this class is initialized in one place, so this class is the cut-in of NacosServerList. We can define our own NacosServerList here. . The entry class is as follows:

@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();
	}

}

In the above source code, the ribbonServerList() method new out the NacosServerList object, then we can define a @Configuration class by ourselves, overwrite this class, and new out our own transformed NacosServerList object. This idea is the best solution. However, after research, it is found that in the NacosServerList class, the IClientConfig class needs to be involved before it can be used normally, and the IClientConfig class comes with the framework. After researching for a long time, I didn’t understand this at all. What is the class for. The level is limited, so I can only give up this idea. The only solution is to define the same package path and class name and class name as the source code to cover the classes in the source code.

Solution

First, define the NacosServerList class subclass to rewrite the getServer() method to obtain service instances under different groups. The code is as follows:

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();
    }

}

Then to cover the NacosRibbonClientConfiguration source code class, add our own defined AppNacosServerList to it. It should be noted that the same jar package path as NacosRibbonClientConfiguration needs to be defined in the project to be covered, as shown in the figure below: Of course, it is
insert image description here
also possible to directly cover the NacosServerList class, so that one less class can be covered. Of course the general idea is the same.

Guess you like

Origin blog.csdn.net/qq1309664161/article/details/128054626