Nacos服务注册
一、客户端注册服务
在 Nacos 源码包中,有 example 包,这里面存放的是 Nacos 的示例代码。这里,这里我将 Nacos 的地址和命名空间更换到了我本地的 nacos 服务上。这个测试类主要完成两个操作:
- 服务注册
- 获取服务注册信息
@Test
public void test_registry() throws NacosException, IOException, InterruptedException {
Properties properties = new Properties();
properties.setProperty("serverAddr", "localhost:8848");
properties.setProperty("namespace", "coding666");
NamingService naming = NamingFactory.createNamingService(properties);
naming.registerInstance("nacos.test.3", "11.11.11.11", 8888, "TEST1");
naming.registerInstance("nacos.test.3", "2.2.2.2", 9999, "DEFAULT");
// 读取注册到的服务列表信息
TimeUnit.SECONDS.sleep(2);
List<Instance> allInstances = naming.getAllInstances("nacos.test.3");
log.info("allInstance.length = {}", allInstances.size());
for (Instance instance : allInstances) {
log.info("instance: {}", instance);
}
System.in.read();
}
执行完成后,在 nacos 服务上就能够看到对应的注册信息,并且点击详情之后,能够看到对应注册 IP 地址和端口也是代码之中配置的

从当前案例,我们也能够看到,NamingService 是客户端注册和读取服务地址的核心类。接下来,我们就通过这个类来看一下客户端是如何发起注册的。
/**
* register a instance to service with specified cluster name.
*
* @param serviceName name of service
* @param ip instance ip
* @param port instance port
* @param clusterName instance cluster name, 这个参数实际上是对应于分组名称
* @throws NacosException nacos exception
*/
void registerInstance(String serviceName, String ip, int port, String clusterName) throws NacosException;
最终调用到了:NacosNamingService#registerInstance,方法定义如下:
类型:NacosNamingService.java
这里的 Instance 实际上就是:ip,prot,clusterName, weight(默认值给的是 1.0)
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
NamingUtils.checkInstanceIsLegal(instance);
// 向服务端发起注册请求
clientProxy.registerService(serviceName, groupName, instance);
}
这里的 clientProxy 对应的定义如下:
private NamingClientProxy clientProxy;

这里从继承体系之中能够看到,支持 GRPC请求,Http 请求。那么最终是如何确定要执行哪一种请求呢?我们需要先查看 clientProxy 的构造方式
this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, nacosClientProperties, changeNotifier);
在 NamingClientProxyDelegate 中,又封装了 GRPC 和 http 的调用类
private final NamingHttpClientProxy httpClientProxy;
private final NamingGrpcClientProxy grpcClientProxy;
通过继续查看 registerService 方法,我们发现,最终是通过 instance 的 ephemeral 属性来确定是 GRPC 还是 HTTP。这个值默认是 true,并且在整个过程之中,我们并没有设置过 instance 的 ephemeral 属性,所以默认走的是:GRPC 请求,如果想要改,就需要自行构造 Instance,调用对应重载的 registerService 方法
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
// return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}
对于客户端的注册相对比较简单,接下来,我们看一下服务端是如何处理注册请求的
二、服务端处理注册事件
在 Nacos 服务端处理时间的入口是:GrpcRequestAcceptor,通过客户端传递的 type 字段,来查找不同的 RequestHandler
对于服务注册和销毁对应的 Handler 是:InstanceRequestHandler,对应的源码如下:
com.alibaba.nacos.naming.remote.rpc.handler#handle
@Override
@Secured(action = ActionTypes.WRITE)
public InstanceResponse handle(InstanceRequest request, RequestMeta meta) throws NacosException {
Service service = Service
.newService(request.getNamespace(), request.getGroupName(), request.getServiceName(), true);
switch (request.getType()) {
case NamingRemoteConstants.REGISTER_INSTANCE:
// 处理服务注册请求
return registerInstance(service, request, meta);
case NamingRemoteConstants.DE_REGISTER_INSTANCE:
// 处理服务注销请求
return deregisterInstance(service, request, meta);
default:
throw new NacosException(NacosException.INVALID_PARAM,
String.format("Unsupported request type %s", request.getType()));
}
}
对于服务注册请求:
private InstanceResponse registerInstance(Service service, InstanceRequest request, RequestMeta meta)
throws NacosException {
clientOperationService.registerInstance(service, request.getInstance(), meta.getConnectionId());
NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(),
meta.getClientIp(), true, service.getNamespace(), service.getGroup(), service.getName(),
request.getInstance().getIp(), request.getInstance().getPort()));
return new InstanceResponse(NamingRemoteConstants.REGISTER_INSTANCE);
}
@Override
public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
// 1. 验证 instance 之中的参数信息
NamingUtils.checkInstanceIsLegal(instance);
/*
* 2. 通过 命名空间 + 分组 + 分组,获取对应的单例对象,这里面会维护两个 Map
* 2.1) singletonRepository: key: service value: service 如果不存在,则发布一个事件
* 2.2) namespaceSingletonMaps: key: namespace value: 所有的 service
* 命名空间下 + 分组 + 服务,构成了一条唯一的记录,这也实际和 Nacos 页面上实际是一致的
*/
Service singleton = ServiceManager.getInstance().getSingleton(service);
// 3. 只有持久化的示例才可以访问
if (!singleton.isEphemeral()) {
throw new NacosRuntimeException(NacosException.INVALID_PARAM,
String.format("Current service %s is persistent service, can't register ephemeral instance.",
singleton.getGroupedServiceName()));
}
// 4. 通过客户端管理器读取对应的客户端信息
Client client = clientManager.getClient(clientId);
if (!clientIsLegal(client, clientId)) {
return;
}
InstancePublishInfo instanceInfo = getPublishInfo(instance);
// 这里会发布:ClientChangedEvent 事件
// Service - InstancePublishInfo
client.addServiceInstance(singleton, instanceInfo);
// 设置一下最后更新时间
client.setLastUpdatedTime();
// 设置一下版本信息
client.recalculateRevision();
// com/alibaba/nacos/naming/core/v2/index/ClientServiceIndexesManager.java 消费对应的事件
// 5. 维护 publisherIndexes: Map<Service, Set<client.getClientId>>, 服务和对应发布这个服务的客户端列表
NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
NotifyCenter.publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
}
对于服务注册流程并不复杂,这里总结一下:
1)命名空间 + 服务 + 分组,构成了一个 Service,这个 Service 实际上是单例
2)客户端发起连接之后,服务端会为其创建 Client 对象,通过客户端传入的注册信息,生成 InstancePublishInfo,在 Client 对象之中,维护 Service 和 InstancePublishInfo 的绑定关系
3)维护 服务 和 注册服务的Client 之间的绑定关系

Nacos 处理服务注册的全流程
浙公网安备 33010602011771号