前言
本文将分析基于Spring Cloud Alibaba Nacos作为注册中心时,服务调用的实现过程。在Spring Cloud架构体系下,对于不同注册中心,其服务调用的核心流程都是一样的,唯一的区分是在获取服务列表时存在差异,而对于服务列表的获取,Spring Cloud抽象出了一个规范接口,不同注册中心只需要实现该规范接口即可。因此,在看本文之前,读者可以先看下之前写的《Spring Cloud组件之深入分析Ribbon服务调用源码实现》这篇文章,本文会基于该文章基础上进行分析
核心类
回顾下《Spring Cloud组件之深入分析Ribbon服务调用源码实现》这篇文章,当通过restTemplate发起服务调用时,需要为当前调用的服务创建对应的上下文对象,并且注入两个核心配置类,而Nacos与Eureka区别在于,Nacos提供的配置类为NacosRibbonClientConfiguration,而另外一个配置类则是Ribbon提供的规范,对不同注册中心来说都是一样的
NacosRibbonClientConfiguration
对于解析NacosRibbonClientConfiguration类,其最关键是会通过@Bean方式向目标服务对应的上下文对象中注入了ServerList类型的Bean,这里的实例为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;
}
//自定义的nacos服务列表
NacosServerList serverList = new NacosServerList(nacosDiscoveryProperties);
serverList.initWithNiwsConfig(config);
return serverList;
}
@Bean
@ConditionalOnMissingBean
public NacosServerIntrospector nacosServerIntrospector() {
return new NacosServerIntrospector();
}
}
NacosServerList实现了ServerList接口提供的getUpdatedListOfServers和getInitialListOfServers两个规范方法,内部都调用了getServers方法,该方法就是完成了服务下客户端实例列表的获取,下面分析实现的流程:
public class NacosServerList extends AbstractServerList<NacosServer> {
@Override
public List<NacosServer> getInitialListOfServers() {
return getServers();
}
@Override
public List<NacosServer> getUpdatedListOfServers() {
//获取目标服务列表
return getServers();
}
private List<NacosServer> getServers() {
try {
String group = discoveryProperties.getGroup();
//获取目标实例列表
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);
}
}
}
获取服务下实例列表
获取服务下实例列表流程:首先,尝试从当前客户端serviceInfoMap 缓存中获取当前调用的服务信息,如果获取不到,则会执行updateServiceNow方法,从远程拉取服务信息到本地。在拉取服务信息时,先通过serverProxy调用queryList方法,对服务端节点发起HTTP GET请求来拉取对应的服务信息,请求地址为:/v1/ns/instance/list,之后,将拉取到的服务信息,更新到serviceInfoMap缓存中,最后,再根据serviceInfoMap中找到对应的serviceInfo实例,并根据该实例来获取最终进行服务调用的实例列表
public class NacosNamingService implements NamingService {
....
@Override
public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy, boolean subscribe) throws NacosException {
ServiceInfo serviceInfo;
if (subscribe) {
//获取请求服务对应的ServiceInfo对象
serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
} else {
serviceInfo = hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
}
//获取实例列表
return selectInstances(serviceInfo, healthy);
}
}
public ServiceInfo getServiceInfo(final String serviceName, final String clusters) {
...
//尝试从serviceInfoMap获取服务信息
ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters);
if (null == serviceObj) {
serviceObj = new ServiceInfo(serviceName, clusters);
serviceInfoMap.put(serviceObj.getKey(), serviceObj);
updatingMap.put(serviceName, new Object());
//向服务端拉取服务信息
updateServiceNow(serviceName, clusters);
updatingMap.remove(serviceName);
} else if (updatingMap.containsKey(serviceName)) {
if (UPDATE_HOLD_INTERVAL > 0) {
// hold a moment waiting for update finish
synchronized (serviceObj) {
try {
serviceObj.wait(UPDATE_HOLD_INTERVAL);
} catch (InterruptedException e) {
NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);
}
}
}
}
//创建定时更新服务信息任务
scheduleUpdateIfAbsent(serviceName, clusters);
return serviceInfoMap.get(serviceObj.getKey());
}
public void updateServiceNow(String serviceName, String clusters) {
ServiceInfo oldService = getServiceInfo0(serviceName, clusters);
try {
//发起HTTP请求查询服务信息
String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUDPPort(), false);
if (StringUtils.isNotEmpty(result)) {
//将查询到服务信息更新到serviceInfoMap中
processServiceJSON(result);
}
} catch (Exception e) {
NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);
} finally {
if (oldService != null) {
synchronized (oldService) {
oldService.notifyAll();
}
}
}
}
public String queryList(String serviceName, String clusters, int udpPort, boolean healthyOnly)
throws NacosException {
final Map<String, String> params = new HashMap<String, String>(8);
params.put