Nacos 注册流程
客户端
在客户端引入了 spring-cloud-starter-alibaba-nacos-discovery
依赖,发现该 Jar 包使用 SPI 机制引入了很多类,其中有个 NacosServiceRegistryAutoConfiguration
,该类主要用途是:主要用于配置和初始化 Nacos 服务注册相关的组件,让应用在启动时自动注册到 Nacos,并维持与 Nacos 的心跳连接。
在 NacosServiceRegistryAutoConfiguration
引入了三个 Bean,分别是
NacosServiceRegistry
: 提供服务注册和注销的具体实现。NacosRegistration
:封装了当前应用的注册信息。NacosAutoServiceRegistration
:实现自动服务注册逻辑。
主要看 NacosAutoServiceRegistration
这个类,是实现自动注册的逻辑的。根据类图发现,继承了ApplicationEvent
,该类是 Spring 事件机制的核心类,可以通过继承该类,在 Spring 的生命周期中,触发一些事件。
继承了 ApplicationEvent
的类,会在事件发布时,回调 onApplicationEvent
方法
// 回调方法
public void onApplicationEvent(WebServerInitializedEvent event) {
bind(event);
}
public void bind(WebServerInitializedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(((ConfigurableWebServerApplicationContext) context)
.getServerNamespace())) {
return;
}
}
this.port.compareAndSet(0, event.getWebServer().getPort());
this.start();
}
public void start() {
if (!isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
}
if (!this.running.get()) {
this.context.publishEvent(
new InstancePreRegisteredEvent(this, getRegistration()));
// 这个地方会调用子类的 register方法
register();
if (shouldRegisterManagement()) {
registerManagement();
}
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
}
}
// 子类NacosAutoServiceRegistration的register方法
protected void register() {
if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
log.debug("Registration disabled.");
return;
}
if (this.registration.getPort() < 0) {
this.registration.setPort(getPort().get());
}
// 调用父类的register方法
super.register();
}
// 父类的register方法
protected void register() {
// 调用了serviceRegistry的register方法
this.serviceRegistry.register(getRegistration());
}
serviceRegistry 的来源是 Spring 在 AbstractAutoServiceRegistration
初始化时,通过自动注入传入的;
// 在spring 初始化时,自动注入的
protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
看下 serviceRegistry的register 方法
public void register(Registration registration) {
// 校验是否合法
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
// 创建名称空间,若存在直接返回,若不存在,通过反射创建该类
NamingService namingService = namingService();
// 获取servicceId,其实就是服务名称
String serviceId = registration.getServiceId();
// 获取分组,默认为 DEFAULT_GROUP
String group = nacosDiscoveryProperties.getGroup();
// 获取自身实例
Instance instance = getNacosInstanceFromRegistration(registration);
try {
// 注册方法
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
instance.getIp(), instance.getPort());
}
catch (Exception e) {
if (nacosDiscoveryProperties.isFailFast()) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
rethrowRuntimeException(e);
}
else {
log.warn("Failfast is false. {} register failed...{},", serviceId,
registration.toString(), e);
}
}
}
registerInstance
注册方法实现
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
// 存活时间的相关校验
// 心跳超时必须大于心跳间隔
// ip删除超时必须大于心跳间隔
NamingUtils.checkInstanceIsLegal(instance);
// 使用代理去注册服务
// clientProxy 是在构造方法中的init() 方法中,创建了NamingClientProxyDelegate对象
clientProxy.registerService(serviceName, groupName, instance);
}
查看 NamingClientProxyDelegate
的 registerInstance
方法
// NamingClientProxyDelegate 的 注册实例方法
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
// 在这调用的 getExecuteClientProxy,获取NamingClientProxy,然后去调用registerService
getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}
private NamingClientProxy getExecuteClientProxy(Instance instance) {
// 根据是否是临时实例,去判断使用grpc方式注册还是http方式注册
// 默认情况下都是临时实例
return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
}
临时实例与持久实例的区别
特性 | 临时实例 | 持久实例 |
---|---|---|
生命周期 | 短,依赖心跳 | 长,需手动注销 |
自动清除 | 是 | 否 |
数据持久化 | 不持久化,仅存于内存中 | 持久化到磁盘 |
使用场景 | 弹性伸缩、边缘节点等短生命周期服务 | 稳定运行的核心服务 |
由于使用的是临时实例,继续深入grpcClientProxy
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
instance);
// 将实例信息缓存一下,可以看到缓存到了ConcurrentMap<String, InstanceRedoData>
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);
}
private <T extends Response> T requestToServer(AbstractNamingRequest request, Class<T> responseClass)
throws NacosException {
// 只放了核心操作,异常捕获没有写
request.putAllHeader(
getSecurityHeaders(request.getNamespace(), request.getGroupName(), request.getServiceName()));
// 判断requestTimeout,发起请求,通过debug发现,此处的requestTimeout为-1
Response response =
requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout);
}
然后继续调用 rpcClient.request(request)
public Response request(Request request, long timeoutMills) throws NacosException {
...
int retryTimes = 0;
// RETRY_TIMES = 3, 最多重试三次
while (retryTimes < RETRY_TIMES && System.currentTimeMillis() < timeoutMills + start) {
...
response = this.currentConnection.request(request, timeoutMills);
...
retryTimes++;
}
...
}
// 继续查看 this.currentConnection.request(request, timeoutMills) 方法
public Response request(Request request, long timeouts) throws NacosException {
Payload grpcRequest = GrpcUtils.convert(request);
// 发起请求,这里可以发现grpcFutureServiceStub与正常的类不太一样,因为这个类是通过protoc文件编译生成的类,这里调用request,就是向服务端发起了注册请求
// 通过protoc文件定义接口,然后通过编译,将其转换为特定语言的代码,以便开发者能够快速、可靠地进行序列化、反序列化和 RPC 调用,使数据的跨语言和跨平台通信变得更加便捷和高效。
ListenableFuture<Payload> requestFuture = grpcFutureServiceStub.request(grpcRequest);
Payload grpcResponse;
try {
grpcResponse = requestFuture.get(timeouts, TimeUnit.MILLISECONDS);
} catch (Exception e) {
throw new NacosException(NacosException.SERVER_ERROR, e);
}
return (Response) GrpcUtils.parse(grpcResponse);
}
自此,nacos注册的客户端流程结束。
整体流程如下
服务端
服务端从启动 grpc 服务开始,在 com.alibaba.nacos.core.remote.BaseRpcServer
类中,存在 start() 方法,该方法是启动 gprc 服务。
@PostConstruct
public void start() throws Exception {
...
startServer();
...
}
// 构建grpc服务器,设置参数
public void startServer() throws Exception {
...
// 添加服务和拦截器
addServices(handlerRegistry, getSeverInterceptors().toArray(new ServerInterceptor[0]));
....
}
// 注册grpc服务的方法处理器,单向调用注册、双向流注册及相应的处理方法
private void addServices(MutableHandlerRegistry handlerRegistry, ServerInterceptor... serverInterceptor) {
...
final ServerCallHandler<Payload, Payload> payloadHandler = ServerCalls.asyncUnaryCall(
(request, responseObserver) -> {
// 收到单向流请求时,使用该方法进行处理
// 服务状态变动、心跳检测等,都会走这个方法
handleCommonRequest(request, responseObserver);
});
...
}
// 处理来自客户端的grpc请求
protected void handleCommonRequest(Payload grpcRequest, StreamObserver<Payload> responseObserver) {
// 校验请求源是否合法
if (!invokeSourceAllowCheck(grpcRequest)) {
Payload payloadResponse = GrpcUtils.convert(ErrorResponse.build(NacosException.BAD_GATEWAY,
String.format(" invoke %s from %s is forbidden", grpcRequest.getMetadata().getType(),
this.getSource())));
responseObserver.onNext(payloadResponse);
responseObserver.onCompleted();
MetricsMonitor.recordGrpcRequestEvent(grpcRequest.getMetadata().getType(), false,
NacosException.BAD_GATEWAY, null, null, 0);
} else {
// 若请求源合法,进行处理
grpcCommonRequestAcceptor.request(grpcRequest, responseObserver);
}
}
然后看下请求处理的 request 方法
public void request(Payload grpcRequest, StreamObserver<Payload> responseObserver) {
// 各种校验
...
// 根据请求类型,获取请求处理器,若获取不到,则抛出异常
RequestHandler requestHandler = requestHandlerRegistry.getByRequestType(type);
...
// 请求处理
Response response = requestHandler.handleRequest(request, requestMeta);
...
}
先看下请求处理器获取逻辑
Map<String, RequestHandler> registryHandlers = new HashMap<>();
public RequestHandler getByRequestType(String requestType) {
return registryHandlers.get(requestType);
}
可以看到请求处理器时从一个Map中获取,定位下Map的初始化逻辑
// 进行了代码简化
// spring初始化完成后,通过事件发布,回调onApplicationEvent
public void onApplicationEvent(ContextRefreshedEvent event) {
// 获取容器中所有类型为RequestHandler的bean,也就是获取容器中所有的处理器
Map<String, RequestHandler> beansOfType = event.getApplicationContext().getBeansOfType(lei'xin.class);
Collection<RequestHandler> values = beansOfType.values();
for (RequestHandler requestHandler : values) {
// 循环所有的bean,并将其放到map中
registryHandlers.putIfAbsent(tClass.getSimpleName(), requestHandler);
}
}
继续回去看请求处理的流程,也就是 handleRequest() 方法
public Response handleRequest(T request, RequestMeta meta) throws NacosException {
// 遍历 requestFilters 中的所有过滤器,对每个过滤器调用 filter 方法
// 过滤器的注册流程
// 1. 在父类中创建一个List,使用@PostConstruct声明init()方法,并在方法中往List中add自身(this)
// 2. 所有继承了该父类的子类,在spring的实例化过程中,都会执行父类的init()
// 3. 最终所有继承了该父类的子类,都会将自身注册到父类创建的List中
for (AbstractRequestFilter filter : requestFilters.filters) {
try {
Response filterResult = filter.filter(request, meta, this.getClass());
if (filterResult != null && !filterResult.isSuccess()) {
return filterResult;
}
} catch (Throwable throwable) {
Loggers.REMOTE.error("filter error", throwable);
}
}
// 请求处理
return handle(request, meta);
}
继续看请求处理方法 handle,该类有很多实现类,通过debugger发现,服务注册调用的是 InstanceRequestHandler
的handle
在 InstanceRequestHandler
的处理方法中,判断是服务注册还是服务下线,看下服务注册流程
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);
checkClientIsLegal(client, clientId);
InstancePublishInfo instanceInfo = getPublishInfo(instance);
// 添加实例,其实就是存到一个Map中
client.addServiceInstance(singleton, instanceInfo);
client.setLastUpdatedTime();
client.recalculateRevision();
// 发布了 ClientRegisterServiceEvent 事件
NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
NotifyCenter
.publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
}
在注册过程中,发布了一个 ClientRegisterServiceEvent 事件,找一下该事件监听的地方
private void handleClientOperation(ClientOperationEvent event) {
Service service = event.getService();
String clientId = event.getClientId();
// 根据不同的事件,进行不同的处理
// 这个是客户端注册事件
if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) {
addPublisherIndexes(service, clientId);
} else if (event instanceof ClientOperationEvent.ClientDeregisterServiceEvent) {
removePublisherIndexes(service, clientId);
} else if (event instanceof ClientOperationEvent.ClientSubscribeServiceEvent) {
addSubscriberIndexes(service, clientId);
} else if (event instanceof ClientOperationEvent.ClientUnsubscribeServiceEvent) {
removeSubscriberIndexes(service, clientId);
}
}
private void addPublisherIndexes(Service service, String clientId) {
// 将实例信息进行存储到另一个map中,用于
publisherIndexes.computeIfAbsent(service, key -> new ConcurrentHashSet<>()).add(clientId);
// 进行事件发布
NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true));
}
上述两个map的区别
-
publishers
主要存储的是服务实例的详细信息(如服务提供者的 IP 地址、端口、元数据等),它是实际的服务数据。 -
publisherIndexes
则是一个索引数据结构,用于提升查找publishers
中服务实例的效率。
至此 Nacos 的注册流程梳理完成。