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

这边学习研究的客户端源代码为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协议

只有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); }
|
这段服务注册的代码干了如下几件事情:
- redoService缓存服务实例,定时检查是否进行重试
- requestToServer发起请求,将服务实例注册到nacos服务端
- 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协议。
订阅服务干了如下几件事情:
- redoService缓存服务订阅,定时检查是否进行重试
- grpcClient发起订阅请求
- redoService.subscriberRegistered,标记服务已经被订阅
服务实例订阅和服务注册,都用到了NamingGrpcRedoService也就是上述所说的redoService。实现了ConnectionEventListener接口,监听了连接状态变化。按照不同的条件,进行服务注册和订阅的重试。
订阅成功以后,一旦服务实例上下线,就会收到通知。Nacos Naming客户端有两类事件:
- 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,实际并未做啥操作。
- ServerListChangedEvent
ServerListManager创建的job会每隔30s,会去从nacos服务端拉取最新的服务端地址列表,然后发送Event事件。接收该Event的有两处,NamingGrpcClientProxy和NamingHttpClientProxy。

grpc实现会判断当前连接的地址,是否在最新的地址列表中,如果不在,就会发起重连。
http实现则对该事件未作任何处理。