『Naocs 2.x』(三) Nacos 服务注册逻辑及通信过程

2,025 阅读6分钟

前言

上一节我们了解到了如何发起注册,这一节我们进一步 Nacos Client 是如何注册到 Nacos Server 的。

Nacos 数据模型

在正式探究 Nacos Client 注册逻辑之前,我们需要了解一下 Nacos 的数据模型。

Nacos 数据模型 Key 由三元组唯一确定, Namespace默认是空串,公共命名空间(public),分组默认是 DEFAULT_GROUP。

首先我们需要明白一点,Nacos 的数据模型是为了进行资源(服务、配置等)隔离,由不同的隔离粒度,划分出了这几个层级。

  • Namespace

    用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。

  • Group

    不同的服务可以归类到同一分组。

  • Service/DataId

    Nacos 中的某个配置集的 ID。配置集 ID 是组织划分配置的维度之一。Data ID 通常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置集,每个配置集都可以被一个有意义的名称标识。Data ID 通常采用类 Java 包(如 com.taobao.tc.refund.log.level)的命名规则保证全局唯一性。此命名规则非强制。

我们目前根据 Nacos 提供的数据模型图,先了解这三个概念即可。这里面有一些与配置中心相关的内容,我们先看一眼,然后掠过即可。

注册实例

从上一节我们知道了,注册中心 Client 会监听 Spring Boot 的 WebServerInitializedEvent 事件,然后调用NacosServiceRegistry # register()

@Override
public void register(Registration registration) {
    .........
    // 获取命名Service
    NamingService namingService = namingService();
    // @Value("${spring.cloud.nacos.discovery.service:${spring.application.name:}}")
    String serviceId = registration.getServiceId();
    // 默认值 String group = "DEFAULT_GROUP"
    String group = nacosDiscoveryProperties.getGroup();
    // 根据 NacosRegistration 的属性创建服务实例
    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) {
        ......
    }
}

我们转到namingService.registerInstance(serviceId, group, instance)

// namingService.registerInstance(serviceId, group, instance);
@Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        NamingUtils.checkInstanceIsLegal(instance);
        // NamingClientProxyDelegate # registerService()
        clientProxy.registerService(serviceName, groupName, instance);
    }

这里有个clientProxy,再进一步执行注册逻辑。

我们来看一下 clientProxy 这个实例的类的 UML 图:

image-20210706105403877

我们看到 NamingClientProxy 的主要实现类有三个:

  • NamingClientProxyDelegate

    代理类。通过此类判断调用以下哪个 clientProxy。

  • NamingHttpClientProxy

    使用 Http 通信协议的客户端。

  • NamingGrpcClientProxy

    使用 Grpc 通信协议的客户端。

OK,我们回到主线上来,继续分析clientProxy.registerService()

// NamingClientProxyDelegate
@Override
    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
    }
 
// 根据instance.isEphemeral()属性选择通信协议,此属性也来自yml配置,默认为true。
private NamingClientProxy getExecuteClientProxy(Instance instance) {
        return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
    }

所以这里实际上选择了NamingGrpcClientProxy执行注册。

//  NamingGrpcClientProxy
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
     ....
     // 创建请求
     InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName,
             NamingRemoteConstants.REGISTER_INSTANCE, instance);
     // 执行实例注册请求
     requestToServer(request, Response.class);
     // 缓存服务实例,断开重新连接时,进行重新注册
     namingGrpcConnectionEventListener.cacheInstanceForRedo(serviceName, groupName, instance);
}

到这里了,请求发送成功,待 Nacos Server 处理完逻辑,服务注册就完成了。

下面我们再进一步了解以下 Nacos 中的 Grpc 通信。

GRPC调用过程

Nacos 2.x 采用 Grpc 协议。Grpc 是谷歌开源的,一个高性能的通用RPC框架。采用 protobuf 来定义接口。

protobuf

下面简要看一下 Nacos Client 与 Server 之间定义的 protobuf 。

syntax = "proto3";
 
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
 
option java_multiple_files = true;
option java_package = "com.alibaba.nacos.api.grpc.auto";
// 定义消息体 Metadata
message Metadata {
  // 请求业务类型
  string type = 3;
  // 客户端Ip
  string clientIp = 8;
  // 请求头
  map<string, string> headers = 7;
}
// 定义消息体 Payload
message Payload {
  // 元数据
  Metadata metadata = 2;
  // 请求体
  google.protobuf.Any body = 3;
}
// 定义接口
service RequestStream {
  // build a streamRequest
  rpc requestStream (Payload) returns (stream Payload) {
  }
}
// 定义接口
service Request {
  // Sends a commonRequest
  rpc request (Payload) returns (Payload) {
  }
}
// 定义接口
service BiRequestStream {
  // Sends a commonRequest
  rpc requestBiStream (stream Payload) returns (stream Payload) {
  }
}

Nacos 定义的是一个大接口,参数为 Payload,然后通过 Type字段来区分业务处理。

调用请求

Request 是 Nacos Client 进行服务请求的实体包装,继承于AbstractNamingRequest,最终要转换为 Payload 进行 grpc 请求。

我们来看一下 Client 的 Request 的 UML 图,暂且隐去了其他内容。

image-20210706112547996

然后,继续看注册实例小节中最后的请求逻辑requestToServer(request, Response.class);

private <T extends Response> T requestToServer(AbstractNamingRequest request, Class<T> responseClass)
        throws NacosException {
    try {
        // 设置请求头
        request.putAllHeader(getSecurityHeaders());
        request.putAllHeader(getSpasHeaders(
                NamingUtils.getGroupedNameOptional(request.getServiceName(), request.getGroupName())));
        // 执行请求
        Response response =
                requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout);
        .......
}

实际调用了rpcClient.request()进行请求:

public Response request(Request request, long timeoutMills) throws NacosException {
    int retryTimes = 0;
    Response response = null;
    Exception exceptionThrow = null;
    long start = System.currentTimeMillis();
    while (retryTimes < RETRY_TIMES && System.currentTimeMillis() < timeoutMills + start) {
        boolean waitReconnect = false;
        try {
            .....
            response = this.currentConnection.request(request, timeoutMills);
            ....
            // return response.
            lastActiveTimeStamp = System.currentTimeMillis();
            return response;
        } catch (Exception e) {
            if (waitReconnect) {
                try {
                    //wait client to re connect.
                    Thread.sleep(Math.min(100, timeoutMills / 3));
                } catch (Exception exception) {
                    //Do nothing.
                }
            }
            ......
        }
        retryTimes++;
    }
    ......
}

继续跟进this.currentConnection.request(request, timeoutMills):

@Override
public Response request(Request request, long timeouts) throws NacosException {
    // 参数转换 Request --> Payload
    Payload grpcRequest = GrpcUtils.convert(request);
    // 请求
    ListenableFuture<Payload> requestFuture = grpcFutureServiceStub.request(grpcRequest);
    Payload grpcResponse = null;
    try {
        // 获取结果
        grpcResponse = requestFuture.get(timeouts, TimeUnit.MILLISECONDS);
    } catch (Exception e) {
        throw new NacosException(NacosException.SERVER_ERROR, e);
    }
    // 参数转换 Payload --> Response
    Response response = (Response) GrpcUtils.parse(grpcResponse);
    return response;
}

就在这里,集中将 Request 转化为 Payload,然后通过 protobuf 生成的 grpcFutureServiceStub执行请求,随即将返回的 Payload 再度转换 Respond。

服务侧处理请求

服务侧的请求处理是由GrpcRequestAcceptor完成的,其继承了由 protobuf 生成的类RequestGrpc.RequestImplBase

public class GrpcRequestAcceptor extends RequestGrpc.RequestImplBase {
    @Autowired
    RequestHandlerRegistry requestHandlerRegistry;
    @Autowired
    private ConnectionManager connectionManager;
 
    @Override
    public void request(Payload grpcRequest, StreamObserver<Payload> responseObserver) {
        .....
        // 从请求中获取 type,上面我们提到过 type 代表业务类型
        String type = grpcRequest.getMetadata().getType();
        .....
        // 根据业务类型,获取不同的业务处理器
        RequestHandler requestHandler = requestHandlerRegistry.getByRequestType(type);
        .....
        Object parseObj = null;
        try {
            parseObj = GrpcUtils.parse(grpcRequest);
        } catch (Exception e) {
            .....
        }
        .....
        Request request = (Request) parseObj;
        try {
            // 获取连接信息,构造 requestMeta
            Connection connection = connectionManager.getConnection(CONTEXT_KEY_CONN_ID.get());
            RequestMeta requestMeta = new RequestMeta();
            requestMeta.setClientIp(connection.getMetaInfo().getClientIp());
            requestMeta.setConnectionId(CONTEXT_KEY_CONN_ID.get());
            requestMeta.setClientVersion(connection.getMetaInfo().getVersion());
            requestMeta.setLabels(connection.getMetaInfo().getLabels());
            connectionManager.refreshActiveTime(requestMeta.getConnectionId());
            // 执行请求处理
            Response response = requestHandler.handleRequest(request, requestMeta);
            // 转换参数, Response --> Payload,
            Payload payloadResponse = GrpcUtils.convert(response);
            traceIfNecessary(payloadResponse, false);
            responseObserver.onNext(payloadResponse);
            responseObserver.onCompleted();
        } catch (Throwable e) {
            ......
        }
 
    }
    ....
}

我们继续来看处理请求的抽象类RequestHandler

这里看起来就是一个简单的模板方法的使用。

public abstract class RequestHandler<T extends Request, S extends Response> {
    @Autowired
    private RequestFilters requestFilters;
 
    public Response handleRequest(T request, RequestMeta meta) throws NacosException {
        // 过滤器处理
        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);
    }
 
    public abstract S handle(T request, RequestMeta meta) throws NacosException;

然后,我们来看RequestHandler的持有类RequestHandlerRegistry

@Service
public class RequestHandlerRegistry implements ApplicationListener<ContextRefreshedEvent> {
    // 持有 RequestHandler 的容器
    Map<String, RequestHandler> registryHandlers = new HashMap<String, RequestHandler>();
    @Autowired
    private TpsMonitorManager tpsMonitorManager;
 
    public RequestHandler getByRequestType(String requestType) {
        return registryHandlers.get(requestType);
    }
 
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        Map<String, RequestHandler> beansOfType = event.getApplicationContext().getBeansOfType(RequestHandler.class);
        Collection<RequestHandler> values = beansOfType.values();
        for (RequestHandler requestHandler : values) {
            Class<?> clazz = requestHandler.getClass();
            boolean skip = false;
            // 取直接继承RequestHandler的 Class
            while (!clazz.getSuperclass().equals(RequestHandler.class)) {
                if (clazz.getSuperclass().equals(Object.class)) {
                    skip = true;
                    break;
                }
                clazz = clazz.getSuperclass();
            }
            if (skip) {
                continue;
            }
            ......
            // 获取类的泛型列表的第一个泛型 T extends Request
            Class tClass = (Class) ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[0];
            // 获取类名作为key, RequestHandler为value.
            registryHandlers.putIfAbsent(tClass.getSimpleName(), requestHandler);
        }
    }
}
 

这个其实算是比较常用的手法,根据类型获取不同的实现类,来处理逻辑,属于策略模式。

我们来接着看RequestHandler的一个实现类InstanceRequestHandler

// 注意这里的泛型,就对应了上面的key
@Component
public class InstanceRequestHandler extends RequestHandler<InstanceRequest, InstanceResponse> {
 
    private final EphemeralClientOperationServiceImpl clientOperationService;
 
    public InstanceRequestHandler(EphemeralClientOperationServiceImpl clientOperationService) {
        this.clientOperationService = clientOperationService;
    }
 
    @Override
    @Secured(action = ActionTypes.WRITE, parser = NamingResourceParser.class)
    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) {
        clientOperationService.registerInstance(service, request.getInstance(), meta.getConnectionId());
        return new InstanceResponse(NamingRemoteConstants.REGISTER_INSTANCE);
    }
    ......

我们来继续进入clientOperationService.registerInstance(service, request.getInstance(), meta.getConnectionId())

@Component("ephemeralClientOperationService")
public class EphemeralClientOperationServiceImpl implements ClientOperationService {
 
    private final ClientManager clientManager;
 
    public EphemeralClientOperationServiceImpl(ClientManagerDelegate clientManager) {
        this.clientManager = clientManager;
    }
 
    @Override
    public void registerInstance(Service service, Instance instance, String clientId) {
        // 创建 Service,如果有这个Service了直接返回,如果没有新创建一个
        Service singleton = ServiceManager.getInstance().getSingleton(service);
        // 获取客户端信息
        Client client = clientManager.getClient(clientId);
        // Instance --> InstancePublishInfo
        InstancePublishInfo instanceInfo = getPublishInfo(instance);
        // 添加客户端实例
        client.addServiceInstance(singleton, instanceInfo);
        client.setLastUpdatedTime();
        // 发布两个事件
        NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
        NotifyCenter
                .publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
    }
    .......
}

那么至此,客户端就注册完毕了。