nacos源码分析(三)-------------服务注册中心的实现

980 阅读4分钟

基于nacos版本1.1.3.RELEASE. 以一个服务注册的demo为例,看nacos服务的实现.

public class NamingExample {
    public static void main(String[] args) throws NacosException {
        Properties properties = new Properties();
        properties.setProperty("serverAddr", System.getProperty("serverAddr"));
        properties.setProperty("namespace", System.getProperty("namespace"));
        NamingService naming = NamingFactory.createNamingService(properties);
        naming.registerInstance("nacos.test.3", "11.11.11.11", 8888, "TEST1");
        naming.registerInstance("nacos.test.3", "2.2.2.2", 9999, "DEFAULT");
        System.out.println(naming.getAllInstances("nacos.test.3"));
        naming.deregisterInstance("nacos.test.3", "2.2.2.2", 9999, "DEFAULT");

        System.out.println(naming.getAllInstances("nacos.test.3"));

        naming.subscribe("nacos.test.3", new EventListener() {
            @Override
            public void onEvent(Event event) {
                System.out.println(((NamingEvent)event).getServiceName());
                System.out.println(((NamingEvent)event).getInstances());
            }
        });
    }

反射初始化NacosNamingService类,并传入上图构造的Properties配置对象.

 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);
            NamingService vendorImpl = (NamingService)constructor.newInstance(properties);
            return vendorImpl;
        } catch (Throwable e) {
            throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
        }
    }

这里厨初始化NacosNamingService,分为以下几步: 第一步: 初始化NameSpace命名空间,默认是空字符串 第二步: 初始化注册中心的地址 第三步: 初始化web context的base路径 第四步: 初始化日志文件的名称. 第五步: 新创建事件分发处理器EventDispatcher对象. 第六步: 构建NamingProxy对象.设置properties配置对象. 第七步: 构建BeatReactor对象 第八步: 初始化客户端心跳的线程数 第九步: 构建HostReactor对象,已经初始化 第十步: 初始化长轮询的线程数量.

  private void init(Properties properties) {
        namespace = InitUtils.initNamespaceForNaming(properties);
        initServerAddr(properties);
        InitUtils.initWebRootContext();
        initCacheDir();
        initLogName(properties);
        eventDispatcher = new EventDispatcher();
        serverProxy = new NamingProxy(namespace, endpoint, serverList);
        serverProxy.setProperties(properties);
        beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties));
        hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties), initPollingThreadCount(properties));
    }

然后调用NacosNamingService的registerInstance方法,传入serviceName、ip、port、clusterName,然后创建Instance的实例的,并设置ip、端口、权重、集群名称后,调用registerInstance执行服务注册.

    public void registerInstance(String serviceName, String groupName, String ip, int port, String clusterName) throws NacosException {
        //Instance是服务实例的抽象
        Instance instance = new Instance();
        instance.setIp(ip);
        instance.setPort(port);
        instance.setWeight(1.0);
        instance.setClusterName(clusterName);

        registerInstance(serviceName, groupName, instance);
    }

这里可以看到Instance的信息是代表一个服务实例.

public class Instance {
    /**
     * unique id of this instance.
     */
    private String instanceId;
    /**
     * instance ip
     */
    private String ip;
    /**
     * instance port
     */
    private int port;
    /**
     * instance weight
     */
    private double weight = 1.0D;
    /**
     * instance health status
     */
    private boolean healthy = true;
    /**
     * If instance is enabled to accept request
     */
    private boolean enabled = true;
    /**
     * If instance is ephemeral
     * @since 1.0.0
     */
    private boolean ephemeral = true;
    /**
     * cluster information of instance
     */
    private String clusterName;
    /**
     * Service information of instance
     */
    private String serviceName;
    /**
     * user extended attributes
     */
    private Map<String, String> metadata = new HashMap<String, String>();

调用注册实例的时候,首先会判断实例信息是否是零时存储的,如果为true,会将beatInfo加入心跳的周期执行线程池中,默认5s执行一次心跳将自己服务信息的集群的注册中心上送心跳信息.

 @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        if (instance.isEphemeral()) {
            BeatInfo beatInfo = new BeatInfo();
            beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
            beatInfo.setIp(instance.getIp());
            beatInfo.setPort(instance.getPort());
            beatInfo.setCluster(instance.getClusterName());
            beatInfo.setWeight(instance.getWeight());
            beatInfo.setMetadata(instance.getMetadata());
            beatInfo.setScheduled(false);
            long instanceInterval = instance.getInstanceHeartBeatInterval();
            beatInfo.setPeriod(instanceInterval == 0 ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);
            beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
        }
        serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
    }

接着执行serverProxy的注册service的流程,就是注册中心的serverList的 /nacos/v1/ns/instance这个url的POSt接口,进行服务的注册,(服务端的后面详细分析).可以看出传递的参数跟beat的信息差不多,

    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}",
            namespaceId, serviceName, instance);
        final Map<String, String> params = new HashMap<String, String>(9);
        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        params.put(CommonParams.SERVICE_NAME, serviceName);
        params.put(CommonParams.GROUP_NAME, groupName);
        params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
        params.put("ip", instance.getIp());
        params.put("port", String.valueOf(instance.getPort()));
        params.put("weight", String.valueOf(instance.getWeight()));
        params.put("enable", String.valueOf(instance.isEnabled()));
        params.put("healthy", String.valueOf(instance.isHealthy()));
        params.put("ephemeral", String.valueOf(instance.isEphemeral()));
        params.put("metadata", JSON.toJSONString(instance.getMetadata()));
        reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);
    }

然后看下NamingService的订阅逻辑,这里传入参数是要订阅的服务名称,以及EventLister的监听回调.

    public void subscribe(String serviceName, EventListener listener) throws NacosException {
        subscribe(serviceName, new ArrayList<String>(), listener);
    }

这里可以看到通过eventDispatcher的addListener,监听集群的serviceName

    public void subscribe(String serviceName, String groupName, List<String> clusters, EventListener listener) throws NacosException {
        eventDispatcher.addListener(hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName),
            StringUtils.join(clusters, ",")), StringUtils.join(clusters, ","), listener);
    }

首先这里hostReactor是主要获取serviceInfo信息的获取和更新的,它也是实现Runnable接口,下面是run方法的代码,

  @Override
        public void run() {
            try {
                ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
                if (serviceObj == null) {
                    updateServiceNow(serviceName, clusters);
                    executor.schedule(this, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
                    return;
                }
                if (serviceObj.getLastRefTime() <= lastRefTime) {
                    updateServiceNow(serviceName, clusters);
                    serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
                } else {
                    // if serviceName already updated by push, we should not override it
                    // since the push data may be different from pull through force push
                    refreshOnly(serviceName, clusters);
                }
                lastRefTime = serviceObj.getLastRefTime();
                if (!eventDispatcher.isSubscribed(serviceName, clusters) &&    !futureMap.containsKey(ServiceInfo.getKey(serviceName, clusters))) {
                    // abort the update task:
                    NAMING_LOGGER.info("update task is stopped, service:" + serviceName + ", clusters:" + clusters);
                    return;
                }
                executor.schedule(this, serviceObj.getCacheMillis(), TimeUnit.MILLISECONDS);
            } catch (Throwable e) {
                NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);
            }

        }
    }

EventDispatcher是初始化事件监听器,并且执行Notifier线程.

 public EventDispatcher() {
        executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "com.alibaba.nacos.naming.client.listener");
                thread.setDaemon(true);
                return thread;
            }
        });
        executor.execute(new Notifier());
    }

Notifier线程主要是执行监听回调处理逻辑, 回调所有的lister的onEvent的 方法.

 private class Notifier implements Runnable {
        @Override
        public void run() {
            while (true) {
                ServiceInfo serviceInfo = null;
                try {
                    serviceInfo = changedServices.poll(5, TimeUnit.MINUTES);
                } catch (Exception ignore) {
                }
                if (serviceInfo == null) {
                    continue;
                }
                try {
                    List<EventListener> listeners = observerMap.get(serviceInfo.getKey());
                    if (!CollectionUtils.isEmpty(listeners)) {
                        for (EventListener listener : listeners) {
                            List<Instance> hosts = Collections.unmodifiableList(serviceInfo.getHosts());
                            listener.onEvent(new NamingEvent(serviceInfo.getName(), serviceInfo.getGroupName(), serviceInfo.getClusters(), hosts));
                        }
                    }
                } catch (Exception e) {
                    NAMING_LOGGER.error("[NA] notify error for service: "
                        + serviceInfo.getName() + ", clusters: " + serviceInfo.getClusters(), e);
                }
            }
        }
    }

总结
这篇主要是对nacos的注册中心的简单介绍,主要涉及服务的的注册和监听函数的回调.