Nacos源码分析01-客户端服务注册

Nacos源码分析01-客户端服务注册

本系列博客,采用官方源码版本为2.0.3

举一个栗子

  服务注册的源码分析可以从Nacos项目中的Nacos-Client模块看起。如果要了解Nacos客户端是如何将服务注册到Nacos中,就可以查看测试用例中的com.alibaba.nacos.client.NamingTest

  NamingTest就是一个用于客户端注册的测试类,模拟真实的服务注册进Nacos的过程,包括NacosServer连接、实例的创建、实例属性的赋值、注册实例,所以在这个其中包含了服务注册的核心代码,仅从此处的代码分析,可以看出,Nacos注册服务实例时,包含了两大类信息:Nacos Server连接信息和服务注册信息。

public class NamingTest {
    
    @Test
    public void testServiceList() throws Exception {
        
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848");
        properties.put(PropertyKeyConst.USERNAME, "nacos");
        properties.put(PropertyKeyConst.PASSWORD, "nacos");
        
        Instance instance = new Instance();
        instance.setIp("1.1.1.1");
        instance.setPort(800);
        instance.setWeight(2);
        Map<String, String> map = new HashMap<String, String>();
        map.put("netType", "external");
        map.put("version", "2.0");
        instance.setMetadata(map);
    
        NamingService namingService = NacosFactory.createNamingService(properties);
        namingService.registerInstance("nacos.test.1", instance);
        
        ThreadUtils.sleep(5000L);
        
        List<Instance> list = namingService.getAllInstances("nacos.test.1");
        
        System.out.println(list);
        
        ThreadUtils.sleep(30000L);
        //        ExpressionSelector expressionSelector = new ExpressionSelector();
        //        expressionSelector.setExpression("INSTANCE.metadata.registerSource = 'dubbo'");
        //        ListView<String> serviceList = namingService.getServicesOfServer(1, 10, expressionSelector);
        
    }
}

服务注册信息

Nacos Server信息

  Nacos Server连接信息,用Properties存储,包含以下信息:

  • serverAddr
    • 指定链接Nacos服务的地址;
  • username
    • 指定连接Nacos服务的用户名;
  • password
    • 指定连接Nacos服务的密码;
  • namespace
    • 指定连接的命名空间;

服务实例信息

  Nacos中用com.alibaba.nacos.api.naming.pojo.Instance类来表示服务注册信息。服务注册信息主要分成两部分:实例基本信息和元数据。

Instance类
  1. 未避免大量Getter,Setter方法影响阅读,通过@Getter、@Setter代替。
  2. 删除覆盖Object类的常用函数。
  3. 删除几个工具方法
public class Instance implements Serializable {
  
    @Getter @Setter private String instanceId;
    @Getter @Setter private String ip;
    @Getter @Setter private int port;
    @Getter @Setter private double weight = 1.0D;
    @Getter @Setter private boolean healthy = true;
    @Getter @Setter private boolean enabled = true;
    @Getter @Setter private boolean ephemeral = true;
    @Getter @Setter private String clusterName;
    @Getter @Setter private String serviceName;
    
    @Getter @Setter private Map<String, String> metadata = new HashMap<String, String>();

    public void addMetadata(final String key, final String value) {
        //.....
    }

    public long getInstanceHeartBeatInterval() {
        return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_INTERVAL,
                Constants.DEFAULT_HEART_BEAT_INTERVAL);
    }
    
    public long getInstanceHeartBeatTimeOut() {
        return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_TIMEOUT,
                Constants.DEFAULT_HEART_BEAT_TIMEOUT);
    }
    
    public long getIpDeleteTimeout() {
        return getMetaDataByKeyWithDefault(PreservedMetadataKeys.IP_DELETE_TIMEOUT,
                Constants.DEFAULT_IP_DELETE_TIMEOUT);
    }
    
    public String getInstanceIdGenerator() {
        return getMetaDataByKeyWithDefault(PreservedMetadataKeys.INSTANCE_ID_GENERATOR,
                Constants.DEFAULT_INSTANCE_ID_GENERATOR);
    }
    
    /**
     * Returns {@code true} if this metadata contains the specified key.
     *
     * @param key metadata key
     * @return {@code true} if this metadata contains the specified key
     */
    public boolean containsMetadata(final String key) {
        //.....
    }
    
    private long getMetaDataByKeyWithDefault(final String key, final long defaultValue) {
        //.....
    }
    
    private String getMetaDataByKeyWithDefault(final String key, final String defaultValue) {
        //.....
    }
    
}
实例基本信息

临时和持久化的区别主要在健康检查失败后的表现,持久化实例健康检查失败后会被标记成不健康,而临时实例会直接从列表中被删除。

  服务实例信息包括以下属性:

  • instanceId:实例的唯一ID;
  • ip:实例IP,提供给消费者进行通信的地址;
  • port: 端口,提供给消费者访问的端口;
  • weight:权重,当前实例的权限,浮点类型(默认1.0D);
  • healthy:健康状况,默认true;
  • enabled:实例是否能接收请求,默认true;
  • ephemeral:实例是否为瞬时的,默认为true;
    • v1.0时,ephemeral在元数据中。
  • clusterName:实例所属的集群名称;
  • serviceName:实例的服务信息
元数据

  Instance类除了包含了实例的基础信息之外,还包含了用于一个HashMap<String,String>类型的metadata属性。

  在NamingTest测试用例中,存放了两种属性:netType和version。netType是网络类型,值为external应该是外网的意思。version指的是Nacos的大版本。

  除了测试用例中自定义的元数据,在Instance类中还定义了一些默认信息,这些信息通过getInstanceHeartBeatInterval()getInstanceHeartBeatTimeOut()、*getIpDeleteTimeout()*和 **getInstanceIdGenerator()**四个函数获得,根据源码可以得到他们的属性名和默认值。

  • preserved.heart.beat.interval
    • 2.0.x 弃用了
    • 心跳间隙的key,默认为5s,也就是默认5秒进行一次心跳;
    • 必须小于等于preserved.heart.beat.timeout;
    • 必须小于等于preserved.ip.delete.timeout;
  • preserved.heart.beat.timeout
    • 2.0.x 弃用了
    • 心跳超时的key,默认为15s,也就是默认15秒收不到心跳,实例将会标记为不健康;
  • preserved.ip.delete.timeout
    • 2.0.x 弃用了
    • 实例IP被删除的key,默认为30s,也就是30秒收不到心跳,实例将会被移除;
  • preserved.instance.id.generator
    • 实例ID生成器key,默认为simple;

命名服务层

NamingService接口

  NamingService接口是Nacos命名服务对外提供的一个统一接口,看对应的源码就可以发现,它提供了大量实例相关的接口方法。

public interface NamingService {
    
    /*
     * 注册实例
     */
    void registerInstance(String serviceName, String ip, int port) throws NacosException;
    void registerInstance(String serviceName, String groupName, String ip, int port) throws NacosException;
    void registerInstance(String serviceName, String ip, int port, String clusterName) throws NacosException;
    void registerInstance(String serviceName, String groupName, String ip, int port, String clusterName)
            throws NacosException;
    void registerInstance(String serviceName, Instance instance) throws NacosException;
    void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException;
    
    /*
     * 注销实例
     */
    void deregisterInstance(String serviceName, String ip, int port) throws NacosException;
    void deregisterInstance(String serviceName, String groupName, String ip, int port) throws NacosException;
    void deregisterInstance(String serviceName, String ip, int port, String clusterName) throws NacosException;
    void deregisterInstance(String serviceName, String groupName, String ip, int port, String clusterName)
            throws NacosException;
    void deregisterInstance(String serviceName, Instance instance) throws NacosException;
    void deregisterInstance(String serviceName, String groupName, Instance instance) throws NacosException;
    
    /*
     * 获取服务实例列表
     */
    List<Instance> getAllInstances(String serviceName) throws NacosException;
    List<Instance> getAllInstances(String serviceName, String groupName) throws NacosException;
    List<Instance> getAllInstances(String serviceName, boolean subscribe) throws NacosException;
    List<Instance> getAllInstances(String serviceName, String groupName, boolean subscribe) throws NacosException;
    List<Instance> getAllInstances(String serviceName, List<String> clusters) throws NacosException;
    List<Instance> getAllInstances(String serviceName, String groupName, List<String> clusters) throws NacosException;
    List<Instance> getAllInstances(String serviceName, List<String> clusters, boolean subscribe) throws NacosException;
    List<Instance> getAllInstances(String serviceName, String groupName, List<String> clusters, boolean subscribe)
            throws NacosException;
    
    /*
     * 查询健康服务实例
     */
    List<Instance> selectInstances(String serviceName, boolean healthy) throws NacosException;
    List<Instance> selectInstances(String serviceName, String groupName, boolean healthy) throws NacosException;
    List<Instance> selectInstances(String serviceName, boolean healthy, boolean subscribe) throws NacosException;
    List<Instance> selectInstances(String serviceName, String groupName, boolean healthy, boolean subscribe)
            throws NacosException;
    List<Instance> selectInstances(String serviceName, List<String> clusters, boolean healthy) throws NacosException;
    List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy)
            throws NacosException;
    List<Instance> selectInstances(String serviceName, List<String> clusters, boolean healthy, boolean subscribe)
            throws NacosException;
    List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy,
            boolean subscribe) throws NacosException;
    
    /*
     * 基于负载均衡策略选择一个健康实例
     */
    Instance selectOneHealthyInstance(String serviceName) throws NacosException;
    Instance selectOneHealthyInstance(String serviceName, String groupName) throws NacosException;
    Instance selectOneHealthyInstance(String serviceName, boolean subscribe) throws NacosException;
    Instance selectOneHealthyInstance(String serviceName, String groupName, boolean subscribe) throws NacosException;
    Instance selectOneHealthyInstance(String serviceName, List<String> clusters) throws NacosException;
    Instance selectOneHealthyInstance(String serviceName, String groupName, List<String> clusters)
            throws NacosException;
    Instance selectOneHealthyInstance(String serviceName, List<String> clusters, boolean subscribe)
            throws NacosException;
    Instance selectOneHealthyInstance(String serviceName, String groupName, List<String> clusters, boolean subscribe)
            throws NacosException;
    
    /*
     * 订阅服务事件
     */
    void subscribe(String serviceName, EventListener listener) throws NacosException;
    void subscribe(String serviceName, String groupName, EventListener listener) throws NacosException;
    void subscribe(String serviceName, List<String> clusters, EventListener listener) throws NacosException;
    void subscribe(String serviceName, String groupName, List<String> clusters, EventListener listener)
            throws NacosException;
    
    /*
     * 取消订阅服务事件
     */
    void unsubscribe(String serviceName, EventListener listener) throws NacosException;
    void unsubscribe(String serviceName, String groupName, EventListener listener) throws NacosException;
    void unsubscribe(String serviceName, List<String> clusters, EventListener listener) throws NacosException;
    void unsubscribe(String serviceName, String groupName, List<String> clusters, EventListener listener)
            throws NacosException;
    
    /*
     * 获得全部服务名称
     */
    ListView<String> getServicesOfServer(int pageNo, int pageSize) throws NacosException;
    ListView<String> getServicesOfServer(int pageNo, int pageSize, String groupName) throws NacosException;
    ListView<String> getServicesOfServer(int pageNo, int pageSize, AbstractSelector selector) throws NacosException;
    ListView<String> getServicesOfServer(int pageNo, int pageSize, String groupName, AbstractSelector selector)
            throws NacosException;
    
    /**
     * 获得所有订阅的服务
     */
    List<ServiceInfo> getSubscribeServices() throws NacosException;
    
    /**
     * 获得Nacos服务器健康状态
     */
    String getServerStatus();
    
    /**
     * 主动关闭Nacos服务
     */
    void shutDown() throws NacosException;

NamingFactory类

  NamingService实例是通过com.alibaba.nacos.api.naming.NamingFactory工厂类创建的。从代码中可以看出,NamingFactory通过反射机制来实例化NamingService,具体的实现类为NacosNamingService。

public class NamingFactory {
    
    /**
     * Create a new naming service.
     *
     * @param serverList server list
     * @return new naming service
     * @throws NacosException nacos exception
     */
    public static NamingService createNamingService(String serverList) throws NacosException {
        try {
            Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
            Constructor constructor = driverImplClass.getConstructor(String.class);
            return (NamingService) constructor.newInstance(serverList);
        } catch (Throwable e) {
            throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
        }
    }
    
    /**
     * Create a new naming service.
     *
     * @param properties naming service properties
     * @return new naming service
     * @throws NacosException nacos exception
     */
    public static NamingService createNamingService(Properties properties) throws NacosException {
        try {
            Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
            Constructor constructor = driverImplClass.getConstructor(Properties.class);
            return (NamingService) constructor.newInstance(properties);
        } catch (Throwable e) {
            throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
        }
    }
}

注册服务在NacosNamingService中的实现

  测试用例中使用了NamingService的registerInstance(String serviceName, Instance instance)方法来进行服务实例的注册,该方法接收两个参数,服务名称和实例对象。这个方法的最大作用是设置了当前实例的默认分组信息,DEFAULT_GROUP。紧接着调用的registerInstance方法中,会对心跳间隔时间(preserved.heart.beat.interval)进行校验。校验通过后会使用NamingClientProxy这个代理类来进行服务注册。

@Override
public void registerInstance throws NacosException {
    registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);
}

@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
    NamingUtils.checkInstanceIsLegal(instance);//检查心跳
    clientProxy.registerService(serviceName, groupName, instance);//通过代理执行服务注册操作
}

  查看NamingClientProxy构造方法调用的init()方法可以找到clientProxy初始化的代码。


public NacosNamingService(Properties properties) throws NacosException {
    init(properties);
}

private void init(Properties properties) throws NacosException {
    ValidatorUtils.checkInitParam(properties);
    this.namespace = InitUtils.initNamespaceForNaming(properties);
    InitUtils.initSerialization();
    InitUtils.initWebRootContext(properties);
    initLogName(properties);
    
    //初始化变更事件通知器
    this.changeNotifier = new InstancesChangeNotifier();
    NotifyCenter.registerToPublisher(InstancesChangeEvent.class, 16384);
    NotifyCenter.registerSubscriber(changeNotifier);
    
    //初始化服务信息
    this.serviceInfoHolder = new ServiceInfoHolder(namespace, properties);
    
    //初始化clientProxy
    this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder,properties, changeNotifier);
}

底层通信层

NamingClientProxy接口

  NamingClientProxy接口负责客户端的底层通讯方式,调用服务端接口。一共有三个实现类:

NamingClientProxyDelegate
  代理类,对所有NacosNamingService中的方法进行代理,根据实际情况选择http或gRPC协议请求服务端。

NamingGrpcClientProxy
  底层通讯基于gRPC长连接。

NamingHttpClientProxy
  底层通讯基于http短连接。原来Nacos 1.x中NamingProxy重命名过来的,基本没改老代码。

NamingClientProxyDelegate#registerService

  通过之前的源码分析可以了解到,NamingService的registerInstance方法最终会调用NamingClientProxyDelegate的registerService方法。

  getExecuteClientProxy(instance)会根据当前实例是否为临时实例判断使用哪种协议进行注册服务。如果当前实例为瞬时对象,则采用gRPC长连接协议(NamingGrpcClientProxy)进行请求,否则采用http协议(NamingHttpClientProxy)进行请求。

  在Nacos 2.x种实例默认为临时实例,也就是说默认采用了gRPC协议进行服务注册。

@Override
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;
}

基于Grpc协议进行通信

  NamingGrpcClientProxy中的registerService方法,首先会在缓存重试机制需要的信息,随后会基于gRPC进行服务的调用和结果的处理。

  NamingGrpcRedoService中用于registeredInstances类型为ConcurrentMap<String, Instance>。key的数据类型是字符串,由serviceName和groupName拼接而成({serviceName}@@{groupName})。value的数据类型是InstanceRedoData类。gRPC调用成功之后,会将InstanceRedoData对象的registered属性设置成true。

@Override
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);
}

总结

服务注册核心流程

API调用流程图

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值