Dubbo Learning Record (12) - Enregistrez-vous auprès du gardien de zoo pour l'exportation de services

Enregistrement de l'exportation de service au gardien de zoo

  • ServiceConfig#export export, appel couche par couche jusqu'au bout, consiste à appeler RegistryProtocol#export pour exporter le service, l'article précédent expliquait, et expliquait également le démarrage du conteneur ;
  • En raison du centre d'enregistrement des gardiens de zoo que nous utilisons, dubbo enregistrera les informations de service dans le centre d'enregistrement ;
 @Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    
    
      
        URL registryUrl = getRegistryUrl(originInvoker); //
        URL providerUrl = getProviderUrl(originInvoker); // dubbo://192.168.40.17:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-provider-application&bean.name=ServiceBean:org.apache.dubbo.demo.DemoService&bind.ip=192.168.40.17&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&logger=log4j&methods=sayHello&pid=27656&release=2.7.0&side=provider&timeout=3000&timestamp=1590735956489

        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);

        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

        providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);

        // export invoker
        // 根据动态配置重写了providerUrl之后,就会调用DubboProtocol或HttpProtocol去进行导出服务了
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

        // url to registry
        // 得到注册中心-ZookeeperRegistry
        final Registry registry = getRegistry(originInvoker);

        // 得到存入到注册中心去的providerUrl,会对服务提供者url中的参数进行简化
        final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);

        // 将当前服务提供者Invoker,以及该服务对应的注册中心地址,以及简化后的服务url存入ProviderConsumerRegTable
        ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
                registryUrl, registeredProviderUrl);


        //to judge if we need to delay publish
        //是否需要注册到注册中心
        boolean register = providerUrl.getParameter(REGISTER_KEY, true);
        if (register) {
    
    
            // 注册服务,把简化后的服务提供者url注册到registryUrl中去
            register(registryUrl, registeredProviderUrl);
            providerInvokerWrapper.setReg(true);
        }

		//监听内容
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);


        exporter.setRegisterUrl(registeredProviderUrl);
        exporter.setSubscribeUrl(overrideSubscribeUrl);
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<>(exporter);
    }

Registre#getRegistry(originInvoker)

Objectif : obtenir un objet d'instance de registre ;
travail :

  1. obtenir l'URL du registre ;
  2. Grâce au mode usine, créez un objet Registre ;
    private Registry getRegistry(final Invoker<?> originInvoker) {
    
    
        URL registryUrl = getRegistryUrl(originInvoker);
        return registryFactory.getRegistry(registryUrl);
    }
    private URL getRegistryUrl(Invoker<?> originInvoker) {
    
    
        // 将registry://xxx?xx=xx&registry=zookeeper 转为 zookeeper://xxx?xx=xx

        URL registryUrl = originInvoker.getUrl();
        if (REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) {
    
    
            String protocol = registryUrl.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY);
            registryUrl = registryUrl.setProtocol(protocol).removeParameter(REGISTRY_KEY);
        }
        return registryUrl;
    }

RegistryFactory#getRegistry(registryUrl)

est une interface ;

@SPI("dubbo")
public interface RegistryFactory {
    
    
    @Adaptive({
    
    "protocol"})
    Registry getRegistry(URL url);
}
  • La classe ZookeeperRegistryFactory implémente la classe abstraite AbstractRegistryFactory et AbstractRegistryFactory implémente l'interface RegistryFactory ;
  • L'appel de la méthode getRegistry appelle la méthode getRegistry de la classe parent ZookeeperRegistryFactory AbstractRegistryFactory
  • AbstractRegistryFactory#getRegistry définit une logique commune pour la création d'un registre, et comme il existe plusieurs types de registre, la logique createRegistry du registre est transmise aux sous-classes à implémenter ; c'est la pratique habituelle du mode usine ;,
  • Utilisez ReentrantLock pour garantir la sécurité des threads et éviter la création répétée d'instances de registre ;
@Override
    public Registry getRegistry(URL url) {
    
    
        url = URLBuilder.from(url)
                .setPath(RegistryService.class.getName())
                .addParameter(INTERFACE_KEY, RegistryService.class.getName())
                .removeParameters(EXPORT_KEY, REFER_KEY)
                .build();
        String key = url.toServiceStringWithoutResolving();
        // 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;
            }
            //create registry by spi/ioc
            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);

ZookeeperRegistryFactory#createRegistry

Création d'une instance ZookeeperRegistry ;

public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
    
    

    private ZookeeperTransporter zookeeperTransporter;

    /**
     * Invisible injection of zookeeper client via IOC/SPI
     * @param zookeeperTransporter
     */
    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
    
    
        this.zookeeperTransporter = zookeeperTransporter;
    }

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

Créez une instance ZookeeperRegistry ;

  1. Appelez super(url) pour charger certaines configurations publiques ;
  2. Obtenir le nom du groupe, la valeur par défaut est "dubbo"
  3. Déterminez si le nom du groupe commence par "/", sinon, raccordez "/"
  4. Le nœud racine du registre Zookeeper est défini sur le nom de groupe groupe ;
  5. Connectez-vous au centre d'enregistrement;
  6. Surveillez si la connexion est réussie, si la connexion échoue, reconnectez-vous ;
    public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
    
    
        super(url);
        if (url.isAnyHost()) {
    
    
            throw new IllegalStateException("registry address == null");
        }
        String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
        if (!group.startsWith(PATH_SEPARATOR)) {
    
    
            group = PATH_SEPARATOR + group;
        }
        this.root = group;
        zkClient = zookeeperTransporter.connect(url);
        zkClient.addStateListener(state -> {
    
    
            if (state == StateListener.RECONNECTED) {
    
    
                try {
    
    
                    recover();
                } catch (Exception e) {
    
    
                    logger.error(e.getMessage(), e);
                }
            }
        });
    }

Après avoir créé l'instance ZookeeperRegistry, revenez couche par couche et revenez à l'endroit où le registre a été obtenu ;

RegistryProtocol#getRegisteredProviderUrl

Objectif : simplifier et supprimer certaines données inutiles dans l'URL ;

       // 得到存入到注册中心去的providerUrl,会对服务提供者url中的参数进行简化
        final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);

Travail:

  1. Déterminez si le RegistryProtocol est configuré avec Simplified, qui est false par défaut ;
  2. Filtrez la clé commençant par "." dans l'URL, et la clé de monitor, bind.ip, bind.port, qos.enable, qos_host, qos_port, qos.accept.foreign.ip, validation, interfaces ; après filtrage, le rest key est la clé à supprimer ;
  3. Supprimez la clé à supprimer dans l'URL ;
  4. retour

    /**
     * Return the url that is registered to the registry and filter the url parameter once
     * 得到能存入到注册中心去的providerUrl
     * @param providerUrl
     * @return url to registry.
     */
    private URL getRegisteredProviderUrl(final URL providerUrl, final URL registryUrl) {
    
    
        //The address you see at the registry
        // 默认都是走这里
        if (!registryUrl.getParameter(SIMPLIFIED_KEY, false)) {
    
    
            // 移除key以"."开始的参数,以及其他跟服务本身没有关系的参数,比如监控,绑定ip,qosd等等
            return providerUrl.removeParameters(getFilteredKeys(providerUrl)).removeParameters(
                    MONITOR_KEY, BIND_IP_KEY, BIND_PORT_KEY, QOS_ENABLE, QOS_HOST, QOS_PORT, ACCEPT_FOREIGN_IP, VALIDATION_KEY,
                    INTERFACES);
        } else {
    
    
            String extraKeys = registryUrl.getParameter(EXTRA_KEYS_KEY, "");
            // if path is not the same as interface name then we should keep INTERFACE_KEY,
            // otherwise, the registry structure of zookeeper would be '/dubbo/path/providers',
            // but what we expect is '/dubbo/interface/providers'
            // 如果path不等于interface的值,那么则把INTERFACE_KEY添加到extraKeys中
            if (!providerUrl.getPath().equals(providerUrl.getParameter(INTERFACE_KEY))) {
    
    
                if (StringUtils.isNotEmpty(extraKeys)) {
    
    
                    extraKeys += ",";
                }
                extraKeys += INTERFACE_KEY;
            }

            // paramsToRegistry包括了DEFAULT_REGISTER_PROVIDER_KEYS和extraKeys
            String[] paramsToRegistry = getParamsToRegistry(DEFAULT_REGISTER_PROVIDER_KEYS
                    , COMMA_SPLIT_PATTERN.split(extraKeys));

            // 生成只含有paramsToRegistry的对应的参数,并且该参数不能为空
            return URL.valueOf(providerUrl, paramsToRegistry, providerUrl.getParameter(METHODS_KEY, (String[]) null));
        }

    }
    //过滤掉以"."开头的key, 返回一个数组;
    private static String[] getFilteredKeys(URL url) {
    
    
        Map<String, String> params = url.getParameters();

        // 过滤url的参数,找到startsWith(HIDE_KEY_PREFIX)的key
        if (CollectionUtils.isNotEmptyMap(params)) {
    
    
            return params.keySet().stream()
                    .filter(k -> k.startsWith(HIDE_KEY_PREFIX))
                    .toArray(String[]::new);
        } else {
    
    
            return new String[0];
        }
    }

ProviderConsumerRegTable.registerProvider(originInvoker,registryUrl, registerProviderUrl)

Objectif : L'Invocateur actuel du fournisseur de services, l'adresse du centre d'enregistrement correspondant au service et l'URL simplifiée du service sont stockés dans le
travail ProviderConsumerRegTable :

  1. Créez une instance de ProviderInvokerWrapper, qui encapsule l'URL enregistrée, l'exécuteur de service et l'URL simplifiée ;
  2. Obtenez le serviceKey dans providerUrl ;
  3. Obtenez la carte du fournisseur de cacheInvokers en fonction de la clé ;
  4. S'il est vide, insérez un ConcurrentHashMap vide, puis retirez le ConcurrentHashMap en fonction de la clé ;
  5. Prenez l'invocateur comme clé, l'instance de wrapper wrapperInvoker comme valeur et placez-le dans les invocateurs, c'est-à-dire le ConcurrenthashMap nouvellement créé ;
    public static <T> ProviderInvokerWrapper<T> registerProvider(Invoker<T> invoker, URL registryUrl, URL providerUrl) {
    
    
        ProviderInvokerWrapper<T> wrapperInvoker = new ProviderInvokerWrapper<>(invoker, registryUrl, providerUrl);
        String serviceUniqueName = providerUrl.getServiceKey();

        // 根据
        ConcurrentMap<Invoker, ProviderInvokerWrapper> invokers = providerInvokers.get(serviceUniqueName);
        if (invokers == null) {
    
    
            providerInvokers.putIfAbsent(serviceUniqueName, new ConcurrentHashMap<>());
            invokers = providerInvokers.get(serviceUniqueName);
        }
        invokers.put(invoker, wrapperInvoker);
        return wrapperInvoker;
    }

registre(registryUrl, registerProviderUrl)

Inscrivez-vous au registre Zookeeper;

  1. Obtenez la valeur de REGISTER_KEY pour déterminer s'il faut s'enregistrer ;
  2. Si une inscription est requise, placez l'URL du service simplifié dans le centre d'inscription ;
        boolean register = providerUrl.getParameter(REGISTER_KEY, true);
        if (register) {
    
    
            // 注册服务,把简化后的服务提供者url注册到registryUrl中去
            register(registryUrl, registeredProviderUrl);
            providerInvokerWrapper.setReg(true);
        }

  1. Appelez d'abord RegistryFactory.getRegistry pour obtenir l'instance de registre.Puisqu'une instance de registre a été créée et placée dans le cache, à cette étape, elle peut être obtenue directement à partir de la carte ;
  2. La méthode ZookeeperRegistry#register appelée ;
    public void register(URL registryUrl, URL registeredProviderUrl) {
    
    
        Registry registry = registryFactory.getRegistry(registryUrl);
        // 调用ZookeeperRegistry的register方法
        registry.register(registeredProviderUrl);
    }

ZookeeperRegistry#register(registeredProviderUrl)

  • ZookeeperRegistry lui-même n'a pas de méthode de registre, mais hérite de la classe parente FailbackRegistry, et la méthode de registre a été implémentée dans la classe parente
  • Par conséquent, appeler ZookeeperRegistry appelle en fait la méthode register de la classe parent FailbackRegistry ;
  • La méthode doRegister(url) est appelée. Puisqu'il existe plusieurs centres d'enregistrement, il est impossible d'être une opération générale, donc la logique d'implémentation est implémentée par la sous-classe ; c'est-à-dire que la méthode ZookeeperRegistry#doRegister(url) est appelée
  • Opinion personnelle : Le modèle d'usine commun est aussi le concept de design ;
  //FailbackRegistry#register
  @Override
    public void register(URL url) {
    
    
        super.register(url);
        removeFailedRegistered(url);
        removeFailedUnregistered(url);
        try {
    
    
            // Sending a registration request to the server side
            doRegister(url);
        } catch (Exception e) {
    
    
            Throwable t = e;
			//省略部分代码;
            // Record a failed registration request to a failed list, retry regularly
            addFailedRegistered(url);
        }
    }

ZookeeperRegistry#doRegister(url)

Objectif : envoyer une demande à Zookeeper et créer un nœud de configuration dont la valeur toUrlPath(url) est dynamique ;

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

toUrlPath(url)

Obtenez la valeur du nœud ;

	//"/dubbo/com.demo.DemoService/providers/#{url}"
	// toCategoryPath(url) ⇒ "/dubbo/com.demo.DemoService/providers"
	  // PATH_SEPARATOR  ==> "/"
	  //URL.encode(url.toFullString())==>对url进行中文编码
    private String toUrlPath(URL url) {
    
    
        return toCategoryPath(url) + PATH_SEPARATOR + URL.encode(url.toFullString());
    }
    //返回的结果:"/dubbo/com.demo.DemoService/providers"
    // toServicePath(url) ⇒  "/dubbo/com.demo.DemoService"
    // PATH_SEPARATOR  ==> "/"
    // 获取URL的catogory的值, 默认为providers;
    private String toCategoryPath(URL url) {
    
    
        return toServicePath(url) + PATH_SEPARATOR + url.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
    }
    //结果: "/dubbo/com.demo.DemoService"
	//toRootDir() --->  "/dubbo/"
	//URL.encode(name) --> 对URL的name属性进行URL编码; name的值为 com.demo.DemoService
	
    private String toServicePath(URL url) {
    
    
        String name = url.getServiceInterface();
        if (ANY_VALUE.equals(name)) {
    
    
            return toRootPath();
        }
        return toRootDir() + URL.encode(name);
    }
    //结果为:根节点 的值 + “/”, 这里为 "/dubbo/"
   private String toRootDir() {
    
    
        if (root.equals(PATH_SEPARATOR)) {
    
    
            return root;
        }
        return root + PATH_SEPARATOR;
    }

Processus de code source d'exportation de service

  1. La méthode ServiceBean.export() est la méthode d'entrée pour l'exportation. Elle exécutera la méthode ServiceConfig.export() pour terminer l'exportation du service. Une fois l'exportation terminée, un événement Spring ServiceBeanExportedEvent sera publié.
  2. Dans la méthode ServiceConfig.export(), checkAndUpdateSubConfigs() sera appelé en premier. Cette méthode complète principalement l'actualisation des paramètres d'AbstractConfig (obtention des paramètres du centre de configuration, etc.). AbstractConfig fait référence à ApplicationConfig, ProtocolConfig, ServiceConfig, etc. Après rafraîchissant, il vérifiera que les paramètres stub, local, mock et autres sont correctement configurés
  3. Une fois les paramètres actualisés et vérifiés, le service commencera à être exporté. Si l'exportation différée est configurée, alors ScheduledExecutorService sera utilisé pour effectuer l'exportation différée à l'heure spécifiée
  4. Sinon appelez doExport() pour exporter le service
  5. Continuez à appeler doExportUrls() pour l'exportation de service
  6. Tout d'abord, obtenez l'URL du centre d'enregistrement configuré via la méthode loadRegistries(). Il peut y avoir plusieurs centres de configuration, de sorte que le service actuellement exporté doit être enregistré auprès de chaque centre de configuration. Ici, le centre d'enregistrement est représenté par une URL. Oui , quel centre d'enregistrement est utilisé, l'adresse et le port du centre d'enregistrement, les paramètres configurés pour le centre d'enregistrement, etc., existeront sur l'URL, et cette URL commence par register://
  7. Après avoir obtenu les RegistryURLs du registre, il va parcourir toutes les ProtocolConfig du service courant et appeler doExportUrlsFor1Protocol(protocolConfig, RegistryURLs); méthode pour exporter le service courant selon chaque protocole et chaque registre
  8. Dans la méthode doExportUrlsFor1Protocol(), une URL de service sera d'abord construite, incluant
    a. le protocole du service dubbo://,
    b. l'IP et le PORT du service. IP,
    c. et le PATH du service. Si aucun paramètre PATH n'est spécifié, prenez le nom de l'interface
    d. et les paramètres du service. Les paramètres incluent les paramètres du service et le paramètre
    e d'une méthode dans le service. L'URL résultante est similaire à : dubbo:/ /192.168 .1.110:20880/com.tuling.DemoService?timeout=3000&&sayHello.loadbalance=aléatoire
  9. Après avoir obtenu l'URL du service, l'URL du service sera ajoutée à l'URL de registre en tant que paramètre, puis un objet proxy Invoker sera généré à partir de l'URL de registre, de l'interface de service et de la référence de classe d'implémentation de service actuelle, puis l'objet proxy et l'objet ServiceConfig actuel sera empaqueté dans un objet DelegateProviderMetaDataInvoker, DelegateProviderMetaDataInvoker représente un service complet
  10. Ensuite, nous utiliserons le protocole pour exporter le service d'exportation. Après l'exportation, nous obtiendrons un objet Exporter (l'objet Exporter peut être compris comme principalement utilisé pour désexporter (désexporter) le service. Quand le service sera-t-il désinstallé ? Lors de la fermeture du Application Dubbo gracieusement)
  11. Examinons maintenant de plus près comment Protocol exporte des services ?
  12. Mais lorsque la méthode protocol.export(wrapperInvoker) est appelée, parce que protocol est un objet adaptatif de l'interface Protocol, une url sera obtenue selon la méthode genUrl de wrapperInvoker à ce moment, et le point d'extension correspondant sera trouvé selon le protocole de cette URL. À ce moment, le point d'extension est RegistryProtocol, cependant, parce que l'interface Protocol a deux classes wrapper, l'une est ProtocolFilterWrapper et ProtocolListenerWrapper, donc quand la méthode d'exportation est appelée, la méthode d'exportation de ces deux classes wrapper sera passer, mais dans la méthode d'exportation de ces deux classes wrapper Le protocole de registre sera jugé et ne fera pas trop de traitement, il appellera donc directement la méthode export(Invoker originInvoker) de RegistryProtocol à la fin
  13. Dans la méthode d'exportation (Invoker originInvoker) de RegistryProtocol, les opérations suivantes sont principalement effectuées :
    a. Générer un écouteur pour surveiller le changement des données de paramètre de ce service dans le centre de configuration dynamique. Une fois le changement détecté, l'URL du service est réécrit, et dans Lors de l'exportation du service, réécrivez d'abord l'URL du service
    b. Après avoir obtenu l'URL réécrite, appelez doLocalExport() pour exporter le service. Dans cette méthode, la méthode d'exportation de DubboProtocol sera appelée pour exporter le service. Après le l'exportation est réussie, vous obtiendrez un ExporterChangeableWrapper
    ⅰ. La principale chose à faire dans la méthode d'exportation de DubboProtocol est de démarrer NettyServer et de définir une série de RequestHandlers, afin qu'ils puissent être traités par ces RequestHandlers à tour de rôle lors de la réception des demandes ⅱ
    . Ces RequestHandlers ont été triés ci-dessus
    c. Obtenez la classe d'implémentation du registre à partir de originInvoker, comme ZookeeperRegistry
    d. Simplifiez l'URL de service réécrite et supprimez les paramètres qui n'ont pas besoin d'être stockés dans le registre
    e. Appelez ZookeeperRegistry.registry () sur l'URL de service simplifiée Enregistrez-vous auprès du centre d'enregistrement et accédez à
    F. Enfin, encapsulez ExporterChangeableWrapper en tant qu'objet DestroyableExporter et renvoyez-le pour terminer l'exportation du service.

Architecture de l'exportateur

Une fois qu'un service a été exporté avec succès, l'exportateur correspondant sera généré :

  1. DestroyableExporter : la classe wrapper la plus externe d'Exporter, la fonction principale de cette classe doit être utilisée pour les services correspondant à unexporter
  2. ExporterChangeableWrapper : cette classe est principalement chargée de supprimer l'URL du service du registre et de supprimer l'écouteur de configuration dynamique correspondant au service avant de désexporter le service correspondant.
  3. ListenerExporterWrapper : cette classe est principalement responsable de la suppression de l'écouteur d'exportation de service après la désexportation du service correspondant.
  4. DubboExporter : cette classe stocke l'objet Invoker du service correspondant et l'identifiant unique du service actuel. Lorsque NettyServer reçoit la demande, il trouvera l'objet DubboExporter correspondant au service en fonction des informations de service dans la demande, puis obtiendra le Objet invoquant à partir de l'objet.
    insérez la description de l'image ici

Résumer

  • Le mode constructeur, la réflexion et le mode usine sont utilisés dans l'exportation de service, et le code est également très polyvalent ;
  • J'ai une compréhension générale du processus d'exportation de service. En ce qui concerne les changements dynamiques de la configuration de Dubbo, cela implique le mécanisme de surveillance, qui est une caractéristique majeure de Zookeeper. register.subscribe(overrideSubscribeUrl, overrideSubscribeListener) Abonnez-vous à la configuration du registre, et il y a un écouteur en même temps. Si la configuration change, l'écouteur overrideSubscribeListener sera déclenché pour réimporter la configuration ;

Je suppose que tu aimes

Origine blog.csdn.net/yaoyaochengxian/article/details/123600130
conseillé
Classement