作为SpringCloudAlibaba微服务架构实战派上下册和RocketMQ消息中间件实战派上下册的作者,我来给大家带来Nacos源码系列的技术文章。
HTTP方式注册服务实例
Naocs使用InstanceController类的HTTP方法register()提供注册服务实例的功能。
/*** 注册服务实例到注册中心。* * @param request HttpServletRequest对象,包含注册服务的请求参数。* @return 返回字符串"ok"表示注册成功。* @throws Exception 抛出异常处理注册过程中的错误。*/
public String register(HttpServletRequest request) throws Exception {// 从请求中获取命名空间ID,未指定则使用默认值final String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,Constants.DEFAULT_NAMESPACE_ID);// 从请求中获取服务名称,这是必选参数final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);// 校验服务名称格式NamingUtils.checkServiceNameFormat(serviceName);// 根据请求构建服务实例对象final Instance instance = HttpRequestInstanceBuilder.newBuilder().setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build();// 向注册中心注册实例getInstanceOperator().registerInstance(namespaceId, serviceName, instance);// 发布注册事件,用于跟踪和日志记录NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(), "", false, namespaceId,NamingUtils.getGroupName(serviceName), NamingUtils.getServiceName(serviceName), instance.getIp(),instance.getPort()));return "ok";
}
这里需要注意的是如下代码实现,其中InstanceOperator接口的实现类为InstanceOperatorClientImpl。
/*** 获取实例操作器的实例。* * @return 返回 InstanceOperator 类型的实例,用于操作服务实例。*/private InstanceOperator getInstanceOperator() {return instanceServiceV2;}
在InstanceOperatorClientImpl类中,Nacos封装了Nacos支持的多种注册方式,
persistent服务注册(非临时)
PersistentClientOperationServiceImpl
PersistentClientOperationServiceImpl
是 Nacos 注册中心的一个关键组件,特别是在处理非临时(persistent)服务注册时。Nacos 是一个用于服务发现和配置管理的开源平台,广泛用于微服务架构中。
在 Nacos 的服务注册流程中,当服务实例尝试注册为非临时服务时,会涉及到 PersistentClientOperationServiceImpl
。这个类负责将服务信息持久化到 Nacos 服务器的存储系统中,通常是通过使用 JRaft 协议(一种基于 Raft 一致性算法的分布式协议)来确保数据的一致性和可靠性。
具体来说,PersistentClientOperationServiceImpl
可能会执行以下任务:
- 接收并处理来自客户端的注册请求。
- 将注册请求中的数据(如服务的 IP 地址、端口、元数据等)按照 Nacos 的数据模型进行格式化。
- 使用 JRaft 协议将数据写入到集群中的多数节点,以确保数据的持久化和一致性。
- 在注册成功后,向客户端返回确认信息。
需要注意的是,由于 Nacos 的内部实现和版本更新可能会发生变化,因此上述描述可能不完全适用于所有版本的 Nacos。
此外,如果在启动 Nacos 服务器时遇到与 PersistentClientOperationServiceImpl
相关的错误,可能需要检查集群状态、网络配置、日志记录等信息来诊断问题。这些错误信息通常会在服务器的日志文件中有所体现,因此查看和分析这些日志是解决问题的关键步骤。
总的来说,PersistentClientOperationServiceImpl
在 Nacos 中扮演着处理非临时服务注册的核心角色,是确保服务数据持久化和一致性的重要组成部分。
/*** 注册一个服务实例。* * @param service 服务信息* @param instance 实例信息* @param clientId 客户端ID* @throws NacosRuntimeException 如果服务是临时服务(非持久化服务),则抛出异常。*/@Overridepublic void registerInstance(Service service, Instance instance, String clientId) {// 获取服务的单例,并检查其是否为临时服务Service singleton = ServiceManager.getInstance().getSingleton(service);if (singleton.isEphemeral()) {throw new NacosRuntimeException(NacosException.INVALID_PARAM,String.format("Current service %s is ephemeral service, can't register persistent instance.",singleton.getGroupedServiceName()));}// 准备实例存储请求,并序列化为二进制数据final InstanceStoreRequest request = new InstanceStoreRequest();request.setService(service);request.setInstance(instance);request.setClientId(clientId);final WriteRequest writeRequest = WriteRequest.newBuilder().setGroup(group()).setData(ByteString.copyFrom(serializer.serialize(request))).setOperation(DataOperation.ADD.name()).build();try {// 向协议层发送写请求,注册实例protocol.write(writeRequest);// 记录注册信息日志Loggers.RAFT.info("Client registered. service={}, clientId={}, instance={}", service, clientId, instance);} catch (Exception e) {// 处理注册过程中可能出现的异常throw new NacosRuntimeException(NacosException.SERVER_ERROR, e);}}
临时服务注册
EphemeralClientOperationServiceImpl
是Nacos注册中心中处理临时(ephemeral)服务注册和注销等操作的组件。与PersistentClientOperationServiceImpl
不同,它处理的服务实例不需要持久化存储,通常这些服务实例的生命周期较短,且依赖于注册它们的客户端进程。
在Nacos中,服务实例可以是临时的或非临时的(持久的)。临时实例通常在客户端与服务端的心跳保持期间存在,如果客户端断开连接或心跳丢失,这些实例将被自动删除。而非临时实例则会被持久化存储,即使客户端断开连接也会保留在注册中心。
EphemeralClientOperationServiceImpl
的主要功能包括:
-
处理临时服务实例的注册请求:当客户端向Nacos注册一个临时服务时,这个类会负责接收并处理这些请求。
-
维护心跳检测:对于临时服务,客户端需要定期发送心跳以表明服务仍然活跃。
EphemeralClientOperationServiceImpl
会检测并处理这些心跳信息,确保服务状态是最新的。 -
处理服务注销:当客户端关闭或不再需要某个服务时,它会发送注销请求,
EphemeralClientOperationServiceImpl
会处理这些请求并从注册中心移除相应的服务实例。 -
通知其他服务消费者:当有新的临时服务注册或现有服务注销时,这个类可能会负责通知其他相关的服务消费者,以确保它们能够获取到最新的服务列表。
由于临时服务不需要持久化存储,因此EphemeralClientOperationServiceImpl
在处理这些服务时更加轻量级和高效。这种特性使得它非常适合于处理动态变化且生命周期较短的服务实例。
/*** 注册一个服务实例到指定的服务中。* * @param service 指定的服务对象。* @param instance 需要注册的实例对象。* @param clientId 客户端ID,用于标识哪个客户端进行了实例注册。* @throws NacosException 当注册过程出现异常时抛出。*/@Overridepublic 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);// 检查客户端及其ID的合法性checkClientIsLegal(client, clientId);// 准备实例发布信息InstancePublishInfo instanceInfo = getPublishInfo(instance);// 向客户端添加服务实例client.addServiceInstance(singleton, instanceInfo);// 更新客户端的最后修改时间client.setLastUpdatedTime();// 重算客户端的版本号client.recalculateRevision();// 发布客户端注册服务事件NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));// 发布实例元数据事件NotifyCenter.publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));}
gRPC方式注册服务实例
在高清楚如何用gRPC方式注册服务实例之前,开发者一定要清楚Nacos的gRPC通信渠道是如何启动。
启动Nacos服务端通信渠道
在Nacos中,GrpcClusterServer
和GrpcSdkServer
是用于不同通信场景的GRPC服务器。具体来说:
-
GrpcClusterServer
:这个服务器主要用于Nacos集群节点之间的交互。当Nacos以集群模式运行时,各个节点之间需要通过GRPC进行通信以保持数据的一致性和同步。GrpcClusterServer
就是为此而设计的,它处理集群内部节点之间的通信请求,确保集群的健壮性和数据的可靠性。 -
GrpcSdkServer
:这个服务器则用于客户端与Nacos服务器之间的交互。当外部客户端(如微服务应用)需要与Nacos服务器进行通信时,例如注册服务、发现服务或获取配置等,就会通过GrpcSdkServer
来完成。它提供了对外服务的GRPC接口,使得客户端能够方便地调用Nacos的功能。
总结来说,当Nacos集群内部节点之间需要通信时,会使用GrpcClusterServer
;而当外部客户端需要与Nacos服务器通信时,则会使用GrpcSdkServer
。这样的设计使得Nacos能够灵活地处理不同类型的通信需求,提高了系统的可扩展性和可维护性。
具体是如何启动的呢?利用抽象类BaseRpcServer中的方法start()。
/*** 在服务器构建之后立即调用此方法,用于启动RPC服务器。* 该方法不接受参数且没有返回值。* 启动过程中,首先记录启动日志,然后启动服务器本身,接着如果存在SSL上下文刷新器,则刷新SSL上下文。* 最后,注册一个关闭钩子,以便在应用程序关闭时能够优雅地停止RPC服务器。* * @throws Exception 如果启动过程中遇到任何错误,则抛出异常。*/
@PostConstruct
public void start() throws Exception {String serverName = getClass().getSimpleName();// 记录启动日志Loggers.REMOTE.info("Nacos {} Rpc server starting at port {}", serverName, getServicePort());startServer();// 如果存在SSL上下文刷新器,则刷新SSL上下文if (RpcServerSslContextRefresherHolder.getInstance() != null) {RpcServerSslContextRefresherHolder.getInstance().refresh(this);}// 记录启动成功日志Loggers.REMOTE.info("Nacos {} Rpc server started at port {}", serverName, getServicePort());// 注册关闭钩子,以优雅地停止服务器Runtime.getRuntime().addShutdownHook(new Thread(() -> {// 记录停止日志Loggers.REMOTE.info("Nacos {} Rpc server stopping", serverName);try {// 停止服务器BaseRpcServer.this.stopServer();// 记录停止成功日志Loggers.REMOTE.info("Nacos {} Rpc server stopped successfully...", serverName);} catch (Exception e) {// 记录停止失败日志Loggers.REMOTE.error("Nacos {} Rpc server stopped fail...", serverName, e);}}));}
最终会启动两个gRPC服务端通信渠道,一个用于Nacos集群节点之间的交互,一个用于客户端与Nacos服务器之间的交互。