Spring Cloud Alibaba 教程 | Dubbo(五):注册中心

注册中心

在现在各种流行的微服务框架中 ,注册中心Registry是必不可少的组件,Dubbo框架使用了第三方的组件作为其注册中心,其中Zookeeper和Redis可用于生产环境,官方推荐使用Zookeeper作为注册中心(Zookeeper注册中心已经在阿里内部各大项目生产环境中大规模使用过),所以我们文章主要讲解Zookeeper注册中心。
在这里插入图片描述
注册中心的主要作用:

  • 服务注册:服务注册到注册中心后才可以被远程调用,并且注册中心可以动态感知服务的上下线。
  • 服务发现:服务消费者可以通过注册中心动态感知服务提供者的信息,通过路由规则发起远程调用。
  • 动态调参:注册中心可以动态调整参数,并通知相关的服务节点。
  • 统一管理:将参数配置统一管理,方便对各个服务的维护管理。

注册中心的工作流程:

  • 服务提供者启动的时候,会通过注册中心客户端向注册中心发送元数据信息,同时会订阅元数据信息。
  • 服务消费者启动的时候,也会通过通过注册中心客户端向注册中心发送元数据信息,同时会订阅服务提供者信息、路由信息和元数据信息。
  • 服务治理中心(dubbo-admin)启动时则会向注册中心订阅所有服务提供者、服务消费者、路由信息和元数据信息。
  • 当服务提供者下线、增加时,服务提供者的目录内容会发生变化,这时候注册中心会通知订阅了该目录的服务消费者和服务治理中心。
  • 如果存在监控中心(dubbo-monitor),服务消费者在发起远程调用时,会异步将调用、统计信息上报给监控中心。

Zookeeper数据结构

在这里插入图片描述
上图是Zookeeper的树形结构图,共分为四层:Root、Service、Type和URL,其中树的Root根节点默认值是dubbo,可以通过<dubbo:registry>标签配置自定义值,接下来就是Service,它的值就是服务接口全路径名,例如:com.alibaba.dubbo.demo.DemoService,Service下来就是Type层,Type共有四种类型值:providers(包含服务提供者URL)、consumers(包含服务消费者URL)、configurators(包含配置元数据)、routers(包含服务消费者路由规则),其中providers和consumers目录下面包含了URL信息。

例如启动dubbo-demo模块的服务提供者和服务消费者之后,查看到Zookeeper数据结构如下:
在这里插入图片描述
Dubbo使用zookeeper作为注册中心时,创建的Root、Service、Type这三层目录节点都是持久化节点,而providers和consumers下面的URL则都是临时节点。服务提供者和服务消费者在和注册中心会话超时或者下线时会删除对应的URL临时节点。

结合上面的注册中心工作流程可以看出,服务提供者启动注册之后,providers目录下面就会增加一行URL信息,并且会订阅configurators目录。服务消费者启动注册后,会在consumers目录下增加一行URL信息,并且会订阅providers、routers、configurators三个目录。而服务治理中心则会订阅根节点下面的所有目录。

Dubbo服务和注册中心的订阅采用的是“客户端拉取+事件通知”的方式更新配置,客户端第一次连接上注册中心时会全量拉取所有数据,并在订阅的节点目录上注册一个watcher,后续数据发生变化时,通过watcher通知到客户端,客户端收到通知之后拉取对应目录节点下的所有数据。

注册中心Registry结构

Dubbo框架定义了一个注册中心接口Registry(空接口),它继承RegistryService接口和Node接口,RegistryService接口定义了四个注册、订阅相关的接口和一个查询接口。Node接口定义了获取注册中心地址、是否可用、以及销毁操作三个接口。

public interface Registry extends Node, RegistryService {
}
public interface RegistryService {
	/**发起注册*/
    void register(URL url);
    /**取消注册*/
    void unregister(URL url);
	/**发起订阅*/
    void subscribe(URL url, NotifyListener listener);
	/**取消订阅*/
    void unsubscribe(URL url, NotifyListener listener);
    /**查询URL*/
    List<URL> lookup(URL url);
}
public interface Node {
    URL getUrl();
    boolean isAvailable();
    void destroy();
}

下图是注册中心Registry的类结构图:
在这里插入图片描述
AbstractRegistry和FailbackRegistry是两个抽象类,其中AbstractRegistry实现了注册和订阅的方法,负责存储URL和监听器NotifyListener。

@Override
public void register(URL url) {
    //......
    registered.add(url);
}

@Override
public void unregister(URL url) {
    //......
    registered.remove(url);
}

@Override
public void subscribe(URL url, NotifyListener listener) {
    //......
    Set<NotifyListener> listeners = subscribed.get(url);
    if (listeners == null) {
        subscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
        listeners = subscribed.get(url);
    }
    listeners.add(listener);
}

@Override
public void unsubscribe(URL url, NotifyListener listener) {
    //......
    Set<NotifyListener> listeners = subscribed.get(url);
    if (listeners != null) {
        listeners.remove(listener);
    }
}

FailbackRegistry继承了AbstractRegistry抽象类,定义了四个抽象模板方法,在四个注册订阅的实现方法中,除了管理URL和监听器之外,分别调用了对应的模板方法doXXX(),熟悉设计模式的读者朋友应该已经看出来了,这其实是使用了模板方法设计模式,将注册和订阅的逻辑交给子类去实现。

public abstract class FailbackRegistry extends AbstractRegistry {

    /**发起注册失败的URL集合*/
    private final Set<URL> failedRegistered = new ConcurrentHashSet<URL>();

    /**取消注册失败的URL集合*/
    private final Set<URL> failedUnregistered = new ConcurrentHashSet<URL>();

    /**发起订阅失败的监听器集合*/
    private final ConcurrentMap<URL, Set<NotifyListener>> failedSubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();

    /**取消订阅失败的监听器集合*/
    private final ConcurrentMap<URL, Set<NotifyListener>> failedUnsubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();

    /**通知失败的URL集合*/
    private final ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified = new ConcurrentHashMap<URL, Map<NotifyListener, List<URL>>>();


	@Override
	public void register(URL url) {
	    super.register(url);
	    failedRegistered.remove(url);
	    failedUnregistered.remove(url);
	    doRegister(url);
	    //......
	}
	
	@Override
	public void unregister(URL url) {
	    super.unregister(url);
	    failedRegistered.remove(url);
	    failedUnregistered.remove(url);
	    doUnregister(url);
	    //......
	}
	
	@Override
	public void subscribe(URL url, NotifyListener listener) {
	    super.subscribe(url, listener);
	    removeFailedSubscribed(url, listener);
	    doSubscribe(url, listener);
	    //......
	}
	
	@Override
	public void unsubscribe(URL url, NotifyListener listener) {
	    super.unsubscribe(url, listener);
	    removeFailedSubscribed(url, listener);
	    doUnsubscribe(url, listener);
	    //......
	}
	
	// ==== Template method ====
	
	protected abstract void doRegister(URL url);
	
	protected abstract void doUnregister(URL url);
	
	protected abstract void doSubscribe(URL url, NotifyListener listener);
	
	protected abstract void doUnsubscribe(URL url, NotifyListener listener);

}

在Dubbo框架中Registry实例是通过RegistryFactory工厂类获取的,这种方式是通过工厂设计模式实现的,下面是RegistryFactory类结构图:
在这里插入图片描述

@SPI("dubbo")
public interface RegistryFactory {
    @Adaptive({"protocol"})
    Registry getRegistry(URL url);
}

RegistryFactory是一个扩展接口,通过URL的protocol参数获取指定的工厂实现类。
AbstractRegistryFactory实现了RegistryFactory接口,主要负责缓存Registry实例,同时将创建Registry方法createRegistry()交给子类去实现。

扫描二维码关注公众号,回复: 10463892 查看本文章
public abstract class AbstractRegistryFactory implements RegistryFactory {

    //同步锁
    private static final ReentrantLock LOCK = new ReentrantLock();

    //缓存Registry实例
    private static final Map<String, Registry> REGISTRIES = new ConcurrentHashMap<String, Registry>();

    @Override
    public Registry getRegistry(URL url) {
        url = url.setPath(RegistryService.class.getName())
                .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
                .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
        String key = url.toServiceString();
        // Lock the registry access process to ensure a single instance of the registry
        LOCK.lock();
        try {
            Registry registry = REGISTRIES.get(key);
            if (registry != null) {
                return registry;
            }
            registry = createRegistry(url);
            if (registry == null) {
                throw new IllegalStateException("Can not create registry " + url);
            }
            REGISTRIES.put(key, registry);
            return registry;
        } finally {
            // Release the lock
            LOCK.unlock();
        }
    }

    protected abstract Registry createRegistry(URL url);

}
public class ZookeeperRegistryFactory extends AbstractRegistryFactory {

    private ZookeeperTransporter zookeeperTransporter;

    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
        this.zookeeperTransporter = zookeeperTransporter;
    }

    @Override
    public Registry createRegistry(URL url) {
        return new ZookeeperRegistry(url, zookeeperTransporter);
    }

}

ZookeeperRegistryFactory继承了AbstractRegistryFactory,通过ZookeeperTransporter去构造ZookeeperRegistry对象。ZookeeperTransporter负责客户端和注册中心的连接,默认使用curator作为Zookeeper客户端框架。
在这里插入图片描述

public class CuratorZookeeperTransporter implements ZookeeperTransporter {
    @Override
    public ZookeeperClient connect(URL url) {
        return new CuratorZookeeperClient(url);
    }
}

服务注册

当服务启动时,Dubbo框架会通过RegistryProtocol对象执行register()方法,通过registryFactory获取到Registry实现类对象ZookeeperRegistry,接着调用registry.register(registedProviderUrl)方法进行服务注册。关于服务启动流程,以及如何走到这一步的相关内容,将在下一篇文章讲解。

public void register(URL registryUrl, URL registedProviderUrl) {
    Registry registry = registryFactory.getRegistry(registryUrl);
    registry.register(registedProviderUrl);
}

在工厂类实例化ZookeeperRegistry时,会依次调用AbstractRegistry、FailbackRegistry和ZookeeperRegistry构造方法。

public abstract class AbstractRegistry implements Registry {
    //消费者或者服务治理中心缓存注册信息
    private final Properties properties = new Properties();
    //磁盘文件服务缓存对象
    private File file;

    public AbstractRegistry(URL url) {
    	//省略部分代码......
        loadProperties();
        notify(url.getBackupUrls());
    }
}
private void loadProperties() {
	//省略部分代码......
    if (file != null && file.exists()) {
        InputStream in = null;
        try {
            in = new FileInputStream(file);
            properties.load(in);
        }catch (Throwable e) {
            logger.warn("Failed to load registry store file " + file, e);
        }
    }
}

AbstractRegistry使用Properties对象缓存注册中心数据,并且本地磁盘文件也将存储一份,构造方法主要负责将磁盘文件缓存数据加载到Properties对象。
加载完成之后执行notify(),通知监听器更新本地缓存数据。

public abstract class FailbackRegistry extends AbstractRegistry {

    // Scheduled executor service
    private final ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryFailedRetryTimer", true));

    // Timer for failure retry, regular check if there is a request for failure, and if there is, an unlimited retry
    private final ScheduledFuture<?> retryFuture;

    /**发起注册失败的URL集合*/
    private final Set<URL> failedRegistered = new ConcurrentHashSet<URL>();

    /**取消注册失败的URL集合*/
    private final Set<URL> failedUnregistered = new ConcurrentHashSet<URL>();

    /**发起订阅失败的监听器集合*/
    private final ConcurrentMap<URL, Set<NotifyListener>> failedSubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();

    /**取消订阅失败的监听器集合*/
    private final ConcurrentMap<URL, Set<NotifyListener>> failedUnsubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();

    /**通知失败的URL集合*/
    private final ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified = new ConcurrentHashMap<URL, Map<NotifyListener, List<URL>>>();

    public FailbackRegistry(URL url) {
        super(url);
        int retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);
        this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                // Check and connect to the registry
                try {
                    retry();
                } catch (Throwable t) { // Defensive fault tolerance
                    logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
                }
            }
        }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
    }
}
protected void retry() {
    Set<URL> failed = new HashSet<URL>(failedRegistered);
    for (URL url : failed) {
        try {
            doRegister(url);
            failedRegistered.remove(url);
        } catch (Throwable t) { 
        }
    }
	
	Set<URL> failed = new HashSet<URL>(failedUnregistered);
	for (URL url : failed) {
         try {
             doUnregister(url);
             failedUnregistered.remove(url);
         } catch (Throwable t) {
         }
     }
     //省略部分代码......
}

FailbackRegistry构造方法通过ScheduledExecutorService线程池执行器每5秒执行一次retry()方法,该方法依次检查5个集合对象,执行重试动作。

public class ZookeeperRegistry extends FailbackRegistry {

    private final static Logger logger = LoggerFactory.getLogger(ZookeeperRegistry.class);

    private final static int DEFAULT_ZOOKEEPER_PORT = 2181;

    private final static String DEFAULT_ROOT = "dubbo";

    private final String root;

    private final Set<String> anyServices = new ConcurrentHashSet<String>();

    private final ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>> zkListeners = new ConcurrentHashMap<URL, ConcurrentMap<NotifyListener, ChildListener>>();

    private final ZookeeperClient zkClient;

    public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
        super(url);
        if (url.isAnyHost()) {
            throw new IllegalStateException("registry address == null");
        }
        String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
        if (!group.startsWith(Constants.PATH_SEPARATOR)) {
            group = Constants.PATH_SEPARATOR + group;
        }
        this.root = group;
        zkClient = zookeeperTransporter.connect(url);
        zkClient.addStateListener(new StateListener() {
            @Override
            public void stateChanged(int state) {
                if (state == RECONNECTED) {
                    try {
                        //将所有注册的地址放入到注册失败的集合中
	            	    //将所有订阅的地址放入到订阅失败的集合中
                        recover();
                    } catch (Exception e) {
                        logger.error(e.getMessage(), e);
                    }
                }
            }
        });
    }
}

ZookeeperRegistry构造方法通过zookeeperTransporter连接Zookeeper注册中心,并且添加连接状态监听器处理重连事件。

前面介绍过AbstractRegistry和FailbackRegistry在服务注册方法register(URL url)只对URL进行管理,真正的逻辑交给由子类ZookeeperRegistry的doRegister(URL url)方法。

@Override
protected void doRegister(URL url) {
    try {
        zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
    } catch (Throwable e) {
        throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

该方法内容比较简单,直接通过zkClient创建URL节点(临时节点),如果是服务提供者则在providers目录下增加一个URL节点数据,如果是服务消费者则在consumers目录下增加一个URL节点数据,所以通过查看这两个目录下面的URL节点,就可以知道指定服务接口的消费者数量或者提供者数量。

服务订阅

RegistryProtocol执行完服务注册方法register()之后,紧接着会执行订阅方法registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener)

AbstractRegistry.subscribe():添加监听器到集合

private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = 
new ConcurrentHashMap<URL, Set<NotifyListener>>();

@Override
public void subscribe(URL url, NotifyListener listener) {
    Set<NotifyListener> listeners = subscribed.get(url);
    if (listeners == null) {
        subscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
        listeners = subscribed.get(url);
    }
    listeners.add(listener);
}

FailbackRegistry.subscribe():删除订阅失败的监听器,执行doSubscribe(url, listener)方法,交给子类ZookeeperRegistry处理。

@Override
public void subscribe(URL url, NotifyListener listener) {
    super.subscribe(url, listener);
    removeFailedSubscribed(url, listener);
    try {
        doSubscribe(url, listener);
    } catch (Exception e) {
    	......
    }   
}

ZookeeperRegistry.doSubscribe()

@Override
protected void doSubscribe(final URL url, final NotifyListener listener) {
    try {
        if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {//*处理监控中心订阅
            String root = toRootPath();
            ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
            if (listeners == null) {
                zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                listeners = zkListeners.get(url);
            }
            ChildListener zkListener = listeners.get(listener);
            if (zkListener == null) {
                listeners.putIfAbsent(listener, new ChildListener() {
                    @Override
                    public void childChanged(String parentPath, List<String> currentChilds) {
                        for (String child : currentChilds) {
                            child = URL.decode(child);
                            //如果不存在,才订阅
                            if (!anyServices.contains(child)) {
                                anyServices.add(child);
                                //订阅
                                subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child,
                                        Constants.CHECK_KEY, String.valueOf(false)), listener);
                            }
                        }
                    }
                });
                zkListener = listeners.get(listener);
            }
            //创建root节点
            zkClient.create(root, false);
            //添加root节点的子节点监听器,并返回当前的services
            List<String> services = zkClient.addChildListener(root, zkListener);
            if (services != null && !services.isEmpty()) {
                //对root下的所有service节点进行订阅
                for (String service : services) {
                    service = URL.decode(service);
                    anyServices.add(service);
                    subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service,
                            Constants.CHECK_KEY, String.valueOf(false)), listener);
                }
            }
        } else {
            List<URL> urls = new ArrayList<URL>();
            for (String path : toCategoriesPath(url)) {
                //获取监听器集合
                ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                if (listeners == null) {
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                    listeners = zkListeners.get(url);
                }
                //获取监听器对应的子监听器
                ChildListener zkListener = listeners.get(listener);
                if (zkListener == null) {
                    listeners.putIfAbsent(listener, new ChildListener() {
                        @Override
                        public void childChanged(String parentPath, List<String> currentChilds) {
                            ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                        }
                    });
                    zkListener = listeners.get(listener);
                }
                //创建临时节点(持久节点)
                zkClient.create(path, false);
                //将子监听器注册到zkClient
                List<String> children = zkClient.addChildListener(path, zkListener);
                if (children != null) {
                    urls.addAll(toUrlsWithEmpty(url, path, children));
                }
            }
            //通知
            notify(url, listener, urls);
        }
    } catch (Throwable e) {
        throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

该方法处理监控中心和服务的订阅,监控中心是订阅所有服务接口节点目录下的内容,服务的订阅又分为提供者服务订阅和消费者服务订阅,提供者服务订阅configurators节点内容,服务消费者订阅providers、configurators和routers节点内容。

对于服务提供者:

我们直接看else部分的内容,如果此时启动的是服务提供者,那么通过toCategoriesPath(url)转换之后,数组只有一个值:path=/dubbo/com.alibaba.dubbo.demo.DemoService/configurators,紧接着创建子监听器ChildListener(该监听器负责处理注册中心的事件推送通知),然后创建持久化节点zkClient.create(path, false),将监听器通过zkClient注册监听器zkClient.addChildListener(path, zkListener),最后执行通知操作notify(url, listener, urls)

urls只有一个元素:
在这里插入图片描述

对于服务消费者:

通过toCategoriesPath(url)转换之后,数组有三个值:
/dubbo/com.alibaba.dubbo.demo.DemoService/providers
/dubbo/com.alibaba.dubbo.demo.DemoService/configurators
/dubbo/com.alibaba.dubbo.demo.DemoService/routers
处理逻辑和上面相同

urls有三个元素:
在这里插入图片描述

服务通知

服务通知代码最终会路由到AbstractRegistry的notify方法:

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
	//省略部分代码......
    Map<String, List<URL>> result = new HashMap<String, List<URL>>();
    for (URL u : urls) {
        if (UrlUtils.isMatch(url, u)) {
            String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            List<URL> categoryList = result.get(category);
            if (categoryList == null) {
                categoryList = new ArrayList<URL>();
                result.put(category, categoryList);
            }
            categoryList.add(u);
        }
    }
    if (result.size() == 0) {
        return;
    }
    Map<String, List<URL>> categoryNotified = notified.get(url);
    if (categoryNotified == null) {
        notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
        categoryNotified = notified.get(url);
    }
    for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
        String category = entry.getKey();
        List<URL> categoryList = entry.getValue();
        categoryNotified.put(category, categoryList);
        //更新本地缓存数据
        saveProperties(url);
        listener.notify(categoryList);
    }
}

该方法首先通过循环urls,按category为key组装结果,并存放到result集合,如果是服务提供者则result只有一个元素,如果是服务消费者则包含有三个元素。

服务提供者存储的URL以empty开头(因为configurators子节点为空)
在这里插入图片描述
服务消费者存储的URL,providers目录下的URL以dubbo开头
在这里插入图片描述
接着将组装好的result赋值给notified,notified存储了服务对应的订阅目录地址信息。

private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<URL, Map<String, List<URL>>>();

然后执行saveProperties(url)更新本地缓存数据

private void saveProperties(URL url) {
    if (file == null) {
        return;
    }

    try {
        StringBuilder buf = new StringBuilder();
        Map<String, List<URL>> categoryNotified = notified.get(url);
        if (categoryNotified != null) {
            for (List<URL> us : categoryNotified.values()) {
                for (URL u : us) {
                    if (buf.length() > 0) {
                        buf.append(URL_SEPARATOR);
                    }
                    buf.append(u.toFullString());
                }
            }
        }
        //存储到properties对象
        properties.setProperty(url.getServiceKey(), buf.toString());
        long version = lastCacheChanged.incrementAndGet();
        if (syncSaveFile) {
        	//同步将properties数据更新到本地缓存文件
            doSaveProperties(version);
        } else {
        	//异步将properties数据更新到本地缓存文件
            registryCacheExecutor.execute(new SaveProperties(version));
        }
    } catch (Throwable t) {
        logger.warn(t.getMessage(), t);
    }
}

首先通过buf将URL拼接起来,然后存储到properties对象,同时根据syncSaveFile值同步或者异步将数据更新到本地缓存文件。

public void doSaveProperties(long version) {
	//省略部分代码......
    if (!file.exists()) {
       file.createNewFile();
    }
    FileOutputStream outputFile = new FileOutputStream(file);
    try {
        properties.store(outputFile, "Dubbo Registry Cache");
    } finally {
        outputFile.close();
    }
}

doSaveProperties()方法主要通过获取到缓存文件对象File,然后将properties对象写入到缓存文件,同时通过FileLock和version保证File更新顺序。

监听器通知

当服务通过Zookeeper客户端注册订阅监听器后,注册中心会将变更事件通知推送到NamespaceWatcher的处理方法process(WatchedEvent event),接着通过CuratorZookeeperClient回调监听器对象listener.childChanged(String path, List children)方法。

这个监听器对象listener是在服务订阅的执行过程中,在ZookeeperRegistry的doSubscribe方法创建的子监听器匿名对象,并实现了childChanged()回调处理逻辑,该方法直接调用notify()方法。

@Override
protected void doSubscribe(final URL url, final NotifyListener listener) {
   ChildListener zkListener = listeners.get(listener);
   if (zkListener == null) {
       listeners.putIfAbsent(listener, new ChildListener() {
           @Override
           public void childChanged(String parentPath, List<String> currentChilds) {
           	   //处理注册中心事件通知
               ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
           }
       });
       zkListener = listeners.get(listener);
   }
}

如果是消费者服务,此时的NotifyListener接口的实例对象是RegistryDirectory,通过跟踪notify()方法,最终代码会走到AbstractRegistry类的notify()方法,该方法上面已经讲解过,除了执行更新本地缓存之外,最后还执行了listener的notify()方法。

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
   for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
       String category = entry.getKey();
       List<URL> categoryList = entry.getValue();
       //存储更新到Properties对象
       saveProperties(url);
       //执行监听器的notify方法
       listener.notify(categoryList);
   }
}

所以最终会进入到RegistryDirectory类的notify()方法,该方法主要负责更新configurators、routers和providers缓存数据。

@Override
public synchronized void notify(List<URL> urls) {
    List<URL> invokerUrls = new ArrayList<URL>();
    List<URL> routerUrls = new ArrayList<URL>();
    List<URL> configuratorUrls = new ArrayList<URL>();
    for (URL url : urls) {
        String protocol = url.getProtocol();
        String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
        if (Constants.ROUTERS_CATEGORY.equals(category)
                || Constants.ROUTE_PROTOCOL.equals(protocol)) {
            routerUrls.add(url);
        } else if (Constants.CONFIGURATORS_CATEGORY.equals(category)
                || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
            configuratorUrls.add(url);
        } else if (Constants.PROVIDERS_CATEGORY.equals(category)) {
            invokerUrls.add(url);
        } else {
            logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
        }
    }
    // configurators 更新覆盖configurators 
    if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
        this.configurators = toConfigurators(configuratorUrls);
    }
    // routers 更新覆盖routers
    if (routerUrls != null && !routerUrls.isEmpty()) {
        List<Router> routers = toRouters(routerUrls);
        if (routers != null) { // null - do nothing
            setRouters(routers);
        }
    }
    List<Configurator> localConfigurators = this.configurators; // local reference
    // merge override parameters
    this.overrideDirectoryUrl = directoryUrl;
    if (localConfigurators != null && !localConfigurators.isEmpty()) {
        for (Configurator configurator : localConfigurators) {
            this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
        }
    }
    // providers
    //刷新服务提供者
    refreshInvoker(invokerUrls);
}
private void refreshInvoker(List<URL> invokerUrls) {
    if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
            && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
        this.forbidden = true; // Forbid to access
        this.methodInvokerMap = null; // Set the method invoker map to null
        destroyAllInvokers(); // Close all invokers
    } else {
        this.forbidden = false; // Allow to access
        Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
        if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
            invokerUrls.addAll(this.cachedInvokerUrls);
        } else {
            this.cachedInvokerUrls = new HashSet<URL>();
            this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
        }
        if (invokerUrls.isEmpty()) {
            return;
        }
        // 将URL列表转成Invoker列表
        Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
        // 换方法名映射Invoker列表
        Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map
        // state change
        // If the calculation is wrong, it is not processed.
        if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
            logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
            return;
        }
        this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
        this.urlInvokerMap = newUrlInvokerMap;
        try {
            destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
        } catch (Exception e) {
            logger.warn("destroyUnusedInvokers error. ", e);
        }
    }
}

refreshInvoker(List invokerUrls)方法是针对消费者服务来处理的,消费者服务通过该方法更新本地的服务提供者Invoker信息。

服务下线:此时invokerUrls以empty开头,将走到if逻辑部分,那么将forbidden置为true,禁止访问服务,执行destroyAllInvokers()方法关闭所有的Invoker(这里的Invoker代表一个服务接口)。

服务上线:此时代码走到else逻辑部分,此时invokerUrls不为空,那么将会通过this.cachedInvokerUrls.addAll(invokerUrls)覆盖之前的数据,否则当缓存cachedInvokerUrls不为空时将会执行invokerUrls.addAll(this.cachedInvokerUrls)加载本地缓存Invoker的URL数据,最后执行destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap)关闭未使用的Invoker。

关注公众号了解更多原创博文

Alt

发布了122 篇原创文章 · 获赞 127 · 访问量 93万+

猜你喜欢

转载自blog.csdn.net/u010739551/article/details/104281950