Nacos客户端NamingClient源码分析

话不多说,先上一个类图。NamingClientProxyDelegate是这一块比较核心的类,像一个装配工厂一样,将各个工具类组装起来,对外提供接口。

Nacos客户端NamingClient

这边学习研究的客户端源代码为spring-cloud-starter-alibaba-nacos-discovery-2021.0.5.0版本,对应的nacos-client-2.2.0版本。

grpc or http

grpcClientProxy和httpClientProxy都是NamingClientProxy的子类,grpcClientProxy是使用grpc协议,httpClientProxy是使用http协议。

1
2
3
4
5
6
7
8
//NamingClientProxyDelegate.java
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}

private NamingClientProxy getExecuteClientProxy(Instance instance) {
return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
}

getExecuteClientProxy方法只有在NamingClientProxyDelegate的registerService和deregisterService方法中才会被使用。delegate中其他方法都是直接用grpc协议

grpc

只有registerService,deregisterService,serverHealthy方法可能会用到http协议。

http服务注册方式,没多少好讲的。http的可靠性和性能,肯定比不上grpc。nacos-client grpcClientProxy加入了重试和事件订阅机制。请求的可靠性和服务调用实例上下线感知的时效性都要比http好。后续都将grpc的实现逻辑。

服务实例注册

服务注册的发起动作,是嵌入在spring容器启动阶段。服务器注册这边按照临时和持久化服务注册方式,使用不同的客户端实现。

服务注册调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// NamingGrpcClientProxy.java
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
instance);
redoService.cacheInstanceForRedo(serviceName, groupName, instance);
doRegisterService(serviceName, groupName, instance);
}

public void doRegisterService(String serviceName, String groupName, Instance instance) throws NacosException {
InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName,
NamingRemoteConstants.REGISTER_INSTANCE, instance);
requestToServer(request, Response.class);
redoService.instanceRegistered(serviceName, groupName);
}

这段服务注册的代码干了如下几件事情:

  1. redoService缓存服务实例,定时检查是否进行重试
  2. requestToServer发起请求,将服务实例注册到nacos服务端
  3. redoService.instanceRegistered,标记服务已经成功注册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// RedoScheduledTask.java
public void run() {
if (!redoService.isConnected()) {
LogUtils.NAMING_LOGGER.warn("Grpc Connection is disconnect, skip current redo task");
return;
}
try {
redoForInstances();
redoForSubscribes();
} catch (Exception e) {
LogUtils.NAMING_LOGGER.warn("Redo task run with unexpected exception: ", e);
}
}


服务实例订阅

服务订阅的发起动作,是在服务调用阶段发生的。

服务调用

我们看到在发生服务调用时,consumer服务会进行服务订阅。目的是拿到被调用服务的实例列表。并且provider服务发生实例上下线,会通过服务订阅,通知到consumer。
这边订阅的实现,完全是用的grpc协议。

订阅服务干了如下几件事情:

  1. redoService缓存服务订阅,定时检查是否进行重试
  2. grpcClient发起订阅请求
  3. redoService.subscriberRegistered,标记服务已经被订阅

服务实例订阅和服务注册,都用到了NamingGrpcRedoService也就是上述所说的redoService。实现了ConnectionEventListener接口,监听了连接状态变化。按照不同的条件,进行服务注册和订阅的重试。

订阅成功以后,一旦服务实例上下线,就会收到通知。Nacos Naming客户端有两类事件:

  1. InstancesChangeEvent
1
2
3
4
5
6
7
8
//NamingGrpcClientProxy.java
private void start(ServerListFactory serverListFactory, ServiceInfoHolder serviceInfoHolder) throws NacosException {
rpcClient.serverListFactory(serverListFactory);
rpcClient.registerConnectionListener(redoService);
rpcClient.registerServerRequestHandler(new NamingPushRequestHandler(serviceInfoHolder));
rpcClient.start();
NotifyCenter.registerSubscriber(this);
}

这里注册的NamingPushRequestHandler, 干了几件事情:

  • 更新本地缓存的实例列表
  • 与本地缓存的老实例信息比较,发生变化的情况下推送InstancesChangeEvent事件

真正处理该事件的InstancesChangeNotifier,实际并未做啥操作。

  1. ServerListChangedEvent
    ServerListManager创建的job会每隔30s,会去从nacos服务端拉取最新的服务端地址列表,然后发送Event事件。接收该Event的有两处,NamingGrpcClientProxy和NamingHttpClientProxy。
    ServerListChangedEvent相关源码位置
    grpc实现会判断当前连接的地址,是否在最新的地址列表中,如果不在,就会发起重连。
    http实现则对该事件未作任何处理。