我们基于springboot来搭建简单的订单和库存服务,通过订单服务调用库存服务,项目代码和配置在文章最后的仓库有,项目文件结构如下图1-1所示:
nacos服务列表如下图2.1-1所示:
postman api接口测试如下图2.1-2所示:
idea 库存服务控制台输出如下2.1-3所示:
我们的测试系统搭建和运行测试通过,下面我们通过nacos官网文档和追踪nacos源码,来分析和理解nacos的服务注册和发现原理。
我们使用nacos客户端一般引入如下依赖:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
即然nacos是基于springboot来完成自动装配,那我们查看spring-cloud-alibaba-nacos-discovery-2.1.0.RELEASE.jar包下/META-INF\spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.nacos.NacosDiscoveryAutoConfiguration,\
com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration,\
com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\
com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientAutoConfiguration,\
com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration
NacosServiceRegistryAutoConfiguration
是咱们服务注册的核心配置类,该类中定义了三个核心的 Bean 对象:
NacosServiceRegistry
:nacos服务注册,服务注册的实现类NacosRegistration
:nacos注册类,获取设置nacos注册属性NacosAutoServiceRegistration
:nacos自动服务注册NacosAutoServiceRegistration
类实现服务向nacos自动注册的功能,它的继承关系如下图2.1-1所示:
当容器启动且上下文准备完毕后, 会调用实现ApplicationListener接口的onApplicationEvent(),跳过中间方法bind()->start()->register()
protected void register() {
this.serviceRegistry.register(getRegistration());
}
继续向下调用实现了ServiceRegistry的NacosServiceRegister(上面配置类其中之一Bean)的register()方法
@Override
public void register(Registration registration) {
// 省略校验。。。
String serviceId = registration.getServiceId();
Instance instance = getNacosInstanceFromRegistration(registration);
try {
namingService.registerInstance(serviceId, instance);
// 省略日志和异常处理
}
继续向下调用实现NacosService接口的NacosNamingService类的registerInstance()方法
public void registerInstance(String serviceName, Instance instance) throws NacosException {
registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);
}
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
// 省略。。。
serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}
继续调用NamingProxy类的registerService()方法
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}",
namespaceId, serviceName, instance);
final Map<String, String> params = new HashMap<String, String>(9);
params.put(CommonParams.NAMESPACE_ID, namespaceId);
params.put(CommonParams.SERVICE_NAME, serviceName);
params.put(CommonParams.GROUP_NAME, groupName);
params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
params.put("ip", instance.getIp());
params.put("port", String.valueOf(instance.getPort()));
params.put("weight", String.valueOf(instance.getWeight()));
params.put("enable", String.valueOf(instance.isEnabled()));
params.put("healthy", String.valueOf(instance.isHealthy()));
params.put("ephemeral", String.valueOf(instance.isEphemeral()));
params.put("metadata", JSON.toJSONString(instance.getMetadata()));
reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);
}
req API我们不在向下追踪,实际通过HttpClient进行远程调用,有兴趣的自行查阅源码或者相关文档。
下面给出示例此处debugger截图如下2.1-2所示:
关于nacos客户端服务自动注册,分析到这里,下面我们去看看请求到达服务端后,服务端怎么处理的。
nacos官网注册实例URL,如下:
/nacos/v2/ns/instance
前面讲解知道nacos本质是基于springboot的web应用,那就好办了。我们去nacos源码找相应的controller,看看是哪个方法匹配该url。
com.alibaba.nacos.naming.controllers.v2.InstanceControllerV2#register()方法完成服务的服务注册
@CanDistro
@PostMapping
@Secured(action = ActionTypes.WRITE)
public Result<String> register(InstanceForm instanceForm) throws NacosException {
// 省略校验
// build instance
Instance instance = buildInstance(instanceForm);
instanceServiceV2.registerInstance(instanceForm.getNamespaceId(), buildCompositeServiceName(instanceForm), instance);
// 省略事件发布
}
我们继续跟进InstanceOperatorClientImpl#registerInstance()方法
@Override
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
NamingUtils.checkInstanceIsLegal(instance);
boolean ephemeral = instance.isEphemeral();
// 生成clientId ip:port#true(orfalse)
String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), ephemeral);
// 缓存客户端
createIpPortClientIfAbsent(clientId);
// 生成服务Servcie对象,new一个新对象
Service service = getService(namespaceId, serviceName, ephemeral);
// 注册实例
clientOperationService.registerInstance(service, instance, clientId);
}
我们来看下它是如何缓存客户端的即createIpPortClientIfAbsent()方法如何执行的?
private void createIpPortClientIfAbsent(String clientId) {
if (!clientManager.contains(clientId)) {
ClientAttributes clientAttributes;
if (ClientAttributesFilter.threadLocalClientAttributes.get() != null) {
clientAttributes = ClientAttributesFilter.threadLocalClientAttributes.get();
} else {
clientAttributes = new ClientAttributes();
}
clientManager.clientConnected(clientId, clientAttributes);
}
}
继续跟进clientOperationService.registerInstance(),ephemeral默认为false即临时实例,调用EphemeralClientOperationServiceImpl#registerInstance()方法,代码如下:
@Override
public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
NamingUtils.checkInstanceIsLegal(instance);
Service singleton = ServiceManager.getInstance().getSingleton(service);
if (!singleton.isEphemeral()) {
throw new NacosRuntimeException(NacosException.INVALID_PARAM,
String.format("Current service %s is persistent service, can't register ephemeral instance.",
singleton.getGroupedServiceName()));
}
Client client = clientManager.getClient(clientId);
if (!clientIsLegal(client, clientId)) {
return;
}
InstancePublishInfo instanceInfo = getPublishInfo(instance);
client.addServiceInstance(singleton, instanceInfo);
client.setLastUpdatedTime();
client.recalculateRevision();
// 省略事件发布
}
到这里我们暂时梳理下客户端和服务的的注册流程,其中还涉及很多其他操作,我们后面慢慢讲解。
如果小伙伴什么问题或者指教,欢迎交流。
❓QQ:806797785
⭐️源代码仓库地址:https://gitee.com/gaogzhen/spring-cloud-study.git
参考地址:
[1]Nacos官网
[2]最新版Nacos 2.X服务端源码分析(一)
[3]Nacos 服务注册源码分析