背景
设计小公司内部微服务框架,提供一套公共的基础服务给所有业务系统使用(如网关服务、认证服务、系统服务、问价服务、流程服务等)。如果全注册在同一Nacos的单一Namespace或group下会比较混乱;每个系统单独部署一套的话又比较麻烦,权衡决定采用同一个Nacos,公共服务注册在public namespace下,其他系统每个系统单分一个命名空间出来,但是可以发现public下的公共服务并进行feign调用。
网上搜这方面资料乱七八糟的,要不就是CSDN恬不知耻收费,故做一个整合,方便自己以及网友浏览查阅。
环境
SpringBoot 3.5.0
JDK 17
Nacos 2.2.0
跨Group服务发现
正常来说,Nacos的服务只有在本Namespace + 本Group下才能互相发现的,就是有两层隔离,我们先了解下如何突破Group的限制,有些场景你可能需要。
直接上代码(添加在SpringBoot服务中或项目common模块):
Java
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import org.springframework.cloud.client.ServiceInstance;
import java.util.*;
import java.util.stream.Collectors;
/**
* 重写nacos服务发现逻辑,使支持跨group发现服务
*/
public class CustomNacosServiceDiscovery extends NacosServiceDiscovery {
private final NacosDiscoveryProperties discoveryProperties;
private final NacosServiceManager nacosServiceManager;
private final List<String> groups;
public CustomNacosServiceDiscovery(NacosDiscoveryProperties discoveryProperties, NacosServiceManager nacosServiceManager) {
super(discoveryProperties, nacosServiceManager);
this.discoveryProperties = discoveryProperties;
this.nacosServiceManager = nacosServiceManager;
// 服务发现顺序:本组服务->公共分组(DEFAULT_GROUP)服务->其他组服务
this.groups = new ArrayList<>(Arrays.asList(discoveryProperties.getGroup(), "DEFAULT_GROUP"))
.stream().distinct().collect(Collectors.toList());
}
/**
* Return the names of all services.
*
* @return list of service names
* @throws NacosException nacosException
*/
@Override
public List<String> getServices() throws NacosException {
Set<String> services = new LinkedHashSet<>();
NamingService nameService = this.discoveryProperties.namingServiceInstance();
for (String group : groups) {
services.addAll(nameService.getServicesOfServer(1, Integer.MAX_VALUE, group).getData());
}
return new ArrayList<>(services);
}
/**
* 重写服务发现逻辑
*/
@Override
public List<ServiceInstance> getInstances(String serviceId) throws NacosException {
NamingService nameService = this.discoveryProperties.namingServiceInstance();
// 优先发现同组服务
List<Instance> instances = new ArrayList<>();
// 在DEFAULT_GROUP中发现公共服务(同组服务已经注册的,使用同组服务而非公共服务)
for (String group : groups) { // 按照GROUPS内的顺序依次发现各组服务,优先发现靠前组的服务,如果某服务在之前分组中出现过,不会重复注册
instances.addAll(
nameService.selectInstances(serviceId, group, true)
.stream()
.filter(e -> !instances.stream().map(Instance::getServiceName).collect(Collectors.toList())
.contains(NamingUtils.getServiceName(e.getServiceName())))
.collect(Collectors.toList()));
}
return hostToServiceInstanceList(instances, serviceId);
}
}
核心是继承NacosServiceDiscovery
这个类,重写getServices
和getInstances(String serviceId)
这俩个方法,前者用于返回所有可用的服务的名称,后者则根据服务名称来获得详细的服务实例信息。
上面的逻辑我们使用DEFAULT_GROUP
作为可被公共发现的Group,并写死到代码中,实际操作你可以根据自己需求来更改逻辑或是改为yml配置,可以更灵活一些。
添加完这个类后,我们再写一个配置类来将该Bean启用,覆盖默认逻辑:
Java
import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration;
import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Nacos自动配置类,加载自定义容器
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({NacosDiscoveryAutoConfiguration.class})
public class CustomNacosDiscoveryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public NacosServiceDiscovery nacosServiceDiscovery(NacosDiscoveryProperties nacosDiscoveryProperties, NacosServiceManager nacosServiceManager) {
return new CustomNacosServiceDiscovery(nacosDiscoveryProperties, nacosServiceManager);
}
}
跨Namespace服务发现
这里在上面的基础上,进一步修改自定义的NacosServiceDiscovery
类的逻辑:
Java
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import org.springframework.cloud.client.ServiceInstance;
import java.util.*;
import java.util.stream.Collectors;
/**
* 重写nacos服务发现逻辑,使支持跨Namespace发现服务
*/
public class CustomNacosServiceDiscovery extends NacosServiceDiscovery {
private final NacosDiscoveryProperties discoveryProperties;
private final NacosServiceManager nacosServiceManager;
private NamingService publicNamingService;
private final List<String> groups;
public CustomNacosServiceDiscovery(NacosDiscoveryProperties discoveryProperties, NacosServiceManager nacosServiceManager) {
super(discoveryProperties, nacosServiceManager);
this.discoveryProperties = discoveryProperties;
this.nacosServiceManager = nacosServiceManager;
initPublicNamingService();
// 服务发现顺序:本组服务->公共分组(DEFAULT_GROUP)服务->其他组服务
this.groups = new ArrayList<>(Arrays.asList(discoveryProperties.getGroup(), "DEFAULT_GROUP"))
.stream().distinct().collect(Collectors.toList());
}
/**
* 创建public命名空间的NamingService实例
*/
private void initPublicNamingService() {
try {
Properties props = new Properties();
// 复用默认配置中的服务器地址、鉴权等信息
props.setProperty("serverAddr", discoveryProperties.getServerAddr());
props.setProperty("username", discoveryProperties.getUsername());
props.setProperty("password", discoveryProperties.getPassword());
// 命名空间固定设置为 "public"
props.setProperty("namespace", "public");
this.publicNamingService = NacosFactory.createNamingService(props);
} catch (NacosException e) {
throw new RuntimeException("Failed to create NamingService for public namespace", e);
}
}
/**
* Return the names of all services.
*
* @return list of service names
* @throws NacosException nacosException
*/
@Override
public List<String> getServices() throws NacosException {
Set<String> services = new LinkedHashSet<>();
// step1:发现本命名空间内所有GROUP的服务
NamingService nameService = this.discoveryProperties.namingServiceInstance();
for (String group : groups) {
services.addAll(nameService.getServicesOfServer(1, Integer.MAX_VALUE, group).getData());
}
// step2:发现public命名空间下的DEFAULT_GROUP的服务
services.addAll(publicNamingService.getServicesOfServer(1, Integer.MAX_VALUE).getData());
return new ArrayList<>(services);
}
/**
* 重写服务发现逻辑
*/
@Override
public List<ServiceInstance> getInstances(String serviceId) throws NacosException {
NamingService nameService = this.discoveryProperties.namingServiceInstance();
// step1:发现本命名空间内所有GROUP的服务
List<Instance> instances = new ArrayList<>();
for (String group : groups) { // 按照GROUPS内的顺序依次发现各组服务,优先发现靠前组的服务,如果某服务在之前分组中出现过,不会重复注册
instances.addAll(
nameService.selectInstances(serviceId, group, true)
.stream()
.filter(e -> !instances.stream().map(Instance::getServiceName).collect(Collectors.toList())
.contains(NamingUtils.getServiceName(e.getServiceName())))
.collect(Collectors.toList()));
}
// step2:发现public命名空间下的DEFAULT_GROUP的服务
instances.addAll(publicNamingService.selectInstances(serviceId, true));
return hostToServiceInstanceList(instances, serviceId);
}
}
与之前不同的是,多出来一个NamingService
对象,本来我们使用:
Java
this.discoveryProperties.namingServiceInstance()
这个方法拿到的是对应yml中配置的那个命名空间,假如我们现在是一个业务系统,那此时拿到的就是私有namespace的NamingService对象,现在需要获取public
这个命名空间下的服务,所以需要一个能代表public的NamingService,这里我查阅源码后参考其方法写了一个initPublicNamingService
方法来初始化这个对象。
其他有的博客里可能有提到再新建一个discoveryProperties
继承原有的,然后通过这个新的discoveryProperties来新建出NamingService, 这个应该是最标准的流程,我们这里取巧简化一下。
总结
代码就这么多,也不是很复杂,但是查起来是真费劲,这也是中文互联网现在的一个现状吧,希望能抛砖引玉,方便一下同行。转载望标明出处。