dubbo消费者篇

启动过程

DubboAutoConfiguration

自动配置类中配置了消费者@Refrence接口的注入处理类

  @ConditionalOnMissingBean
    @Bean(name = ReferenceAnnotationBeanPostProcessor.BEAN_NAME)
    public ReferenceAnnotationBeanPostProcessor referenceAnnotationBeanPostProcessor() {
        return new ReferenceAnnotationBeanPostProcessor();
    }

ReferenceAnnotationBeanPostProcessor

入口
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

	// Allow post-processors to modify the merged bean definition.
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

使用MergedBeanDefinitionPostProcessor接口处理 beanDefinitation

AutowiredAnnotationBeanPostProcessor

@SuppressWarnings("unchecked")
	public AutowiredAnnotationBeanPostProcessor() {
		this.autowiredAnnotationTypes.add(Autowired.class);
		this.autowiredAnnotationTypes.add(Value.class);
		try {
			this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
					ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
			logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
		}
		catch (ClassNotFoundException ex) {
			// JSR-330 API not available - simply skip.
		}
	}

AutowiredAnnotationBeanPostProcessor处理注解Autowired的字段

	private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
		// Fall back to class name as cache key, for backwards compatibility with custom callers.
		String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
		// Quick check on the concurrent map first, with minimal locking.
		InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
		if (InjectionMetadata.needsRefresh(metadata, clazz)) {
			synchronized (this.injectionMetadataCache) {
				metadata = this.injectionMetadataCache.get(cacheKey);
				if (InjectionMetadata.needsRefresh(metadata, clazz)) {
					if (metadata != null) {
						metadata.clear(pvs);
					}
					metadata = buildAutowiringMetadata(clazz);
					this.injectionMetadataCache.put(cacheKey, metadata);
				}
			}
		}
		return metadata;
	}

寻找字段封装成InjectionMetadata
org.springframework.beans.factory.annotation.InjectionMetadata#inject

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		Collection<InjectedElement> checkedElements = this.checkedElements;
		Collection<InjectedElement> elementsToIterate =
				(checkedElements != null ? checkedElements : this.injectedElements);
		if (!elementsToIterate.isEmpty()) {
			for (InjectedElement element : elementsToIterate) {
				element.inject(target, beanName, pvs);
			}
		}
	}
	@Override
		protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			Field field = (Field) this.member;
			Object value;
			if (this.cached) {
				value = resolvedCachedArgument(beanName, this.cachedFieldValue);
			}
			else {
				DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
				desc.setContainingClass(bean.getClass());
				Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
				Assert.state(beanFactory != null, "No BeanFactory available");
				TypeConverter typeConverter = beanFactory.getTypeConverter();
				try {
					value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
				}
				catch (BeansException ex) {
					throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
				}
				synchronized (this) {
					if (!this.cached) {
						Object cachedFieldValue = null;
						if (value != null || this.required) {
							cachedFieldValue = desc;
							registerDependentBeans(beanName, autowiredBeanNames);
							if (autowiredBeanNames.size() == 1) {
								String autowiredBeanName = autowiredBeanNames.iterator().next();
								if (beanFactory.containsBean(autowiredBeanName) &&
										beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
									cachedFieldValue = new ShortcutDependencyDescriptor(
											desc, autowiredBeanName, field.getType());
								}
							}
						}
						this.cachedFieldValue = cachedFieldValue;
						this.cached = true;
					}
				}
			}
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
				field.set(bean, value);
			}
		}
	}

可见根据依赖的类型,从beanFactory中寻找再通过反射注入

ReferenceAnnotationBeanPostProcessor

再看ReferenceAnnotationBeanPostProcessor是怎么处理的

   public ReferenceAnnotationBeanPostProcessor() {
        super(Reference.class, com.alibaba.dubbo.config.annotation.Reference.class);
    }

从这里看也可以自定义注解继承MergedBeanDefinitionPostProcessor实现注入
处理#@Reference注解
在其继承的AbstractAnnotationBeanPostProcessor中

  private List<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> findFieldAnnotationMetadata(final Class<?> beanClass) {

        final List<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> elements = new LinkedList<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement>();

        ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {

                for (Class<? extends Annotation> annotationType : getAnnotationTypes()) {

                    AnnotationAttributes attributes = getAnnotationAttributes(field, annotationType, getEnvironment(), true, true);

                    if (attributes != null) {

                        if (Modifier.isStatic(field.getModifiers())) {
                            if (logger.isWarnEnabled()) {
                                logger.warn("@" + annotationType.getName() + " is not supported on static fields: " + field);
                            }
                            return;
                        }

                        elements.add(new AnnotatedFieldElement(field, attributes));
                    }
                }
            }
        });

        return elements;

    }

把需要注入的字段封装成AnnotatedFieldElement

AnnotatedFieldElement
 public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement {

        private final Field field;

        private final AnnotationAttributes attributes;

        private volatile Object bean;

        protected AnnotatedFieldElement(Field field, AnnotationAttributes attributes) {
            super(field, null);
            this.field = field;
            this.attributes = attributes;
        }

        @Override
        protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {

            Class<?> injectedType = field.getType();

            Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this);

            ReflectionUtils.makeAccessible(field);

            field.set(bean, injectedObject);

        }

    }
 @Override
    protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {
        /**
         * The name of bean that annotated Dubbo's {@link Service @Service} in local Spring {@link ApplicationContext}
         */
        String referencedBeanName = buildReferencedBeanName(attributes, injectedType);

        /**
         * The name of bean that is declared by {@link Reference @Reference} annotation injection
         */
        String referenceBeanName = getReferenceBeanName(attributes, injectedType);

        ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);

        boolean localServiceBean = isLocalServiceBean(referencedBeanName, referenceBean, attributes);

        registerReferenceBean(referencedBeanName, referenceBean, attributes, localServiceBean, injectedType);

        cacheInjectedReferenceBean(referenceBean, injectedElement);

        return getOrCreateProxy(referencedBeanName, referenceBean, localServiceBean, injectedType);
    }

在这里插入图片描述

注意这里有referencedBeanName和referenceBeanName两个名称,分别代表ServiceBean的name和需要注册到bean工厂的ReferenceBean,注意如果是本地暴露,也就是本地存在ServiceBean,会直接给本地服务ServiceBean引用的ref也就是真正的服务实例beanName注册一个ReferenceBean的名称,直接引用本地服务。

org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor#buildReferenceBeanIfAbsent

   private ReferenceBean buildReferenceBeanIfAbsent(String referenceBeanName, AnnotationAttributes attributes,
                                                     Class<?> referencedType)
            throws Exception {

        ReferenceBean<?> referenceBean = referenceBeanCache.get(referenceBeanName);

        if (referenceBean == null) {
            ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder
                    .create(attributes, applicationContext)
                    .interfaceClass(referencedType);
            referenceBean = beanBuilder.build();
            referenceBeanCache.put(referenceBeanName, referenceBean);
        } else if (!referencedType.isAssignableFrom(referenceBean.getInterfaceClass())) {
            throw new IllegalArgumentException("reference bean name " + referenceBeanName + " has been duplicated, but interfaceClass " +
                    referenceBean.getInterfaceClass().getName() + " cannot be assigned to " + referencedType.getName());
        }
        return referenceBean;
    }

构建ReferenceBean
dubbo消费者篇_第1张图片

ServiceBean和ReferenceBean的注解配置加载方式
  1. serviceBean

org.apache.dubbo.config.spring.beans.factory.annotation.ServiceAnnotationBeanPostProcessor#registerServiceBean

  AbstractBeanDefinition serviceBeanDefinition =
                buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName);

        // ServiceBean Bean name
        String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass);

        if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { // check duplicated candidate bean
            registry.registerBeanDefinition(beanName, serviceBeanDefinition);

            if (logger.isInfoEnabled()) {
                logger.info("The BeanDefinition[" + serviceBeanDefinition +
                        "] of ServiceBean has been registered with name : " + beanName);
            }

        }

在把ServiceBean注册到BeanDefination时
在这里插入图片描述

注解中的属性回放到BeanDefination的pvs中

@Nullable
	private MutablePropertyValues propertyValues;

这样在实例化时自动注入配置

  1. ReferenceBean

org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor#buildReferenceBeanIfAbsent

  private ReferenceBean buildReferenceBeanIfAbsent(String referenceBeanName, AnnotationAttributes attributes,
                                                     Class<?> referencedType)
            throws Exception {

        ReferenceBean<?> referenceBean = referenceBeanCache.get(referenceBeanName);

        if (referenceBean == null) {
            ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder
                    .create(attributes, applicationContext)
                    .interfaceClass(referencedType);
            referenceBean = beanBuilder.build();
            referenceBeanCache.put(referenceBeanName, referenceBean);
        } else if (!referencedType.isAssignableFrom(referenceBean.getInterfaceClass())) {
            throw new IllegalArgumentException("reference bean name " + referenceBeanName + " has been duplicated, but interfaceClass " +
                    referenceBean.getInterfaceClass().getName() + " cannot be assigned to " + referencedType.getName());
        }
        return referenceBean;
    }

在创建ServiceBean时直接读注解属性注入
判断是不是本地jvm中的dubbo服务

   private boolean isLocalServiceBean(String referencedBeanName, ReferenceBean referenceBean, AnnotationAttributes attributes) {
        return existsServiceBean(referencedBeanName) && !isRemoteReferenceBean(referenceBean, attributes);
    }
private boolean isRemoteReferenceBean(ReferenceBean referenceBean, AnnotationAttributes attributes) {
        boolean remote = Boolean.FALSE.equals(referenceBean.isInjvm()) || Boolean.FALSE.equals(attributes.get("injvm"));
        return remote;
    }

如果 是serviceBean并且,暴露到injvm协议,则认为是本地服务

org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor#registerReferenceBean

 private void registerReferenceBean(String referencedBeanName, ReferenceBean referenceBean,
                                       AnnotationAttributes attributes,
                                       boolean localServiceBean, Class<?> interfaceClass) {

        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        String beanName = getReferenceBeanName(attributes, interfaceClass);

        if (localServiceBean) {  // If @Service bean is local one
            /**
             * Get  the @Service's BeanDefinition from {@link BeanFactory}
             * Refer to {@link ServiceAnnotationBeanPostProcessor#buildServiceBeanDefinition}
             */
            AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition(referencedBeanName);
            RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) beanDefinition.getPropertyValues().get("ref");
            // The name of bean annotated @Service
            String serviceBeanName = runtimeBeanReference.getBeanName();
            // register Alias rather than a new bean name, in order to reduce duplicated beans
            beanFactory.registerAlias(serviceBeanName, beanName);
        } else { // Remote @Service Bean
            if (!beanFactory.containsBean(beanName)) {
                beanFactory.registerSingleton(beanName, referenceBean);
            }
        }
    }

如果是本地服务注册一个RefrenceBean 的别名,否则将RefrenceBean注册进bean工厂
org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor#getOrCreateProxy

 private Object getOrCreateProxy(String referencedBeanName, ReferenceBean referenceBean, boolean localServiceBean,
                                    Class<?> serviceInterfaceType) {
        if (localServiceBean) { // If the local @Service Bean exists, build a proxy of Service
            return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType},
                    newReferencedBeanInvocationHandler(referencedBeanName));
        } else {
            exportServiceBeanIfNecessary(referencedBeanName); // If the referenced ServiceBean exits, export it immediately
            return referenceBean.get();
        }
    }

如果 是本地服务直接使用动态代理
ReferencedBeanInvocationHandler

private class ReferencedBeanInvocationHandler implements InvocationHandler {

        private final String referencedBeanName;

        private Object bean;

        private ReferencedBeanInvocationHandler(String referencedBeanName) {
            this.referencedBeanName = referencedBeanName;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = null;
            try {
                if (bean == null) {
                    init();
                }
                result = method.invoke(bean, args);
            } catch (InvocationTargetException e) {
                // re-throws the actual Exception.
                throw e.getTargetException();
            }
            return result;
        }

        private void init() {
            ServiceBean serviceBean = applicationContext.getBean(referencedBeanName, ServiceBean.class);
            this.bean = serviceBean.getRef();
        }
    }

直接本地获取serviceBean,反射调用
否则需要引用服务

referenceBean.get();

dubbo消费者篇_第2张图片

referenceBean继承FactoryBean get方法生成实例

  public synchronized T get() {
        if (destroyed) {
            throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
        }
        if (ref == null) {
            init();
        }
        return ref;
    }
public synchronized void init() {
        if (initialized) {
            return;
        }

        if (bootstrap == null) {
            bootstrap = DubboBootstrap.getInstance();
            bootstrap.init();
        }

        checkAndUpdateSubConfigs();

        checkStubAndLocal(interfaceClass);
        ConfigValidationUtils.checkMock(interfaceClass, this);

        Map<String, String> map = new HashMap<String, String>();
        map.put(SIDE_KEY, CONSUMER_SIDE);

        ReferenceConfigBase.appendRuntimeParameters(map);
        if (!ProtocolUtils.isGeneric(generic)) {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put(REVISION_KEY, revision);
            }

            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), COMMA_SEPARATOR));
            }
        }
        map.put(INTERFACE_KEY, interfaceName);
        AbstractConfig.appendParameters(map, getMetrics());
        AbstractConfig.appendParameters(map, getApplication());
        AbstractConfig.appendParameters(map, getModule());
        // remove 'default.' prefix for configs from ConsumerConfig
        // appendParameters(map, consumer, Constants.DEFAULT_KEY);
        AbstractConfig.appendParameters(map, consumer);
        AbstractConfig.appendParameters(map, this);
        MetadataReportConfig metadataReportConfig = getMetadataReportConfig();
        if (metadataReportConfig != null && metadataReportConfig.isValid()) {
            map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);
        }
        Map<String, AsyncMethodInfo> attributes = null;
        if (CollectionUtils.isNotEmpty(getMethods())) {
            attributes = new HashMap<>();
            for (MethodConfig methodConfig : getMethods()) {
                AbstractConfig.appendParameters(map, methodConfig, methodConfig.getName());
                String retryKey = methodConfig.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(methodConfig.getName() + ".retries", "0");
                    }
                }
                AsyncMethodInfo asyncMethodInfo = AbstractConfig.convertMethodConfig2AsyncInfo(methodConfig);
                if (asyncMethodInfo != null) {
//                    consumerModel.getMethodModel(methodConfig.getName()).addAttribute(ASYNC_KEY, asyncMethodInfo);
                    attributes.put(methodConfig.getName(), asyncMethodInfo);
                }
            }
        }

        String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);
        if (StringUtils.isEmpty(hostToRegistry)) {
            hostToRegistry = NetUtils.getLocalHost();
        } else if (isInvalidLocalHost(hostToRegistry)) {
            throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
        }
        map.put(REGISTER_IP_KEY, hostToRegistry);

        serviceMetadata.getAttachments().putAll(map);

        ref = createProxy(map);

        serviceMetadata.setTarget(ref);
        serviceMetadata.addAttribute(PROXY_CLASS_REF, ref);
        ConsumerModel consumerModel = repository.lookupReferredService(serviceMetadata.getServiceKey());
        consumerModel.setProxyObject(ref);
        consumerModel.init(attributes);

        initialized = true;

        // dispatch a ReferenceConfigInitializedEvent since 2.7.4
        dispatch(new ReferenceConfigInitializedEvent(this, invoker));
    }
org.apache.dubbo.config.bootstrap.DubboBootstrap#initialize
private void initialize() {
        if (!initialized.compareAndSet(false, true)) {
            return;
        }

        ApplicationModel.initFrameworkExts();

        startConfigCenter();

        useRegistryAsConfigCenterIfNecessary();

        loadRemoteConfigs();

        checkGlobalConfigs();

        initMetadataService();

        initEventListener();

        if (logger.isInfoEnabled()) {
            logger.info(NAME + " has been initialized!");
        }
    }

跟生产者的几个步骤相同,不再重复看
来到checkAndUpdateSubConfigs

  public void checkAndUpdateSubConfigs() {
        if (StringUtils.isEmpty(interfaceName)) {
            throw new IllegalStateException(" interface not allow null!");
        }
        completeCompoundConfigs(consumer);
        if (consumer != null) {
            if (StringUtils.isEmpty(registryIds)) {
                setRegistryIds(consumer.getRegistryIds());
            }
        }
        // get consumer's global configuration
        checkDefault();
        this.refresh();
        if (getGeneric() == null && getConsumer() != null) {
            setGeneric(getConsumer().getGeneric());
        }
        if (ProtocolUtils.isGeneric(generic)) {
            interfaceClass = GenericService.class;
        } else {
            try {
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            checkInterfaceAndMethods(interfaceClass, getMethods());
        }

        //init serivceMetadata
        serviceMetadata.setVersion(version);
        serviceMetadata.setGroup(group);
        serviceMetadata.setDefaultGroup(group);
        serviceMetadata.setServiceType(getActualInterface());
        serviceMetadata.setServiceInterfaceName(interfaceName);
        // TODO, uncomment this line once service key is unified
        serviceMetadata.setServiceKey(URL.buildKey(interfaceName, group, version));

        ServiceRepository repository = ApplicationModel.getServiceRepository();
        ServiceDescriptor serviceDescriptor = repository.registerService(interfaceClass);
        repository.registerConsumer(
                serviceMetadata.getServiceKey(),
                serviceDescriptor,
                this,
                null,
                serviceMetadata);

        resolveFile();
        ConfigValidationUtils.validateReferenceConfig(this);
        postProcessConfig();
    }

重点看refresh方法

 public void refresh() {
        Environment env = ApplicationModel.getEnvironment();
        try {
            CompositeConfiguration compositeConfiguration = env.getPrefixedConfiguration(this);
            // loop methods, get override value and set the new value back to method
            Method[] methods = getClass().getMethods();
            for (Method method : methods) {
                if (MethodUtils.isSetter(method)) {
                    try {
                        String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method)));
                        // isTypeMatch() is called to avoid duplicate and incorrect update, for example, we have two 'setGeneric' methods in ReferenceConfig.
                        if (StringUtils.isNotEmpty(value) && ClassUtils.isTypeMatch(method.getParameterTypes()[0], value)) {
                            method.invoke(this, ClassUtils.convertPrimitive(method.getParameterTypes()[0], value));
                        }
                    } catch (NoSuchMethodException e) {
                        logger.info("Failed to override the property " + method.getName() + " in " +
                                this.getClass().getSimpleName() +
                                ", please make sure every property has getter/setter method provided.");
                    }
                } else if (isParametersSetter(method)) {
                    String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method)));
                    if (StringUtils.isNotEmpty(value)) {
                        Map<String, String> map = invokeGetParameters(getClass(), this);
                        map = map == null ? new HashMap<>() : map;
                        map.putAll(convert(StringUtils.parseParameters(value), ""));
                        invokeSetParameters(getClass(), this, map);
                    }
                }
            }
        } catch (Exception e) {
            logger.error("Failed to override ", e);
        }
    }

org.apache.dubbo.common.config.Environment#getPrefixedConfiguration

   public synchronized CompositeConfiguration getPrefixedConfiguration(AbstractConfig config) {
//        CompositeConfiguration prefixedConfiguration =
//                prefixedConfigurations.putIfAbsent(config, new CompositeConfiguration(config.getPrefix(), config.getId()));
//        if (prefixedConfiguration != null) {
//            return prefixedConfiguration;
//        }
//        prefixedConfiguration = prefixedConfigurations.get(config);
        CompositeConfiguration prefixedConfiguration = new CompositeConfiguration(config.getPrefix(), config.getId());
        Configuration configuration = new ConfigConfigurationAdapter(config);
        if (this.isConfigCenterFirst()) {
            // The sequence would be: SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration
            // Config center has the highest priority
            prefixedConfiguration.addConfiguration(systemConfiguration);
            prefixedConfiguration.addConfiguration(environmentConfiguration);
            prefixedConfiguration.addConfiguration(appExternalConfiguration);
            prefixedConfiguration.addConfiguration(externalConfiguration);
            prefixedConfiguration.addConfiguration(configuration);
            prefixedConfiguration.addConfiguration(propertiesConfiguration);
        } else {
            // The sequence would be: SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> PropertiesConfiguration
            // Config center has the highest priority
            prefixedConfiguration.addConfiguration(systemConfiguration);
            prefixedConfiguration.addConfiguration(environmentConfiguration);
            prefixedConfiguration.addConfiguration(configuration);
            prefixedConfiguration.addConfiguration(appExternalConfiguration);
            prefixedConfiguration.addConfiguration(externalConfiguration);
            prefixedConfiguration.addConfiguration(propertiesConfiguration);
        }
        return prefixedConfiguration;
    }

这里有一个有趣的地方
他把本身包装成了一个config放入prefixedConfiguration
目的是实现优先级控制
如果配置了configCenterFirst为true
那么配置中心的配置优先于本身,否则,本身优先于配置中心。
如果配置中心的优先级更高,那么即使是方法注解上的配置,也能被配置中心上的配置覆盖
这点所有的Config都相同,refresh都是这个的逻辑

回到checkAndUpdateSubConfigs继续
创建serviceMetadata注册到ServiceRepository中
根据接口和配置组装数据
最终得map例如
dubbo消费者篇_第3张图片

org.apache.dubbo.config.ReferenceConfig#createProxy

 private T createProxy(Map<String, String> map) {
        if (shouldJvmRefer(map)) {
            URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
            invoker = REF_PROTOCOL.refer(interfaceClass, url);
            if (logger.isInfoEnabled()) {
                logger.info("Using injvm service " + interfaceClass.getName());
            }
        } else {
            urls.clear();
            if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
                String[] us = SEMICOLON_SPLIT_PATTERN.split(url);
                if (us != null && us.length > 0) {
                    for (String u : us) {
                        URL url = URL.valueOf(u);
                        if (StringUtils.isEmpty(url.getPath())) {
                            url = url.setPath(interfaceName);
                        }
                        if (UrlUtils.isRegistry(url)) {
                            urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
                        } else {
                            urls.add(ClusterUtils.mergeUrl(url, map));
                        }
                    }
                }
            } else { // assemble URL from register center's configuration
                // if protocols not injvm checkRegistry
                if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) {
                    checkRegistry();
                    List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
                    if (CollectionUtils.isNotEmpty(us)) {
                        for (URL u : us) {
                            URL monitorUrl = ConfigValidationUtils.loadMonitor(this, u);
                            if (monitorUrl != null) {
                                map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                            }
                            urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
                        }
                    }
                    if (urls.isEmpty()) {
                        throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config  to your spring config.");
                    }
                }
            }

            if (urls.size() == 1) {
                invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
            } else {
                List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
                URL registryURL = null;
                for (URL url : urls) {
                    invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
                    if (UrlUtils.isRegistry(url)) {
                        registryURL = url; // use last registry url
                    }
                }
                if (registryURL != null) { // registry url is available
                    // for multi-subscription scenario, use 'zone-aware' policy by default
                    URL u = registryURL.addParameterIfAbsent(CLUSTER_KEY, ZoneAwareCluster.NAME);
                    // The invoker wrap relation would be like: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker
                    invoker = CLUSTER.join(new StaticDirectory(u, invokers));
                } else { // not a registry url, must be direct invoke.
                    invoker = CLUSTER.join(new StaticDirectory(invokers));
                }
            }
        }

        if (shouldCheck() && !invoker.isAvailable()) {
            throw new IllegalStateException("Failed to check the status of the service "
                    + interfaceName
                    + ". No provider available for the service "
                    + (group == null ? "" : group + "/")
                    + interfaceName +
                    (version == null ? "" : ":" + version)
                    + " from the url "
                    + invoker.getUrl()
                    + " to the consumer "
                    + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
        }
        if (logger.isInfoEnabled()) {
            logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
        }
        /**
         * @since 2.7.0
         * ServiceData Store
         */
        String metadata = map.get(METADATA_KEY);
        WritableMetadataService metadataService = WritableMetadataService.getExtension(metadata == null ? DEFAULT_METADATA_STORAGE_TYPE : metadata);
        if (metadataService != null) {
            URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
            metadataService.publishServiceDefinition(consumerURL);
        }
        // create service proxy
        return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
    }

按照我们分析的这个逻辑,如果是本地服务,在inject时已经直接返回经过代理本地spring工厂实例,是不会来到这里的

      invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));

从注册中心引用服务
和生产者的的spi相同 经过,ProtocolFilterWrapper,QosProtocolWrapper,ProtocolListenerWrapper的扩展处理,最终进入
RegistryProtocol

org.apache.dubbo.registry.integration.RegistryProtocol#refer

 private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        // all attributes of REFER_KEY
        Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
        URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
        if (directory.isShouldRegister()) {
            directory.setRegisteredConsumerUrl(subscribeUrl);
            registry.register(directory.getRegisteredConsumerUrl());
        }
        directory.buildRouterChain(subscribeUrl);
        directory.subscribe(toSubscribeUrl(subscribeUrl));

        Invoker<T> invoker = cluster.join(directory);
        List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
        if (CollectionUtils.isEmpty(listeners)) {
            return invoker;
        }

        RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker, subscribeUrl);
        for (RegistryProtocolListener listener : listeners) {
            listener.onRefer(this, registryInvokerWrapper);
        }
        return registryInvokerWrapper;
    }

注册消费者服务到注册中心
在这里插入图片描述
订阅服务信息
org.apache.dubbo.registry.nacos.NacosRegistry#doSubscribe(org.apache.dubbo.common.URL, org.apache.dubbo.registry.NotifyListener)

@Override
    public void doSubscribe(final URL url, final NotifyListener listener) {
        Set<String> serviceNames = getServiceNames(url, listener);
        doSubscribe(url, listener, serviceNames);
    }

根据url生成provider的服务名称
org.apache.dubbo.registry.nacos.NacosRegistry#doSubscribe(org.apache.dubbo.common.URL, org.apache.dubbo.registry.NotifyListener, java.util.Set)

 private void doSubscribe(final URL url, final NotifyListener listener, final Set<String> serviceNames) {
        execute(namingService -> {
            List<Instance> instances = new LinkedList();
            for (String serviceName : serviceNames) {
                instances.addAll(namingService.getAllInstances(serviceName));
                subscribeEventListener(serviceName, url, listener);
            }
            notifySubscriber(url, listener, instances);
        });
    }

 private void subscribeEventListener(String serviceName, final URL url, final NotifyListener listener)
            throws NacosException {
        EventListener eventListener = event -> {
            if (event instanceof NamingEvent) {
                NamingEvent e = (NamingEvent) event;
                notifySubscriber(url, listener, e.getInstances());
            }
        };
        namingService.subscribe(serviceName, eventListener);
    }

首先从注册中心获取服务实例,并且订阅指定serviceName
如果订阅服务发生变动,执行 notifySubscriber(url, listener, e.getInstances());

第一次初始化,获取第一次instance之后也执行一次

notifySubscriber(url, listener, instances);
    private void notifySubscriber(URL url, NotifyListener listener, Collection<Instance> instances) {
        List<Instance> healthyInstances = new LinkedList<>(instances);
        if (healthyInstances.size() > 0) {
            // Healthy Instances
            filterHealthyInstances(healthyInstances);
        }
        List<URL> urls = toUrlWithEmpty(url, healthyInstances);
        NacosRegistry.this.notify(url, listener, urls);
    }

org.apache.dubbo.registry.support.AbstractRegistry#notify(org.apache.dubbo.common.URL, org.apache.dubbo.registry.NotifyListener, java.util.List)

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
        if (url == null) {
            throw new IllegalArgumentException("notify url == null");
        }
        if (listener == null) {
            throw new IllegalArgumentException("notify listener == null");
        }
        if ((CollectionUtils.isEmpty(urls))
                && !ANY_VALUE.equals(url.getServiceInterface())) {
            logger.warn("Ignore empty notify urls for subscribe url " + url);
            return;
        }
        if (logger.isInfoEnabled()) {
            logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
        }
        // keep every provider's category.
        Map<String, List<URL>> result = new HashMap<>();
        for (URL u : urls) {
            if (UrlUtils.isMatch(url, u)) {
                String category = u.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
                List<URL> categoryList = result.computeIfAbsent(category, k -> new ArrayList<>());
                categoryList.add(u);
            }
        }
        if (result.size() == 0) {
            return;
        }
        Map<String, List<URL>> categoryNotified = notified.computeIfAbsent(url, u -> new ConcurrentHashMap<>());
        for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
            String category = entry.getKey();
            List<URL> categoryList = entry.getValue();
            categoryNotified.put(category, categoryList);
            listener.notify(categoryList);
            // We will update our cache file after each notification.
            // When our Registry has a subscribe failure due to network jitter, we can return at least the existing cache URL.
            saveProperties(url);
        }
    }

根据nacos通知的url变更

生成invoker

                        invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);

这时使用dubbo协议引用 spi
又会经过这几个wrapper处处理
ProtocolFilterWrapper,QosProtocolWrapper,ProtocolListenerWrapper的扩展处理,最终进入
dubboProtocol创建invoker
着重看看ProtocolFilterWrapper
buildInvokerChain

 private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

        if (!filters.isEmpty()) {
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                last = new Invoker<T>() {

                    @Override
                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

                    @Override
                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    @Override
                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        Result asyncResult;
                        try {
                            asyncResult = filter.invoke(next, invocation);
                        } catch (Exception e) {
                            if (filter instanceof ListenableFilter) {
                                ListenableFilter listenableFilter = ((ListenableFilter) filter);
                                try {
                                    Filter.Listener listener = listenableFilter.listener(invocation);
                                    if (listener != null) {
                                        listener.onError(e, invoker, invocation);
                                    }
                                } finally {
                                    listenableFilter.removeListener(invocation);
                                }
                            } else if (filter instanceof Filter.Listener) {
                                Filter.Listener listener = (Filter.Listener) filter;
                                listener.onError(e, invoker, invocation);
                            }
                            throw e;
                        } finally {

                        }
                        return asyncResult.whenCompleteWithContext((r, t) -> {
                            if (filter instanceof ListenableFilter) {
                                ListenableFilter listenableFilter = ((ListenableFilter) filter);
                                Filter.Listener listener = listenableFilter.listener(invocation);
                                try {
                                    if (listener != null) {
                                        if (t == null) {
                                            listener.onResponse(r, invoker, invocation);
                                        } else {
                                            listener.onError(t, invoker, invocation);
                                        }
                                    }
                                } finally {
                                    listenableFilter.removeListener(invocation);
                                }
                            } else if (filter instanceof Filter.Listener) {
                                Filter.Listener listener = (Filter.Listener) filter;
                                if (t == null) {
                                    listener.onResponse(r, invoker, invocation);
                                } else {
                                    listener.onError(t, invoker, invocation);
                                }
                            }
                        });
                    }

                    @Override
                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }

        return last;
    }

原理和生产者相同,不再重复讲,不同的是,消费者自带了就三个filter,具体什么功能后面看
dubbo消费者篇_第4张图片

生成invoker之后
更新到RegistryDirectory中

  List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
            // pre-route and build cache, notice that route cache should build on original Invoker list.
            // toMergeMethodInvokerMap() will wrap some invokers having different groups, those wrapped invokers not should be routed.
            routerChain.setInvokers(newInvokers);
            this.invokers = multiGroup ? toMergeInvokerList(newInvokers) : newInvokers;
            this.urlInvokerMap = newUrlInvokerMap;

            try {
                destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
            } catch (Exception e) {
                logger.warn("destroyUnusedInvokers error. ", e);
            }

至此完成directory.subscribe(toSubscribeUrl(subscribeUrl));

继续到

    Invoker<T> invoker = cluster.join(directory);

仍然是spi处理

MockClusterWrapper

public class MockClusterWrapper implements Cluster {

    private Cluster cluster;

    public MockClusterWrapper(Cluster cluster) {
        this.cluster = cluster;
    }

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new MockClusterInvoker<T>(directory,
                this.cluster.join(directory));
    }

}

org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster#join
注意这个spi接口 cluster的实现有多种
dubbo消费者篇_第5张图片

上面的每个类都 对应一个 Cluster Invoker, 其中 MockClusterWrapper 是 扩展类,对应的 Cluster Invoker 为 MockClusterInvoker,其余都是容错策略,其作用如下:

MockClusterInvoker : MockClusterWrapper 对应的 Cluster Invoker。完成了本地Mock 的功能。这里需要注意由于MockClusterWrapper 是 扩展类,所以 MockClusterInvoker 在最外层,即当服务调用时的顺序为 : MockClusterInvoker#invoker -> XxxClusterInvoker#invoker。关于 MockClusterInvoker 的实现,详参: Dubbo衍生篇⑦ :本地Mock 和服务降级

Failover Cluster:失败重试。当服务消费方调用服务提供者失败后,会自动切换到其他服务提供者服务器进行重试,这通常用于读操作或者具有幂等的写操作。需要注意的是,重试会带来更长延迟。可以通过retries="2"来设置重试次数(不含第1次)。 可以使用<dubbo:reference retries=2/>来进行接口级别配置的重试次数,当服务消费方调用服务失败后,此例子会再重试两次,也就是说最多会做3次调用,这里的配置对该接口的所有方法生效。

Failfast Cluster:快速失败。当服务消费方调用服务提供者失败后,立即报错,也就是只调用一次。通常,这种模式用于非幂等性的写操作。

Failsafe Cluster:安全失败。当服务消费者调用服务出现异常时,直接忽略异常。这种模式通常用于写入审计日志等操作。

Failback Cluster:失败自动恢复。当服务消费端调用服务出现异常后,在后台记录失败的请求,并按照一定的策略后期再进行重试。这种模式通常用于消息通知操作。

Forking Cluster:并行调用。当消费方调用一个接口方法后,Dubbo Client会并行调用多个服务提供者的服务,只要其中有一个成功即返回。这种模式通常用于实时性要求较高的读操作,但需要浪费更多服务资源。如下代码可通过forks="4"来设置最大并行数:

Available Cluster :可用集群调用器。前面提到doInvoke的入参有远程服务提供者的列表invokers。AvailableClusterInvoker遍历invokers,当遍历到第一个服务可用的提供者时,便访问该提供者,成功返回结果,如果访问时失败抛出异常终止遍历。

Mergeable Cluster :该集群容错策略是对多个服务端返回结果合并,在消费者调多个分组下的同一个服务时会指定使用该 Cluster 来合并 多个分组执行的结果。

Broadcast Cluster:广播调用。当消费者调用一个接口方法后,Dubbo Client会逐个调用所有服务提供者,任意一台服务器调用异常则这次调用就标志失败。这种模式通常用于通知所有提供者更新缓存或日志等本地资源信息。

RegistryAware Cluster :当消费者引用多个注册中心时会指定使用该策略。默认会首先引用默认的注册中心服务,如果默认注册中心服务没有提供该服务,则会从其他注册中心中寻找该服务。

Dubbo本身提供了丰富的集群容错模式,但是如果你有定制化需求,可以根据Dubbo提供的扩展接口Cluster进行定制。

在这里插入图片描述

可通过注解进行指定,对应配置

mock=org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper
failover=org.apache.dubbo.rpc.cluster.support.FailoverCluster
failfast=org.apache.dubbo.rpc.cluster.support.FailfastCluster
failsafe=org.apache.dubbo.rpc.cluster.support.FailsafeCluster
failback=org.apache.dubbo.rpc.cluster.support.FailbackCluster
forking=org.apache.dubbo.rpc.cluster.support.ForkingCluster
available=org.apache.dubbo.rpc.cluster.support.AvailableCluster
mergeable=org.apache.dubbo.rpc.cluster.support.MergeableCluster
broadcast=org.apache.dubbo.rpc.cluster.support.BroadcastCluster
registryaware=org.apache.dubbo.rpc.cluster.support.RegistryAwareCluster

默认使用failover失败重试策略

 @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return buildClusterInterceptors(doJoin(directory), directory.getUrl().getParameter(REFERENCE_INTERCEPTOR_KEY));
    }
  private <T> Invoker<T> buildClusterInterceptors(AbstractClusterInvoker<T> clusterInvoker, String key) {
        AbstractClusterInvoker<T> last = clusterInvoker;
        List<ClusterInterceptor> interceptors = ExtensionLoader.getExtensionLoader(ClusterInterceptor.class).getActivateExtension(clusterInvoker.getUrl(), key);

        if (!interceptors.isEmpty()) {
            for (int i = interceptors.size() - 1; i >= 0; i--) {
                final ClusterInterceptor interceptor = interceptors.get(i);
                final AbstractClusterInvoker<T> next = last;
                last = new InterceptorInvokerNode<>(clusterInvoker, interceptor, next);
            }
        }
        return last;
    }

看下这个InterceptorInvokerNode

  @Override
        public Result invoke(Invocation invocation) throws RpcException {
            Result asyncResult;
            try {
                interceptor.before(next, invocation);
                asyncResult = interceptor.intercept(next, invocation);
            } catch (Exception e) {
                // onError callback
                if (interceptor instanceof ClusterInterceptor.Listener) {
                    ClusterInterceptor.Listener listener = (ClusterInterceptor.Listener) interceptor;
                    listener.onError(e, clusterInvoker, invocation);
                }
                throw e;
            } finally {
                interceptor.after(next, invocation);
            }
            return asyncResult.whenCompleteWithContext((r, t) -> {
                // onResponse callback
                if (interceptor instanceof ClusterInterceptor.Listener) {
                    ClusterInterceptor.Listener listener = (ClusterInterceptor.Listener) interceptor;
                    if (t == null) {
                        listener.onMessage(r, clusterInvoker, invocation);
                    } else {
                        listener.onError(t, clusterInvoker, invocation);
                    }
                }
            });
        }
    default Result intercept(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) throws RpcException {
        return clusterInvoker.invoke(invocation);
    }

通过不断使用InterceptorInvokerNode包裹的方式,形成链式filter
默认只有一个ConsumerContextClusterInterceptor
最终 RegistryProtocol.refer方法返回的就是 MockClusterInvoker包裹的directory和invoker,默认被包裹的为FailoverClusterInvoker,

 @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new MockClusterInvoker<T>(directory,
                this.cluster.join(directory));
    }

MockClusterInvoker就是我们在调用接口实际进入的invoker

   public MockClusterInvoker(Directory<T> directory, Invoker<T> invoker) {
        this.directory = directory;
        this.invoker = invoker;
    }

生成invoker之后。

     String metadata = map.get(METADATA_KEY);
        WritableMetadataService metadataService = WritableMetadataService.getExtension(metadata == null ? DEFAULT_METADATA_STORAGE_TYPE : metadata);
        if (metadataService != null) {
            URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
            metadataService.publishServiceDefinition(consumerURL);
        }
        // create service proxy
        return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));

上报元数据中心,生成代理类。使用invoker代理目标接口

dubbo admin管理控制台和dubbo monitor监控中心介绍

1.dubbo admin管理控制台

根据字面意思很好理解,就是管理dubbo的,可以用这个工具进行服务治理,包括服务注册,服务降级,路由规则,访问控制,动态配置,权重调节,负载均衡,服务负责人等。当然也有服务监控查看的功能,管理控制台为内部裁剪版本,开源部分主要包含:路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡,等管理功能。
安装方式也很简单:http://dubbo.apache.org/zh-cn/docs/admin/install/admin-console.html
2.dubbo-monitor-simple监控中心
从字面意思也很好理解,就是监控dubbo服务的,可以查看服务提供方,消费方,调用次数等等。

注意:
Simple Monitor 挂掉不会影响到 Consumer 和 Provider 之间的调用,所以用于生产环境不会有风险。
Simple Monitor 采用磁盘存储统计信息,请注意安装机器的磁盘限制,如果要集群,建议用mount共享磁盘。
charts 目录必须放在 jetty.directory 下,否则页面上访问不了。
安装方式也很简单:http://dubbo.apache.org/zh-cn/docs/admin/install/simple-monitor-center.html

dubbo 元数据中心

元数据中心介绍

服务治理中的元数据(Metadata)指的是服务分组、服务版本、服务名、方法列表、方法参数列表、超时时间等,这些信息将会存储在元数据中心之中。与元数据平起平坐的一个概念是服务的注册信息,即:服务分组、服务版本、服务名、地址列表等,这些信息将会存储在注册中心中。稍微一对比可以发现,元数据中心和注册中心存储了一部分共同的服务信息,例如服务名。两者也有差异性,元数据中心还会存储方法列表即参数列表,注册中心存储了服务地址。上述的概述,体现出了元数据中心和注册中心在服务治理过程中,担任着不同的角色。为了有一个直观的对比,我整理出了下面的表格:
dubbo消费者篇_第6张图片

职责

在 Dubbo 2.7 版本之前,并没有元数据中心的概念,那时候注册信息和元数据都耦合在一起。Dubbo Provider 的服务配置有接近 30 个配置项,排除一部分注册中心服务治理需要的参数,很大一部分配置项仅仅是 Provider 自己使用,不需要透传给消费者;Dubbo Consumer 也有 20 多个配置项。在注册中心之中,服务消费者列表中只需要关注 application,version,group,ip,dubbo 版本等少量配置。这部分数据不需要进入注册中心,而只需要以 key-value 形式持久化存储在元数据中心即可。从职责来看,将不同职责的数据存储在对应的组件中,会使得逻辑更加清晰。

变化频繁度

注册信息和元数据耦合在一起会导致注册中心数据量的膨胀,进而增大注册中心的网络开销,直接造成了服务地址推送慢等负面影响。服务上下线会随时发生,变化的其实是注册信息,元数据是相对不变的。

数据量

由于元数据包含了服务的方法列表以及参数列表,这部分数据会导致元数据要比注册信息大很多。注册信息被设计得精简会直接直接影响到服务推送的 SLA。

数据交互/存储模型

注册中心采用的是 PubSub 模型,这属于大家的共识,所以注册中心组件的选型一般都会要求其有 notify 的机制。而元数据中心并没有 notify 的诉求,一般只需要组件能够提供 key-value 的存储结构即可。

主要使用场景

在服务治理中,注册中心充当了通讯录的职责,在复杂的分布式场景下,让消费者能找到提供者。而元数据中心存储的元数据,主要适用于服务测试、服务 MOCK 等场景,这些场景都对方法列表、参数列表有所诉求。在下面的小节中,我也会对使用场景进行更加详细的介绍。

可用性要求

注册中心宕机或者网络不通会直接影响到服务的可用性,它影响了服务调用的主路径。但一般而言,元数据中心出现问题,不会影响到服务调用,它提供的能力是可被降级的。这也阐释了一点,为什么很多用户在 Dubbo 2.7 中没有配置元数据中心,也没有影响到正常的使用。元数据中心在服务治理中扮演的是锦上添花的一个角色。在组件选型时,我们一般也会对注册中心的可用性要求比较高,元数据中心则可以放宽要求。

元数据中心的价值

小孩子才分对错,成年人只看利弊。额外引入一个元数据中心,必然带来运维成本、理解成本、迁移成本等问题,那么它具备怎样的价值,来说服大家选择它呢?上面我们介绍元数据中心时已经提到了服务测试、服务 MOCK 等场景,这一节我们重点探讨一下元数据中心的价值。

降低地址推送的时延

由于注册中心采用的是 PubSub 模型,数据量的大小会直接影响到服务地址推送时间,不知道你有没有遇到过 No provider available 的报错呢?明明提供者已经启动了,但由于注册中心推送慢会导致很多问题,一方面会影响到服务的可用性,一方面也会增加排查问题的难度。
在一次杭州 Dubbo Meetup 中,网易考拉分享了他们对 Zookeeper 的改造,根源就是
推送量大 -> 存储数据量大 -> 网络传输量大 -> 延迟严重
这一实际案例佐证了元数据改造并不是凭空产生的需求,而是切实解决了一个痛点。

服务测试 & 服务 MOCK

在 Dubbo 2.7 之前,虽然注册中心耦合存储了不少本应属于元数据的数据,但也漏掉了一部分元数据,例如服务的方法列表,参数列表。这些是服务测试和服务 MOCK 必备的数据,想要使用这些能力,就必须引入元数据中心。例如开源的 Dubbo Admin 就实现了服务测试功能,用户可以在控制台上对已经发布的服务提供者进行功能测试。可能你之前有过这样的疑惑:为什么只有 Dubbo 2.7 才支持了服务测试呢?啊哈,原因就是 Dubbo 2.7 才有了元数据中心的概念。当然,服务 MOCK 也是如此。

Dubbo 配置元数据中心

目前 Dubbo 最新的版本为 2.7.4,目前支持的几种元数据中心可以从源码中得知(官方文档尚未更新):
支持 consul、etcd、nacos、redis、zookeeper 这五种组件。
配置方式如下:

dubbo.metadata-report.address=nacos://127.0.0.1:8848

元数据存储格式剖析

前面我们介绍了元数据中心的由来以及价值,还是飘在天上的概念,这一节将会让概念落地。元数据是以怎么样一个格式存储的呢?
以 DemoService 服务为例:

<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" executes="4500" retries="7" owner="kirito"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181" />

首先观察在 Dubbo 2.6.x 中,注册中心如何存储这个服务的信息:

dubbo://30.5.120.185:20880/com.alibaba.dubbo.demo.DemoService?
anyhost=true&
application=demo-provider&
interface=com.alibaba.dubbo.demo.DemoService&
methods=sayHello&
bean.name=com.alibaba.dubbo.demo.DemoService&
dubbo=2.0.2&executes=4500&
generic=false&owner=kirito&
pid=84228&retries=7&side=provider&timestamp=1552965771067

例如 bean.name 和 owner 这些属性,肯定是没必要注册上来的。
接着,我们在 Dubbo 2.7 中使用最佳实践,为 registry 配置 simplified=true:

<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" executes="4500" retries="7" owner="kirito"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181" simplified="true" />
<dubbo:metadata-report address="nacos://127.0.0.1:8848"/>
dubbo://30.5.120.185:20880/org.apache.dubbo.demo.api.DemoService?
application=demo-provider&
dubbo=2.0.2&
release=2.7.0&
timestamp=1552975501873

被精简省略的数据不代表没有用了,而是转移到了元数据中心之中,我们观察一下此时元数据中心中的数据:
dubbo消费者篇_第7张图片

总结

元数据中心其实就是存储生产者或者消费者存放不必要的一些配置选项的地方, 同样可以用于服务治理,但是却减轻了注册中心的压力

请求执行过程

首先进入jdk动态代理

org.apache.dubbo.rpc.proxy.InvokerInvocationHandler#invoke

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(invoker, args);
        }
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (parameterTypes.length == 0) {
            if ("toString".equals(methodName)) {
                return invoker.toString();
            } else if ("$destroy".equals(methodName)) {
                invoker.destroy();
                return null;
            } else if ("hashCode".equals(methodName)) {
                return invoker.hashCode();
            }
        } else if (parameterTypes.length == 1 && "equals".equals(methodName)) {
            return invoker.equals(args[0]);
        }
        RpcInvocation rpcInvocation = new RpcInvocation(method, invoker.getInterface().getName(), args);
        String serviceKey = invoker.getUrl().getServiceKey();
        rpcInvocation.setTargetServiceUniqueName(serviceKey);
      
        if (consumerModel != null) {
            rpcInvocation.put(Constants.CONSUMER_MODEL, consumerModel);
            rpcInvocation.put(Constants.METHOD_MODEL, consumerModel.getMethodModel(method));
        }

        return invoker.invoke(rpcInvocation).recreate();
    }

如果是toString,$destroy,hashCode,equals这种方法直接用invoker
将方法,接口,参数,serviceKey组装进RpcInvocation
调用invoker
org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#invoke

@Override
    public Result invoke(Invocation invocation) throws RpcException {
        Result result = null;

        String value = getUrl().getMethodParameter(invocation.getMethodName(), MOCK_KEY, Boolean.FALSE.toString()).trim();
        if (value.length() == 0 || "false".equalsIgnoreCase(value)) {
            //no mock
            result = this.invoker.invoke(invocation);
        } else if (value.startsWith("force")) {
            if (logger.isWarnEnabled()) {
                logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + getUrl());
            }
            //force:direct mock
            result = doMockInvoke(invocation, null);
        } else {
            //fail-mock
            try {
                result = this.invoker.invoke(invocation);

                //fix:#4585
                if(result.getException() != null && result.getException() instanceof RpcException){
                    RpcException rpcException= (RpcException)result.getException();
                    if(rpcException.isBiz()){
                        throw  rpcException;
                    }else {
                        result = doMockInvoke(invocation, rpcException);
                    }
                }

            } catch (RpcException e) {
                if (e.isBiz()) {
                    throw e;
                }

                if (logger.isWarnEnabled()) {
                    logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + getUrl(), e);
                }
                result = doMockInvoke(invocation, e);
            }
        }
        return result;
    }

可以看出来,如果没有mock直接调用服务,如果配置force:mock,直接调用本地mock,否则就是在调用失败后,调用mock服务。

Dubbo——Mock

在开发自测,联调过程中,经常碰到一些下游服务调用不通的场景,这个时候我们如何不依赖于下游系统,就业务系统独立完成自测?
dubbo自身是支持mock服务的,在reference标签里,有一个参数mock,该参数有四个值,false,default,true,或者Mock类的类名。分别代表如下含义:

false,不调用mock服务。
● true,当服务调用失败时,使用mock服务。
● default,当服务调用失败时,使用mock服务。
● force,强制使用Mock服务(不管服务能否调用成功)(使用xml配置不生效,使用ReferenceConfigAPI可以生效)

mock=“true”
示例:

<dubbo:reference id="demoService" mock="true" interface="org.apache.dubbo.demo.DemoService"/>

<dubbo:reference id="demoService" mock="fail:true" interface="org.apache.dubbo.demo.DemoService"/>

指定 mock 策略为布尔类型,且为 true ,此时需要消费端提供服务接口的 mock 实现类,该类的包名需要与服务接口一致,且类名格式为 接口名 + Mock, 即 org.apache.dubbo.demo.DemoServiceMock, 当调用远程服务失败后, 就会执行 DemoServiceMock的 sayHello 方法, 如果不提供此 mock 类,dubbo 消费端会启动失败。

错误信息如下:

Caused by: java.lang.ClassNotFoundException: org.apache.dubbo.demo.DemoServiceMock

一、fail 策略

1.2 mock=“具体的mock实现类”
<dubbo:reference id="demoService" mock="org.apache.dubbo.demo.consumer.mock.DemoServiceMock2" interface="org.apache.dubbo.demo.DemoService"/>

<dubbo:reference id="demoService" mock="fail:org.apache.dubbo.demo.consumer.mock.DemoServiceMock2" interface="org.apache.dubbo.demo.DemoService"/>

指定 mock 策略为具体的 mock 实现类, 当调用远程服务失败时, 就会执行 mock 实现类的 sayHello 方法.

1.3 mock=“抛出自定义异常”
<dubbo:reference id="demoService" mock="throw org.apache.dubbo.demo.consumer.exception.CustomException" interface="org.apache.dubbo.demo.DemoService"/>

<dubbo:reference id="demoService" mock="fail:throw org.apache.dubbo.demo.consumer.exception.CustomException" interface="org.apache.dubbo.demo.DemoService"/>

1.4 mock=“返回 mock 数据”
<dubbo:reference id="demoService" mock="return haha" interface="org.apache.dubbo.demo.DemoService"/>

<dubbo:reference id="demoService" mock="fail:return haha" interface="org.apache.dubbo.demo.DemoService"/>

二、force 策略

当服务消费者调用服务提供者时,会直接执行 mock 配置的策略,不会进行服务调用。
具体的策略跟 fail 策略一致, 此处不再详细说明。
总结
Dubbo 的 mock 的策略总共分为两大类:

一是当服务调用失败时,去进行 mock 调用;

二是绕过服务调用,直接进行 mock 调用。

而具体的 mock 调用策略又分别 4 种:

1、返回 mock 数据

2、抛出自定义异常

3、执行默认的 Mock 实现类

4、执行指定的 Mock 实现类

调用服务

  result = this.invoker.invoke(invocation);
  @Override
        public Result invoke(Invocation invocation) throws RpcException {
            Result asyncResult;
            try {
                interceptor.before(next, invocation);
                asyncResult = interceptor.intercept(next, invocation);
            } catch (Exception e) {
                // onError callback
                if (interceptor instanceof ClusterInterceptor.Listener) {
                    ClusterInterceptor.Listener listener = (ClusterInterceptor.Listener) interceptor;
                    listener.onError(e, clusterInvoker, invocation);
                }
                throw e;
            } finally {
                interceptor.after(next, invocation);
            }
            return asyncResult.whenCompleteWithContext((r, t) -> {
                // onResponse callback
                if (interceptor instanceof ClusterInterceptor.Listener) {
                    ClusterInterceptor.Listener listener = (ClusterInterceptor.Listener) interceptor;
                    if (t == null) {
                        listener.onMessage(r, clusterInvoker, invocation);
                    } else {
                        listener.onError(t, clusterInvoker, invocation);
                    }
                }
            });
        }

根据前面我们知道InterceptorInvokerNyigeode是继承invoker实现,链式连接器的功能。
一共只有一个拦截器
ConsumerContextClusterInterceptor

  @Override
    public void before(AbstractClusterInvoker<?> invoker, Invocation invocation) {
        RpcContext.getContext()
                .setInvocation(invocation)
                .setLocalAddress(NetUtils.getLocalHost(), 0);
        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(invoker);
        }
        RpcContext.removeServerContext();
    }

设置了调用的invocation和 本机ip
dubbo消费者篇_第8张图片

 default Result intercept(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) throws RpcException {
        return clusterInvoker.invoke(invocation);
    }

默认使用FailoverCluster,即调用失败,再次尝试从别的服务器上调用

集群容错

在Dubbo中有5种容错模式分别是:

Failover Cluster

失败自动切换:当我们在调用Dubbo服务时出现失败,容错策略会重试其它服务器 。

使用场景:对于一些必达性要求高的服务调用,但是服务提供方要求做幂等处理 。

Failfast Cluster

快速失败:只发起一次调用,如果调用Dubbo服务失败立即报错。

使用场景:通常用于非幂等性的写操作,比如新增记录。

Failsafe Cluster

失败安全:当调用Dubbo服务出现异常时,直接忽略。

使用场景:通常用于运行数据丢失常见,例如:日志记录等操作。

Failback Cluster

失败自动恢复:当调用Dubbo服务失败,后台记录失败请求并定时重发。

使用场景:通常用于必达通知场景,例如:消息通知操作。

Forking Cluster

集群并行:并行调用多个Dubbo服务,只要其中有一个成功即返回。

使用场景:通常用于从多个源获取相同数据,以获取最快的响应速度,例如:同时从多个备库查询数据。

Broadcast Cluster

集群广播:循环调用所有Dubbo服务提供者,任意一台报错则报错。

使用场景:通用用于向多个实例通知消息,例如:更新集群中所有应用缓存或日志。
下面我们看默认的Failover Cluster是怎么实现的

  @Override
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();

        // binding attachments into invocation.
        Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
        }

        List<Invoker<T>> invokers = list(invocation);
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        return doInvoke(invocation, invokers, loadbalance);
    }

将RpcContext中的数据放到invocation用于数据透传
org.apache.dubbo.registry.integration.RegistryDirectory#doList

  @Override
    public List<Invoker<T>> doList(Invocation invocation) {
        if (forbidden) {
            // 1. No service provider 2. Service providers are disabled
            throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry " +
                    getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " +
                    NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() +
                    ", please check status of providers(disabled, not registered or in blacklist).");
        }

        if (multiGroup) {
            return this.invokers == null ? Collections.emptyList() : this.invokers;
        }

        List<Invoker<T>> invokers = null;
        try {
            // Get invokers from cache, only runtime routers will be executed.
            invokers = routerChain.route(getConsumerUrl(), invocation);
        } catch (Throwable t) {
            logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
        }

        return invokers == null ? Collections.emptyList() : invokers;
    }

forbidden属性的含义
看刷新invoker方法

  private void refreshInvoker(List<URL> invokerUrls) {
        Assert.notNull(invokerUrls, "invokerUrls should not be null");

        if (invokerUrls.size() == 1
                && invokerUrls.get(0) != null
                && EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
            this.forbidden = true; // Forbid to access
            this.invokers = Collections.emptyList();
            routerChain.setInvokers(this.invokers);
            destroyAllInvokers(); // Close all invokers
        } else {
            this.forbidden = false; // Allow to access
           ...........................................

当注册中心通知变化被消费者感知到之后,进行刷新invoker如果,注册中心中没有服务地址,那么forbidden为true,此时执行,直接返回
org.apache.dubbo.rpc.cluster.RouterChain#route

 public List<Invoker<T>> route(URL url, Invocation invocation) {
        List<Invoker<T>> finalInvokers = invokers;
        for (Router router : routers) {
            finalInvokers = router.route(finalInvokers, url, invocation);
        }
        return finalInvokers;
    }

先看routers是从哪里得来的
构造

路由

private RouterChain(URL url) {
        List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
                .getActivateExtension(url, "router");

        List<Router> routers = extensionFactories.stream()
                .map(factory -> factory.getRouter(url))
                .collect(Collectors.toList());

        initWithRouters(routers);
    }

dubbo消费者篇_第9张图片

自带四个带有@Active注解接口
分别看看这上次个route的功能分别是什么
org.apache.dubbo.registry.integration.RegistryDirectory#refreshInvoker

.................
    
    
            List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
            // pre-route and build cache, notice that route cache should build on original Invoker list.
            // toMergeMethodInvokerMap() will wrap some invokers having different groups, those wrapped invokers not should be routed.
            routerChain.setInvokers(newInvokers);
...................

org.apache.dubbo.rpc.cluster.RouterChain#setInvokers

public void setInvokers(List<Invoker<T>> invokers) {
        this.invokers = (invokers == null ? Collections.emptyList() : invokers);
        routers.forEach(router -> router.notify(this.invokers));
    }

其中只有TagRouter重写了notify方法

@Override
    public <T> void notify(List<Invoker<T>> invokers) {
        if (CollectionUtils.isEmpty(invokers)) {
            return;
        }

        Invoker<T> invoker = invokers.get(0);
        URL url = invoker.getUrl();
        String providerApplication = url.getParameter(CommonConstants.REMOTE_APPLICATION_KEY);

        if (StringUtils.isEmpty(providerApplication)) {
            logger.error("TagRouter must getConfig from or subscribe to a specific application, but the application " +
                    "in this TagRouter is not specified.");
            return;
        }

        synchronized (this) {
            if (!providerApplication.equals(application)) {
                if (!StringUtils.isEmpty(application)) {
                    ruleRepository.removeListener(application + RULE_SUFFIX, this);
                }
                String key = providerApplication + RULE_SUFFIX;
                ruleRepository.addListener(key, this);
                application = providerApplication;
                String rawRule = ruleRepository.getRule(key, DynamicConfiguration.DEFAULT_GROUP);
                if (StringUtils.isNotEmpty(rawRule)) {
                    this.process(new ConfigChangedEvent(key, DynamicConfiguration.DEFAULT_GROUP, rawRule));
                }
            }
        }
    }

MockInvokersSelector

mock路由是在请求有隐式参数invocation.need.mock=ture的情况下生效,获取mock协议的Invoker。用于服务降级。

  @Override
    public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers,
                                      URL url, final Invocation invocation) throws RpcException {
        if (CollectionUtils.isEmpty(invokers)) {
            return invokers;
        }

        if (invocation.getObjectAttachments() == null) {
            return getNormalInvokers(invokers);
        } else {
            String value = (String) invocation.getObjectAttachments().get(INVOCATION_NEED_MOCK);
            if (value == null) {
                return getNormalInvokers(invokers);
            } else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
                return getMockedInvokers(invokers);
            }
        }
        return invokers;
    }
   private <T> List<Invoker<T>> getMockedInvokers(final List<Invoker<T>> invokers) {
        if (!hasMockProviders(invokers)) {
            return null;
        }
        List<Invoker<T>> sInvokers = new ArrayList<Invoker<T>>(1);
        for (Invoker<T> invoker : invokers) {
            if (invoker.getUrl().getProtocol().equals(MOCK_PROTOCOL)) {
                sInvokers.add(invoker);
            }
        }
        return sInvokers;
    }

invokers为生产者从注册中心获取到的invoker和本地的mockerInvoker
逻辑也很简单有隐式参数invocation.need.mock=ture就挑选mockInvoker
4.路由的具体实现

ConditionRouter实现

条件路由
条件路由可以编写一些自定义路由规则实现一些服务治理的需求比如黑白名单、读写分离等。条件路由可以在接口级别和消费者应用级别创建规则。

创建条件路由规则需要填写 interface、version、group 信息(version、group无则不填),下图展示一个简单的黑名单配置,=> 左边代表消费者匹配条件,右边代表provider匹配条件(无代表没有匹配)具体规则详情参考官网(具体请见文末相关链接)。所以配置的含义为consumer ip 为 192.168.1.3 的消费者没有 provider 提供服务。
dubbo消费者篇_第10张图片

根据Url的键rule获取对应的规则字符串,以=>为界,把规则分成两段,前面为whenRule消费者匹配条件,后面为thenRule是提供者地址列表的过滤条件。具体是根据正则规则进行匹配,有点麻烦,就不分析记录。
提供者与消费者部署在同集群内,本机只访问本机的服务****使用方式**
下面我们简单的讨论下条件路由使用方式:
条件路由

  • 接口服务粒度
# demo-consumer1 的消费者只能消费所有端口为20880的服务实例
# demo-consumer2 的消费者只能消费所有端口为20881的服务实例
---
scope: application #应用粒度
force: true
runtime: true
enabled: true
key: demo-provider 
conditions:
  - application=demo-consumer1 => address=*:20880
  - application=demo-consumer2 => address=*:20881

  • 应用粒度
# BookFacade 的 queryAll 方法只能消费所有端口为20880的服务实例
# BookFacade 的 queryByName 方法只能消费所有端口为20881的服务实例
---
scope: service #服务粒度
force: true
runtime: true
enabled: true
key: com.muke.dubbocourse.common.api.BookFacade
conditions:
  - method=queryAll => address=*:20880
  - method=queryByName => address=*:20881

字段说明:dubbo消费者篇_第11张图片

对应的是dubbo-admin上的配置
dubbo消费者篇_第12张图片

Conditions规则体

格式:

=> 之前的为消费者匹配条件,所有参数和消费者的 URL 进行对比,当消费者满足匹配条件时,对该消费者执行后面的过滤规则。
=> 之后为提供者地址列表的过滤条件,所有参数和提供者的 URL 进行对比,消费者最终只拿到过滤后的地址列表。
如果匹配条件为空,表示对所有消费方应用,如:=> host != 192.168.53.11
如果过滤条件为空,表示禁止访问,如:host = 192.168.53.10 =>
表达式:

参数支持:

服务调用信息,如:method, argument 等,暂不支持参数路由
URL 本身的字段,如:protocol, host, port 等
以及 URL 上的所有参数,如:application, organization 等
条件支持:

等号 = 表示"匹配",如:host = 192.168.53.10
不等号 != 表示"不匹配",如:host != 192.168.53.10
值支持:

以逗号 , 分隔多个值,如:host != 192.168.53.10,192.168.53.11
以星号 * 结尾,表示通配,如:host != 10.20.*
以美元符 $ 开头,表示引用消费者参数,如:host = $host
Tips:conditions部分是规则的主体,由1到任意多条规则组成,下面我们就每个规则的配置语法做详细说明:
** 使用场景**
从上面的简单介绍我们可以大致了解到,当我们消费对访问服务提供者时我们可以通过一定的规则对服务提供者列表进行过滤。那下面我们列举下工作中常使用的场景:
黑名单: 比如我们需要禁止某些服务消费者消费服务

host = 192.168.53.10,192.168.53.11 =>

上面配置表示禁止192.168.53.10、192.168.53.11消费者访问服务提供者。

=> host = 192.168.53.1*,192.168.53.2*

上面配置表示只能放192.168.53.1*、192.168.53.2 ip 地址开头的服务提供者。
读写分离:读取数据和写入数据操作分开

method = find*,list*,get*,is* => host = 192.168.53.10,192.168.53.11,192.168.53.12
method != find*,list*,get*,is* => host = 192.168.20.97,192.168.53.21

上面配置表示以find*,list*,get*,is方法命名开始的方法只能访问192.168.53.10,192.168.53.11,192.168.53.12服务提供者,而不是find,list*,get*,is*方法命名开始的方法只能访问192.168.20.97,192.168.53.21服务提供者。

=> host = $host

上面配置表示所有消费者只能访问集群内的服务。
实现原理
dubbo-admin发布后,会生成配置。
dubbo消费者篇_第13张图片

ServiceRouter
dubbo消费者篇_第14张图片

在监听到nacos服务器上的route变化之后触发,添加规则。在执行远程调用时根据规则匹配

  @Override
    public synchronized void process(ConfigChangedEvent event) {
        if (logger.isInfoEnabled()) {
            logger.info("Notification of condition rule, change type is: " + event.getChangeType() +
                    ", raw rule is:\n " + event.getContent());
        }

        if (event.getChangeType().equals(ConfigChangeType.DELETED)) {
            routerRule = null;
            conditionRouters = Collections.emptyList();
        } else {
            try {
                routerRule = ConditionRuleParser.parse(event.getContent());
                generateConditions(routerRule);
            } catch (Exception e) {
                logger.error("Failed to parse the raw condition rule and it will not take effect, please check " +
                        "if the condition rule matches with the template, the raw rule is:\n " + event.getContent(), e);
            }
        }
    }

触发时机
nacoshttp长轮询
com.alibaba.nacos.client.config.impl.ClientWorker.LongPollingRunnable

  class LongPollingRunnable implements Runnable {
        
        private final int taskId;
        
        public LongPollingRunnable(int taskId) {
            this.taskId = taskId;
        }
        
        @Override
        public void run() {
            
            List<CacheData> cacheDatas = new ArrayList<CacheData>();
            List<String> inInitializingCacheList = new ArrayList<String>();
            try {
                // check failover config
                for (CacheData cacheData : cacheMap.get().values()) {
                    if (cacheData.getTaskId() == taskId) {
                        cacheDatas.add(cacheData);
                        try {
                            checkLocalConfig(cacheData);
                            if (cacheData.isUseLocalConfigInfo()) {
                                cacheData.checkListenerMd5();
                            }
                        } catch (Exception e) {
                            LOGGER.error("get local config info error", e);
                        }
                    }
                }
                
                // check server config
                List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
                if (!CollectionUtils.isEmpty(changedGroupKeys)) {
                    LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
                }
                
                for (String groupKey : changedGroupKeys) {
                    String[] key = GroupKey.parseKey(groupKey);
                    String dataId = key[0];
                    String group = key[1];
                    String tenant = null;
                    if (key.length == 3) {
                        tenant = key[2];
                    }
                    try {
                        String[] ct = getServerConfig(dataId, group, tenant, 3000L);
                        CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
                        cache.setContent(ct[0]);
                        if (null != ct[1]) {
                            cache.setType(ct[1]);
                        }
                        LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}",
                                agent.getName(), dataId, group, tenant, cache.getMd5(),
                                ContentUtils.truncateContent(ct[0]), ct[1]);
                    } catch (NacosException ioe) {
                        String message = String
                                .format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",
                                        agent.getName(), dataId, group, tenant);
                        LOGGER.error(message, ioe);
                    }
                }
                for (CacheData cacheData : cacheDatas) {
                    if (!cacheData.isInitializing() || inInitializingCacheList
                            .contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
                        cacheData.checkListenerMd5();
                        cacheData.setInitializing(false);
                    }
                }
                inInitializingCacheList.clear();
                
                executorService.execute(this);
                
            } catch (Throwable e) {
                
                // If the rotation training task is abnormal, the next execution time of the task will be punished
                LOGGER.error("longPolling error : ", e);
                executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
            }
        }
    }

根据cacheMap中的配置,轮询nacos服务端请求配置
在ServiceRouter构造过程中
dubbo消费者篇_第15张图片

会将制动配置名称加入cachemap
检测到配置变大触发
org.apache.dubbo.configcenter.support.nacos.NacosDynamicConfiguration.NacosConfigListener#innerReceive

 @Override
        public void innerReceive(String dataId, String group, String configInfo) {
            String oldValue = cacheData.get(dataId);
            ConfigChangedEvent event = new ConfigChangedEvent(dataId, group, configInfo, getChangeType(configInfo, oldValue));
            if (configInfo == null) {
                cacheData.remove(dataId);
            } else {
                cacheData.put(dataId, configInfo);
            }
            listeners.forEach(listener -> listener.process(event));
        }

当收到nacos服务器配置变动之后,通知对应listener.process触发condition
org.apache.dubbo.rpc.cluster.router.condition.config.ListenableRouter#route

@Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
        if (CollectionUtils.isEmpty(invokers) || conditionRouters.size() == 0) {
            return invokers;
        }

        // We will check enabled status inside each router.
        for (Router router : conditionRouters) {
            invokers = router.route(invokers, url, invocation);
        }

        return invokers;
    }
   @Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
            throws RpcException {
        if (!enabled) {
            return invokers;
        }

        if (CollectionUtils.isEmpty(invokers)) {
            return invokers;
        }
        try {
            if (!matchWhen(url, invocation)) {
                return invokers;
            }
            List<Invoker<T>> result = new ArrayList<Invoker<T>>();
            if (thenCondition == null) {
                logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
                return result;
            }
            for (Invoker<T> invoker : invokers) {
                if (matchThen(invoker.getUrl(), url)) {
                    result.add(invoker);
                }
            }
            if (!result.isEmpty()) {
                return result;
            } else if (force) {
                logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(RULE_KEY));
                return result;
            }
        } catch (Throwable t) {
            logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
        }
        return invokers;
    }

根据规则过滤invoker

TagRouter实现

1. 标签路由简介
首先小伙伴能够经过《Dubbo 路由规则之条件路由》回归一下什么是路由规则?下面咱们主要讨论什么标签路由:git
上图中咱们能够看到有两个机房分别是机房A、机房B,其中机房 A 只能访问到 Service A 和 Service B ,而机房B 只能访问到 Service C 和 Service D。要实现上面这种场景咱们就须要用到标签路由。从机房 A 发起的调用携带标签 TAG_A 访问到 Service A 和 Service B,而从机房 B 发起的调用携带 TAG_B Service C 和 Service D 。那什么是标签路由呢?shell

  • 标签路由:以服务提供者应用为粒度配置路由规则,经过将某一个或多个服务的提供者划分到同一个分组,约束流量只在指定分组中流转,从而实现流量隔离的目的,能够做为蓝绿发布、灰度发布等场景的能力基础。标签主要是指对Provider端应用实例的分组,目前有两种方式能够完成实例分组,分别是动态规则打标静态规则打标,其中动态规则相较于静态规则优先级更高,而当两种规则同时存在且出现冲突时,将以动态规则为准。

2. 使用方式
下面咱们简单的讨论下标签路由的使用方式:
动态规则打标,可随时在服务治理控制台下发标签归组规则编程apache

# demo-provider应用增长了两个标签分组tag1和tag2
# tag1包含一个实例 127.0.0.1:20880
# tag2包含一个实例 127.0.0.1:20881
---
  force: false
  runtime: true
  enabled: true
  key: demo-provider
  tags:
    - name: tag1
      addresses: ["127.0.0.1:20880"]
    - name: tag2
    addresses: ["127.0.0.1:20881"]

静态打标

<dubbo:provider tag="tag1"/>@Service(timeout = 4000,owner = "lxs",tag = "tag1")

**:**消费端经过编程的方式使用 **RpcContext.getContext().setAttachment(CommonConstants.TAG_KEY,“TAG_A”)**请求标签的做用域为每一次 invocation,使用 attachment 来传递请求标签,注意保存在 attachment 中的值将会在一次完整的远程调用中持续传递,得益于这样的特性,咱们只须要在起始调用时,经过一行代码的设置,达到标签的持续传递。
也可以使用注解指定

    @Reference(retries = 3,tag = "tag1")


  • 字段说明:
    | 编号 | 字段名称 | 说明 | 必填 |
    | — | — | — | — |
    | 1 | scope | 路由规则的做用粒度,scope的取值会决定key的取值。
    service 服务粒度 application 应用粒度。 | 必填 |
    | 2 | Key | 明确规则体做用在哪一个接口服务或应用。 scope=service时,
    key取值为[{group}:]{service}[:{version}]的组合 scope=application时,
    key取值为application名称 。 | 必填 |
    | 3 | enabled | enabled=true 当前路由规则是否生效,,缺省生效。 | 可不填 |
    | 4 | force | force=false 当路由结果为空时,是否强制执行,若是不强制执行,
    路由结果为空的路由规则将自动失效,缺省为 false。 | 可不填 |
    | 5 | runtime | runtime=false 是否在每次调用时执行路由规则,
    不然只在提供者地址列表变动时预先执行并缓存结果,
    调用时直接从缓存中获取路由结果。若是用了参数路由,必须设为 true
    须要注意设置会影响调用的性能,缺省为 false。 | 可不填 |
    | 6 | priority | priority=1 路由规则的优先级,用于排序,优先级越大越靠前执行,缺省为 0。 | 可不填 |
    | 7 | tags | 定义具体的标签分组内容,可定义任意n(n>=1)个标签并为每一个标签指定实例列表。其中name为标签名称 | 必填 |

2.2 降级约定

  • request.tag=tag1 时优先选择标记了tag=tag1provider。若集群中不存在与请求标记对应的服务,默认将降级请求 tag为空的provider;若是要改变这种默认行为,即找不到匹配tag1provider返回异常,需设置request.tag.force=true
  • request.tag未设置时,只会匹配tag为空的provider。即便集群中存在可用的服务,若 tag 不匹配也就没法调用,这与约定1不一样,携带标签的请求能够降级访问到无标签的服务,但不携带标签/携带其余种类标签的请求永远没法访问到其余标签的服务。

3. 使用场景
从上面的简单介绍咱们能够大体了解到,标签路由经过将某一个或多个服务的提供者划分到同一个分组,约束流量只在指定分组中流转,从而实现流量隔离的目的。咱们平常工做中经常使用的场景有:蓝绿发布、灰度发布等场景的能力基础等。分布

TagRouter

public class TagRouter extends AbstractRouter implements ConfigurationListener {
 

    @Override
    public synchronized void process(ConfigChangedEvent event) {
        if (logger.isDebugEnabled()) {
            logger.debug("Notification of tag rule, change type is: " + event.getChangeType() + ", raw rule is:\n " +
                    event.getContent());
        }

        try {
            if (event.getChangeType().equals(ConfigChangeType.DELETED)) {
                this.tagRouterRule = null;
            } else {
                this.tagRouterRule = TagRuleParser.parse(event.getContent());
            }
        } catch (Exception e) {
            logger.error("Failed to parse the raw tag router rule and it will not take effect, please check if the " +
                    "rule matches with the template, the raw rule is:\n ", e);
        }
    }

    @Override
    public URL getUrl() {
        return url;
    }

    @Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
        if (CollectionUtils.isEmpty(invokers)) {
            return invokers;
        }

        // since the rule can be changed by config center, we should copy one to use.
        final TagRouterRule tagRouterRuleCopy = tagRouterRule;
        if (tagRouterRuleCopy == null || !tagRouterRuleCopy.isValid() || !tagRouterRuleCopy.isEnabled()) {
            return filterUsingStaticTag(invokers, url, invocation);
        }

        List<Invoker<T>> result = invokers;
        String tag = StringUtils.isEmpty(invocation.getAttachment(TAG_KEY)) ? url.getParameter(TAG_KEY) :
                invocation.getAttachment(TAG_KEY);

        // if we are requesting for a Provider with a specific tag
        if (StringUtils.isNotEmpty(tag)) {
            List<String> addresses = tagRouterRuleCopy.getTagnameToAddresses().get(tag);
            // filter by dynamic tag group first
            if (CollectionUtils.isNotEmpty(addresses)) {
                result = filterInvoker(invokers, invoker -> addressMatches(invoker.getUrl(), addresses));
                // if result is not null OR it's null but force=true, return result directly
                if (CollectionUtils.isNotEmpty(result) || tagRouterRuleCopy.isForce()) {
                    return result;
                }
            } else {
                // dynamic tag group doesn't have any item about the requested app OR it's null after filtered by
                // dynamic tag group but force=false. check static tag
                result = filterInvoker(invokers, invoker -> tag.equals(invoker.getUrl().getParameter(TAG_KEY)));
            }
            // If there's no tagged providers that can match the current tagged request. force.tag is set by default
            // to false, which means it will invoke any providers without a tag unless it's explicitly disallowed.
            if (CollectionUtils.isNotEmpty(result) || isForceUseTag(invocation)) {
                return result;
            }
            // FAILOVER: return all Providers without any tags.
            else {
                List<Invoker<T>> tmp = filterInvoker(invokers, invoker -> addressNotMatches(invoker.getUrl(),
                        tagRouterRuleCopy.getAddresses()));
                return filterInvoker(tmp, invoker -> StringUtils.isEmpty(invoker.getUrl().getParameter(TAG_KEY)));
            }
        } else {
            // List addresses = tagRouterRule.filter(providerApp);
            // return all addresses in dynamic tag group.
            List<String> addresses = tagRouterRuleCopy.getAddresses();
            if (CollectionUtils.isNotEmpty(addresses)) {
                result = filterInvoker(invokers, invoker -> addressNotMatches(invoker.getUrl(), addresses));
                // 1. all addresses are in dynamic tag group, return empty list.
                if (CollectionUtils.isEmpty(result)) {
                    return result;
                }
                // 2. if there are some addresses that are not in any dynamic tag group, continue to filter using the
                // static tag group.
            }
            return filterInvoker(result, invoker -> {
                String localTag = invoker.getUrl().getParameter(TAG_KEY);
                return StringUtils.isEmpty(localTag) || !tagRouterRuleCopy.getTagNames().contains(localTag);
            });
        }
    }

    /**
     * If there's no dynamic tag rule being set, use static tag in URL.
     * 

* A typical scenario is a Consumer using version 2.7.x calls Providers using version 2.6.x or lower, * the Consumer should always respect the tag in provider URL regardless of whether a dynamic tag rule has been set to it or not. *

* TODO, to guarantee consistent behavior of interoperability between 2.6- and 2.7+, this method should has the same logic with the TagRouter in 2.6.x. * * @param invokers * @param url * @param invocation * @param * @return */ private <T> List<Invoker<T>> filterUsingStaticTag(List<Invoker<T>> invokers, URL url, Invocation invocation) { List<Invoker<T>> result = invokers; // Dynamic param String tag = StringUtils.isEmpty(invocation.getAttachment(TAG_KEY)) ? url.getParameter(TAG_KEY) : invocation.getAttachment(TAG_KEY); // Tag request if (!StringUtils.isEmpty(tag)) { result = filterInvoker(invokers, invoker -> tag.equals(invoker.getUrl().getParameter(TAG_KEY))); if (CollectionUtils.isEmpty(result) && !isForceUseTag(invocation)) { result = filterInvoker(invokers, invoker -> StringUtils.isEmpty(invoker.getUrl().getParameter(TAG_KEY))); } } else { result = filterInvoker(invokers, invoker -> StringUtils.isEmpty(invoker.getUrl().getParameter(TAG_KEY))); } return result; } }

process用于接收订阅的配置信息。
主要逻辑为 如果 没有合法动态标签,根据生产者配置的静态标签匹配。
配置了动态标签,按照配置的ip地址匹配过滤。,如果没有ip地址,按照静态标签的规则
如果为true,那么不论是否匹配到,都直接返回
如果force为false。若集群中不存在与请求标记对应的服务,默认将降级请求 tag为空的provider

AppRoute

对应的配置为
dubbo消费者篇_第16张图片

和条件路由类似,不过这里配置的的消费者的应用名称。条件路由配置的是服务名称

  @Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
            throws RpcException {
        if (!enabled) {
            return invokers;
        }

        if (CollectionUtils.isEmpty(invokers)) {
            return invokers;
        }
        try {
            if (!matchWhen(url, invocation)) {
                return invokers;
            }
            List<Invoker<T>> result = new ArrayList<Invoker<T>>();
            if (thenCondition == null) {
                logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
                return result;
            }
            for (Invoker<T> invoker : invokers) {
                if (matchThen(invoker.getUrl(), url)) {
                    result.add(invoker);
                }
            }
            if (!result.isEmpty()) {
                return result;
            } else if (force) {
                logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(RULE_KEY));
                return result;
            }
        } catch (Throwable t) {
            logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
        }
        return invokers;
    }

主要有match when当满足条件
然后根据match then进行过滤
经过路由挑选出invoker之后
回到invoke方法

 @Override
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();

        // binding attachments into invocation.
        Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
        }

        List<Invoker<T>> invokers = list(invocation);
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        return doInvoke(invocation, invokers, loadbalance);
    }

负载均衡策略

 protected LoadBalance initLoadBalance(List<Invoker<T>> invokers, Invocation invocation) {
        if (CollectionUtils.isNotEmpty(invokers)) {
            return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                    .getMethodParameter(RpcUtils.getMethodName(invocation), LOADBALANCE_KEY, DEFAULT_LOADBALANCE));
        } else {
            return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(DEFAULT_LOADBALANCE);
        }
    }

获取负载的方案,默认采用random随机
一:负载均衡介绍
1.1:负载均衡简介
以下是wikipedia对负载均衡的定义:
负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动的的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件
1.2:简单解释
这个概念如何理解呢?通俗点来说假如一个请求从客户端发起,比如(查询订单列表),要选择服务器进行处理,但是我们的集群环境提供了5个服务器A\B\C\D\E,每个服务器都有处理这个请求的能力,此时客户端就必须选择一个服务器来进行处理(不存在先选择A,处理一会又选择C,又跳到D).说白了就是一个选择的问题。当请求多了的话,就要考虑各服务器的负载,一共5个服务器,不可能每次都让一个服务器都来处理吧,比如把让其他服务器来分压。这就是负载均衡的优点:避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题。
二:dubbo的loadBalance接口
1.1:loadBalance
dubbo的负载均衡策略,主体向外暴露出来是一个接口,名字叫做loadBlace,位于com.alibaba.dubbo.rpc.cluster包下,很明显根据包名就可以看出它是用来管理集群的:
这个接口就一个方法,select方法的作用就是从众多的调用的List选择出一个调用者,Invoker可以理解为客户端的调用者,dubbo专门封装一个类来表示,URL就是调用者发起的URL请求链接,从这个URL中可以获取很多请求的具体信息,Invocation表示的是调用的具体过程,dubbo用这个类模拟调用具体细节过程:
1.2:AbstractLoadBlance
该接口在下面的子类都会对其进行实现。接口下是一个抽象类AbstractLoadBlance
package com.alibaba.dubbo.rpc.cluster;

public interface LoadBalance {@Adaptive("loadbalance")
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}

AbstractLoadBlance抽象类继承自LoadBalance,其中有个static方法表明它在类加载的时候就会运行,它表示的含义是计算预热加载权重,参数是uptime,这里可以理解为服务启动的时间,warmup就是预热时间,weight是权重的值,下面会对比进行详细解释:

public abstract class AbstractLoadBalance implements LoadBalance{
 
   static int calculateWarmupWeight(int uptime, int warmup, int weight){
    //
  }
    @Override
    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, 
   Invocation invocation){
   //
   }
    protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> 
     invokers, URL url, Invocation invocation);
      
     protected int getWeight(Invoker<?> invoker, Invocation invocation) {
    }
}

1.2.1:select方法
抽象类方法中有个有方法体的方法select,先判断调用者组成的List是否为null,如果是null就返回null。再判断调用者的大小,如果只有一个就返回那个唯一的调用者(试想,如果服务调用另一个服务时,当服务的提供者机器只有一个,那么就可以返回那一个,因为没有选择了!)如果这些都不成立,就继续往下走,走doSelect方法:

@Override
    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        if (invokers == null || invokers.isEmpty())
            return null;
        if (invokers.size() == 1)
            return invokers.get(0);
        return doSelect(invokers, url, invocation);
    }

1.2.2:doSelect方法
该方法是抽象的,交给具体的子类去实现,由此也可以思考出一个问题就是:dubbo为什么要将一个接口首先做出一个实现抽象类,再由不同的子类去实现?原因是抽象类中的非抽象方法,再子类中都是必须要实现的,而他们子类的不同点就是具体做出选择的策略不同,将公共的逻辑提取出来放在抽象类里,子类不用写多余的代码,只用维护和实现最终要的自己的逻辑

 protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation);

1.2.3:getWeight方法
顾名思义,这个方法的含义就是获取权重,首先通过URL(URL为dubbo封装的一个实体)获取基本的权重,如果权重大于0,会获取服务启动时间,再用当前的时间-启动时间就是服务到目前为止运行了多久,因此这个upTime就可以理解为服务启动时间,再获取配置的预热时间,如果启动时间小于预热时间,就会再次调用获取权重。这个预热的方法其实dubbo针对JVM做出的一个很契合的优化,因为JVM从启动到起来都运行到最佳状态是需要一点时间的,这个时间叫做warmup,而dubbo就会对这个时间进行设定,然后等到服务运行时间和warmup相等时再计算权重,这样就可以保障服务的最佳运行状态!

protected int getWeight(Invoker<?> invoker, Invocation invocation) {
        int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);
        if (weight > 0) {
            long timestamp = invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L);
            if (timestamp > 0L) {
                int uptime = (int) (System.currentTimeMillis() - timestamp);
                int warmup = invoker.getUrl().getParameter(Constants.WARMUP_KEY, Constants.DEFAULT_WARMUP);
                if (uptime > 0 && uptime < warmup) {
                    weight = calculateWarmupWeight(uptime, warmup, weight);
                }
            }
        }
        return weight;
    }

三:dubbo的几种负载均衡策略
3.1:整体架构图
可以看出抽象的负载均衡下的类分为4个,这4个类表示了4种负载均衡策略,分别是一致性Hash均衡算法、随机调用法、轮询法、最少活动调用法
dubbo消费者篇_第17张图片

3.2:RandomLoadBalance
随机调用负载均衡,该类实现了抽象的AbstractLoadBalance接口,重写了doSelect方法,看方法的细节就是首先遍历每个提供服务的机器,获取每个服务的权重,然后累加权重值,判断每个服务的提供者权重是否相同,如果每个调用者的权重不相同,并且每个权重大于0**,那么就会根据权重的总值生成一个随机数,再用这个随机数,根据调用者的数量每次减去调用者的权重,直到计算出当前的服务提供者随机数小于0,就选择那个提供者**!另外,如果每个机器的权重的都相同,那么权重就不会参与计算,直接选择随机算法生成的某一个选择,完全随机。可以看出,随机调用法,

public class RandomLoadBalance extends AbstractLoadBalance {
 
    public static final String NAME = "random";
 
    private final Random random = new Random();
 
    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        int length = invokers.size(); // Number of invokers
        int totalWeight = 0; // The sum of weights
        boolean sameWeight = true; // Every invoker has the same weight?
        for (int i = 0; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            totalWeight += weight; // Sum
            if (sameWeight && i > 0
                    && weight != getWeight(invokers.get(i - 1), invocation)) {
                sameWeight = false;
            }
        }
        if (totalWeight > 0 && !sameWeight) {
            // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
            int offset = random.nextInt(totalWeight);
            // Return a invoker based on the random value.
            for (int i = 0; i < length; i++) {
                offset -= getWeight(invokers.get(i), invocation);
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        // If all invokers have the same weight value or totalWeight=0, return evenly.
        return invokers.get(random.nextInt(length));
    }

RoundRobinLoadBlance
RoundRobinLoadBalance 实现的是加权轮询负载均衡算法。
轮询指的是将请求轮流分配给每个 Provider。例如,有 A、B、C 三个 Provider 节点,按照普通轮询的方式,我们会将第一个请求分配给 Provider A,将第二个请求分配给 Provider B,第三个请求分配给 Provider C,第四个请求再次分配给 Provider A……如此循环往复。
轮询是一种无状态负载均衡算法,实现简单,适用于集群中所有 Provider 节点性能相近的场景。 但现实情况中就很难保证这一点了,因为很容易出现集群中性能最好和最差的 Provider 节点处理同样流量的情况,这就可能导致性能差的 Provider 节点各方面资源非常紧张,甚至无法及时响应了,但是性能好的 Provider 节点的各方面资源使用还较为空闲。这时我们可以通过加权轮询的方式,降低分配到性能较差的 Provider 节点的流量。
加权之后,分配给每个 Provider 节点的流量比会接近或等于它们的权重比。例如,Provider 节点 A、B、C 权重比为 5:1:1,那么在 7 次请求中,节点 A 将收到 5 次请求,节点 B 会收到 1 次请求,节点 C 则会收到 1 次请求。
在 Dubbo 2.6.4 版本及之前,RoundRobinLoadBalance 的实现存在一些问题,例如,选择 Invoker 的性能问题、负载均衡时不够平滑等。在 Dubbo 2.6.5 版本之后,这些问题都得到了修复,所以这里我们就来介绍最新的 RoundRobinLoadBalance 实现。
每个 Provider 节点有两个权重:一个权重是配置的 weight,该值在负载均衡的过程中不会变化;另一个权重是 currentWeight (初始为0 每次自增都是weight),该值会在负载均衡的过程中动态调整,初始值为 0。
当有新的请求进来时,RoundRobinLoadBalance 会遍历 Invoker 列表,并用对应的 currentWeight 加上其配置的权重。遍历完成后,再找到最大的 currentWeight,将其减去权重总和,然后返回相应的 Invoker 对象。
下面我们通过一个示例说明 RoundRobinLoadBalance 的执行流程,这里我们依旧假设 A、B、C 三个节点的权重比例为 5:1:1。
dubbo消费者篇_第18张图片

  • 1、处理第一个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [0, 0, 0] 变为 [5, 1, 1]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后,将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [-2, 1, 1]。
  • 2、处理第二个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [-2, 1, 1] 变为 [3, 2, 2]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后,将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [-4, 2, 2]。
  • 3、处理第三个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [-4, 2, 2] 变为 [1, 3, 3]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 B。最后,将节点 B 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [1, -4, 3]。
  • 4、处理第四个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [1, -4, 3] 变为 [6, -3, 4]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后,将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [-1, -3, 4]。
  • 5、处理第五个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [-1, -3, 4] 变为 [4, -2, 5]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 C。最后,将节点 C 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [4, -2, -2]。
  • 6、处理第六个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [4, -2, -2] 变为 [9, -1, -1]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后,将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [2, -1, -1]。
  • 7、处理第七个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [2, -1, -1] 变为 [7, 0, 0]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后,将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [0, 0, 0]。

到此为止,一个轮询的周期就结束了。

而在 Dubbo 2.6.4 版本中,上面示例的一次轮询结果是 [A, A, A, A, A, B, C],也就是说前 5 个请求会全部都落到 A 这个节点上。这将会使节点 A 在短时间内接收大量的请求,压力陡增,而节点 B 和节点 C 此时没有收到任何请求,处于完全空闲的状态,这种“瞬间分配不平衡”的情况也就是前面提到的“不平滑问题”。
在 RoundRobinLoadBalance 中,为每个 Invoker 对象创建了一个对应的 WeightedRoundRobin 对象,用来记录配置的权重(weight 字段)以及随每次负载均衡算法执行变化的 current 权重(current 字段)。
了解了 WeightedRoundRobin 这个内部类后,我们再来看 RoundRobinLoadBalance.doSelect() 方法的具体实现:

public class RoundRobinLoadBalance extends AbstractLoadBalance {
 
    public static final String NAME = "roundrobin";
 
    private final ConcurrentMap<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<String, AtomicPositiveInteger>();
 
    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
        int length = invokers.size(); // Number of invokers
        int maxWeight = 0; // The maximum weight
        int minWeight = Integer.MAX_VALUE; // The minimum weight
        final LinkedHashMap<Invoker<T>, IntegerWrapper> invokerToWeightMap = new LinkedHashMap<Invoker<T>, IntegerWrapper>();
        int weightSum = 0;
        for (int i = 0; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            maxWeight = Math.max(maxWeight, weight); // Choose the maximum weight
            minWeight = Math.min(minWeight, weight); // Choose the minimum weight
            if (weight > 0) {
                invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight));
                weightSum += weight;
            }
        }
        AtomicPositiveInteger sequence = sequences.get(key);
        if (sequence == null) {
            sequences.putIfAbsent(key, new AtomicPositiveInteger());
            sequence = sequences.get(key);
        }
        int currentSequence = sequence.getAndIncrement();
        if (maxWeight > 0 && minWeight < maxWeight) {
            int mod = currentSequence % weightSum;
            for (int i = 0; i < maxWeight; i++) {
                for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
                    final Invoker<T> k = each.getKey();
                    final IntegerWrapper v = each.getValue();
                    if (mod == 0 && v.getValue() > 0) {
                        return k;
                    }
                    if (v.getValue() > 0) {
                        v.decrement();
                        mod--;
                    }
                }
            }
        }
        // Round robin
        return invokers.get(currentSequence % length);
    }

LeastActiveLoadBalance
配置方法

    @Reference(retries = 3,loadbalance = "leastactive",filter = "activelimit", actives=1)

看activelimit是怎么工作的

@Activate(group = CONSUMER, value = ACTIVES_KEY)
public class ActiveLimitFilter implements Filter, Filter.Listener {

    private static final String ACTIVELIMIT_FILTER_START_TIME = "activelimit_filter_start_time";

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        URL url = invoker.getUrl();
        String methodName = invocation.getMethodName();
        int max = invoker.getUrl().getMethodParameter(methodName, ACTIVES_KEY, 0);
        final RpcStatus rpcStatus = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
        if (!RpcStatus.beginCount(url, methodName, max)) {
            long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), TIMEOUT_KEY, 0);
            long start = System.currentTimeMillis();
            long remain = timeout;
            synchronized (rpcStatus) {
                while (!RpcStatus.beginCount(url, methodName, max)) {
                    try {
                        rpcStatus.wait(remain);
                    } catch (InterruptedException e) {
                        // ignore
                    }
                    long elapsed = System.currentTimeMillis() - start;
                    remain = timeout - elapsed;
                    if (remain <= 0) {
                        throw new RpcException(RpcException.LIMIT_EXCEEDED_EXCEPTION,
                                "Waiting concurrent invoke timeout in client-side for service:  " +
                                        invoker.getInterface().getName() + ", method: " + invocation.getMethodName() +
                                        ", elapsed: " + elapsed + ", timeout: " + timeout + ". concurrent invokes: " +
                                        rpcStatus.getActive() + ". max concurrent invoke limit: " + max);
                    }
                }
            }
        }

        invocation.put(ACTIVELIMIT_FILTER_START_TIME, System.currentTimeMillis());

        return invoker.invoke(invocation);
    }

max为注解中配置的actives代表 同时最多请求访问服务,
构建RpcStatus维护正切请求的数量。请求前+1.请求后-1.
如果达到了max。那么循环等到,直到别的线程释放,再请求,可以看出来,RpcStatus是维护在消费者本地的,而不是从生产者获取的。

LeastActiveLoadBalance 使用的是最小活跃数负载均衡算法。它认为当前活跃请求数越小的 Provider 节点,剩余的处理能力越多,处理请求的效率也就越高,那么该 Provider 在单位时间内就可以处理更多的请求,所以我们应该优先将请求分配给该 Provider 节点。
LeastActiveLoadBalance 需要配合 ActiveLimitFilter 使用,ActiveLimitFilter 会记录每个接口方法的活跃请求数,在 LeastActiveLoadBalance 进行负载均衡时,只会从活跃请求数最少的 Invoker 集合里挑选 Invoker。
在 LeastActiveLoadBalance 的实现中,首先会选出所有活跃请求数最小的 Invoker 对象,之后的逻辑与 RandomLoadBalance 完全一样,即按照这些 Invoker 对象的权重挑选最终的 Invoker 对象。下面是 LeastActiveLoadBalance.doSelect() 方法的具体实现:再看LeastActiveLoadBalance、

在 LeastActiveLoadBalance 的实现中,首先会选出所有活跃请求数最小的 Invoker 对象,之后的逻辑与 RandomLoadBalance 完全一样,即按照这些 Invoker 对象的权重挑选最终的 Invoker 对象。下面是 LeastActiveLoadBalance.doSelect() 方法的具体实现:

public class LeastActiveLoadBalance extends AbstractLoadBalance {

    public static final String NAME = "leastactive";

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        // 初始化Invoker数量
        int length = invokers.size();
        // 记录最小的活跃请求数
        int leastActive = -1;
        // 记录活跃请求数最小的Invoker集合的个数
        int leastCount = 0;
        // 记录活跃请求数最小的Invoker在invokers数组中的下标位置 
        int[] leastIndexes = new int[length];
        // 记录活跃请求数最小的Invoker集合中,每个Invoker的权重值
        int[] weights = new int[length];
        // 记录活跃请求数最小的Invoker集合中,所有Invoker的权重值之和
        int totalWeight = 0;
        // 记录活跃请求数最小的Invoker集合中,第一个Invoker的权重值
        int firstWeight = 0;
        // 活跃请求数最小的集合中,所有Invoker的权重值是否相同
        boolean sameWeight = true;


        // 遍历所有Invoker,获取活跃请求数最小的Invoker集合
        for (int i = 0; i < length; i++) {
            Invoker<T> invoker = invokers.get(i);
            // 获取该Invoker的活跃请求数
            int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
            // 获取该Invoker的权重
            int afterWarmup = getWeight(invoker, invocation);
            // save for later use
            weights[i] = afterWarmup;
            // 比较活跃请求数
            if (leastActive == -1 || active < leastActive) {
                // 当前的Invoker是第一个活跃请求数最小的Invoker,则记录如下信息
                // 重新记录最小的活跃请求数
                leastActive = active;
                // 重新记录活跃请求数最小的Invoker集合个数
                leastCount = 1;
                // 重新记录Invoker
                leastIndexes[0] = i;
                // 重新记录总权重值
                totalWeight = afterWarmup;
                // 该Invoker作为第一个Invoker,记录其权重值
                firstWeight = afterWarmup;
                // 重新记录是否权重值相等
                sameWeight = true;
                // If current invoker's active value equals with leaseActive, then accumulating.
            } else if (active == leastActive) {
                // 当前Invoker属于活跃请求数最小的Invoker集合
                // 记录该Invoker的下标
                leastIndexes[leastCount++] = i;
                // 更新总权重
                totalWeight += afterWarmup;
                // If every invoker has the same weight?
                if (sameWeight && afterWarmup != firstWeight) {
                    // 更新权重值是否相等
                    sameWeight = false;
                }
            }
        }
        // 如果只有一个活跃请求数最小的Invoker对象,直接返回即可
        if (leastCount == 1) {
            // If we got exactly one invoker having the least active value, return this invoker directly.
            return invokers.get(leastIndexes[0]);
        }
        
        // 下面按照RandomLoadBalance的逻辑,从活跃请求数最小的Invoker集合中,随机选择一个Invoker对象返回
        if (!sameWeight && totalWeight > 0) {
            // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on 
            // totalWeight.
            int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
            // Return a invoker based on the random value.
            for (int i = 0; i < leastCount; i++) {
                int leastIndex = leastIndexes[i];
                offsetWeight -= weights[leastIndex];
                if (offsetWeight < 0) {
                    return invokers.get(leastIndex);
                }
            }
        }
        // If all invokers have the same weight value or totalWeight=0, return evenly.
        return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
    }
}

远程调用

经过负载均衡挑选出一个invoker之后

  invoked.add(invoker);
            RpcContext.getContext().setInvokers((List) invoked);
            try {
                Result result = invoker.invoke(invocation);
                if (le != null && logger.isWarnEnabled()) {
                    logger.warn("Although retry the method " + methodName
                            + " in the service " + getInterface().getName()
                            + " was successful by the provider " + invoker.getUrl().getAddress()
                            + ", but there have been failed providers " + providers
                            + " (" + providers.size() + "/" + copyInvokers.size()
                            + ") from the registry " + directory.getUrl().getAddress()
                            + " on the consumer " + NetUtils.getLocalHost()
                            + " using the dubbo version " + Version.getVersion() + ". Last error is: "
                            + le.getMessage(), le);
                }
                return result;
            } catch (RpcException e) {
                if (e.isBiz()) { // biz exception.
                    throw e;
                }
                le = e;
            } catch (Throwable e) {
                le = new RpcException(e.getMessage(), e);
            } finally {
                providers.add(invoker.getUrl().getAddress());
            }
        }
        throw new RpcException(le.getCode(), "Failed to invoke the method "
                + methodName + " in the service " + getInterface().getName()
                + ". Tried " + len + " times of the providers " + providers
                + " (" + providers.size() + "/" + copyInvokers.size()
                + ") from the registry " + directory.getUrl().getAddress()
                + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
                + Version.getVersion() + ". Last error is: "
                + le.getMessage(), le.getCause() != null ? le.getCause() : le);
    }

正式开始执行invoker

 private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

        if (!filters.isEmpty()) {
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                last = new Invoker<T>() {

                    @Override
                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

                    @Override
                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    @Override
                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        Result asyncResult;
                        try {
                            asyncResult = filter.invoke(next, invocation);
                        } catch (Exception e) {
                            if (filter instanceof ListenableFilter) {
                                ListenableFilter listenableFilter = ((ListenableFilter) filter);
                                try {
                                    Filter.Listener listener = listenableFilter.listener(invocation);
                                    if (listener != null) {
                                        listener.onError(e, invoker, invocation);
                                    }
                                } finally {
                                    listenableFilter.removeListener(invocation);
                                }
                            } else if (filter instanceof Filter.Listener) {
                                Filter.Listener listener = (Filter.Listener) filter;
                                listener.onError(e, invoker, invocation);
                            }
                            throw e;
                        } finally {

                        }
                        return asyncResult.whenCompleteWithContext((r, t) -> {
                            if (filter instanceof ListenableFilter) {
                                ListenableFilter listenableFilter = ((ListenableFilter) filter);
                                Filter.Listener listener = listenableFilter.listener(invocation);
                                try {
                                    if (listener != null) {
                                        if (t == null) {
                                            listener.onResponse(r, invoker, invocation);
                                        } else {
                                            listener.onError(t, invoker, invocation);
                                        }
                                    }
                                } finally {
                                    listenableFilter.removeListener(invocation);
                                }
                            } else if (filter instanceof Filter.Listener) {
                                Filter.Listener listener = (Filter.Listener) filter;
                                if (t == null) {
                                    listener.onResponse(r, invoker, invocation);
                                } else {
                                    listener.onError(t, invoker, invocation);
                                }
                            }
                        });
                    }

                    @Override
                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }

        return last;
    }

又来了一层filter

   @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        RpcContext.getContext()
                .setInvoker(invoker)
                .setInvocation(invocation)
                .setLocalAddress(NetUtils.getLocalHost(), 0)
                .setRemoteAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort())
                .setRemoteApplicationName(invoker.getUrl().getParameter(REMOTE_APPLICATION_KEY))
                .setAttachment(REMOTE_APPLICATION_KEY, invoker.getUrl().getParameter(APPLICATION_KEY));
        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(invoker);
        }
        return invoker.invoke(invocation);
    }

这个地方 就会执行刚刚说的ActiveLimitFilter
最终执行
DubboInvoker

public class DubboInvoker<T> extends AbstractInvoker<T> {

    private final ExchangeClient[] clients;

    private final AtomicPositiveInteger index = new AtomicPositiveInteger();

    private final String version;

    private final ReentrantLock destroyLock = new ReentrantLock();

    private final Set<Invoker<?>> invokers;

    public DubboInvoker(Class<T> serviceType, URL url, ExchangeClient[] clients) {
        this(serviceType, url, clients, null);
    }

    public DubboInvoker(Class<T> serviceType, URL url, ExchangeClient[] clients, Set<Invoker<?>> invokers) {
        super(serviceType, url, new String[]{INTERFACE_KEY, GROUP_KEY, TOKEN_KEY, TIMEOUT_KEY});
        this.clients = clients;
        // get version.
        this.version = url.getParameter(VERSION_KEY, "0.0.0");
        this.invokers = invokers;
    }

    @Override
    protected Result doInvoke(final Invocation invocation) throws Throwable {
        RpcInvocation inv = (RpcInvocation) invocation;
        final String methodName = RpcUtils.getMethodName(invocation);
        inv.setAttachment(PATH_KEY, getUrl().getPath());
        inv.setAttachment(VERSION_KEY, version);

        ExchangeClient currentClient;
        if (clients.length == 1) {
            currentClient = clients[0];
        } else {
            currentClient = clients[index.getAndIncrement() % clients.length];
        }
        try {
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
            int timeout = getUrl().getMethodPositiveParameter(methodName, TIMEOUT_KEY, DEFAULT_TIMEOUT);
            if (isOneway) {
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                currentClient.send(inv, isSent);
                return AsyncRpcResult.newDefaultAsyncResult(invocation);
            } else {
                ExecutorService executor = getCallbackExecutor(getUrl(), inv);
                CompletableFuture<AppResponse> appResponseFuture =
                        currentClient.request(inv, timeout, executor).thenApply(obj -> (AppResponse) obj);
                // save for 2.6.x compatibility, for example, TraceFilter in Zipkin uses com.alibaba.xxx.FutureAdapter
                FutureContext.getContext().setCompatibleFuture(appResponseFuture);
                AsyncRpcResult result = new AsyncRpcResult(appResponseFuture, inv);
                result.setExecutor(executor);
                return result;
            }
        } catch (TimeoutException e) {
            throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        } catch (RemotingException e) {
            throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

}

org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeChannel#request(java.lang.Object, int, java.util.concurrent.ExecutorService)

public CompletableFuture<Object> request(Object request, int timeout, ExecutorService executor) throws RemotingException {
        if (closed) {
            throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
        }
        // create request.
        Request req = new Request();
        req.setVersion(Version.getProtocolVersion());
        req.setTwoWay(true);
        req.setData(request);
        DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout, executor);
        try {
            channel.send(req);
        } catch (RemotingException e) {
            future.cancel();
            throw e;
        }
        return future;
    }
 private DefaultFuture(Channel channel, Request request, int timeout) {
        this.channel = channel;
        this.request = request;
        this.id = request.getId();
        this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
        // put into waiting map.
        FUTURES.put(id, this);
        CHANNELS.put(id, channel);
    }

首先我们知道Future接口本质就是一个回调,当任务完成时,将任务结果塞回Future中。
通过一个全局变量存储消息id对应的 future。消息id唯一,利用这FUTURES这个map可以在收到消息回复是,找到对应future,完成future。
利用netty发送消息send之后返回future。

@Override
    public Result invoke(Invocation invocation) throws RpcException {
        Result asyncResult = invoker.invoke(invocation);

        try {
            if (InvokeMode.SYNC == ((RpcInvocation) invocation).getInvokeMode()) {
                /**
                 * NOTICE!
                 * must call {@link java.util.concurrent.CompletableFuture#get(long, TimeUnit)} because
                 * {@link java.util.concurrent.CompletableFuture#get()} was proved to have serious performance drop.
                 */
                asyncResult.get(Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
            }
        } catch (InterruptedException e) {
            throw new RpcException("Interrupted unexpectedly while waiting for remoting result to return!  method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        } catch (ExecutionException e) {
            Throwable t = e.getCause();
            if (t instanceof TimeoutException) {
                throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
            } else if (t instanceof RemotingException) {
                throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
            }
        } catch (Throwable e) {
            throw new RpcException(e.getMessage(), e);
        }
        return asyncResult;
    }

对于同步调用,在这里阻塞住。

   public T get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        Object r;
        long nanos = unit.toNanos(timeout);
        return reportGet((r = result) == null ? timedGet(nanos) : r);
    }

在获取结果时,
判断result有没有被塞回来。如果没有等待一会再获取
那么result是什么时候塞进来的呢
org.apache.dubbo.remoting.exchange.support.DefaultFuture#received(org.apache.dubbo.remoting.Channel, org.apache.dubbo.remoting.exchange.Response, boolean)

 public static void received(Channel channel, Response response, boolean timeout) {
        try {
            DefaultFuture future = FUTURES.remove(response.getId());
            if (future != null) {
                Timeout t = future.timeoutCheckTask;
                if (!timeout) {
                    // decrease Time
                    t.cancel();
                }
                future.doReceived(response);
            } else {
                logger.warn("The timeout response finally returned at "
                        + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))
                        + ", response " + response
                        + (channel == null ? "" : ", channel: " + channel.getLocalAddress()
                        + " -> " + channel.getRemoteAddress()));
            }
        } finally {
            CHANNELS.remove(response.getId());
        }
    }

在接收回复时,回复回将requestId返回,根据requestId取出对应的future

  private void doReceived(Response res) {
        if (res == null) {
            throw new IllegalStateException("response cannot be null");
        }
        if (res.getStatus() == Response.OK) {
            this.complete(res.getResult());
        } else if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
            this.completeExceptionally(new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage()));
        } else {
            this.completeExceptionally(new RemotingException(channel, res.getErrorMessage()));
        }

        // the result is returning, but the caller thread may still waiting
        // to avoid endless waiting for whatever reason, notify caller thread to return.
        if (executor != null && executor instanceof ThreadlessExecutor) {
            ThreadlessExecutor threadlessExecutor = (ThreadlessExecutor) executor;
            if (threadlessExecutor.isWaiting()) {
                threadlessExecutor.notifyReturn(new IllegalStateException("The result has returned, but the biz thread is still waiting" +
                        " which is not an expected state, interrupt the thread manually by returning an exception."));
            }
        }
    }

DefaultFuture内置了一个30秒检查是否超时的TimeoutCheckTask
超时判定详情请看这篇https://blog.csdn.net/qq_37436172/article/details/120086329

 private static class TimeoutCheckTask implements TimerTask {

        private final Long requestID;

        TimeoutCheckTask(Long requestID) {
            this.requestID = requestID;
        }

        @Override
        public void run(Timeout timeout) {
            DefaultFuture future = DefaultFuture.getFuture(requestID);
            if (future == null || future.isDone()) {
                return;
            }

            if (future.getExecutor() != null) {
                future.getExecutor().execute(() -> notifyTimeout(future));
            } else {
                notifyTimeout(future);
            }
        }

        private void notifyTimeout(DefaultFuture future) {
            // create exception response.
            Response timeoutResponse = new Response(future.getId());
            // set timeout status.
            timeoutResponse.setStatus(future.isSent() ? Response.SERVER_TIMEOUT : Response.CLIENT_TIMEOUT);
            timeoutResponse.setErrorMessage(future.getTimeoutMessage(true));
            // handle response.
            DefaultFuture.received(future.getChannel(), timeoutResponse, true);
        }
    }

接收回复有三种情况

  1. 正常接收消息
  2. 超时处理
  3. RemotingException异常,主要是网络方面的异常

得到结果后,返回invoke的结果

 private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

        if (!filters.isEmpty()) {
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                last = new Invoker<T>() {

                    @Override
                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

                    @Override
                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    @Override
                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        Result asyncResult;
                        try {
                            asyncResult = filter.invoke(next, invocation);
                        } catch (Exception e) {
                            if (filter instanceof ListenableFilter) {
                                ListenableFilter listenableFilter = ((ListenableFilter) filter);
                                try {
                                    Filter.Listener listener = listenableFilter.listener(invocation);
                                    if (listener != null) {
                                        listener.onError(e, invoker, invocation);
                                    }
                                } finally {
                                    listenableFilter.removeListener(invocation);
                                }
                            } else if (filter instanceof Filter.Listener) {
                                Filter.Listener listener = (Filter.Listener) filter;
                                listener.onError(e, invoker, invocation);
                            }
                            throw e;
                        } finally {

                        }
                        return asyncResult.whenCompleteWithContext((r, t) -> {
                            if (filter instanceof ListenableFilter) {
                                ListenableFilter listenableFilter = ((ListenableFilter) filter);
                                Filter.Listener listener = listenableFilter.listener(invocation);
                                try {
                                    if (listener != null) {
                                        if (t == null) {
                                            listener.onResponse(r, invoker, invocation);
                                        } else {
                                            listener.onError(t, invoker, invocation);
                                        }
                                    }
                                } finally {
                                    listenableFilter.removeListener(invocation);
                                }
                            } else if (filter instanceof Filter.Listener) {
                                Filter.Listener listener = (Filter.Listener) filter;
                                if (t == null) {
                                    listener.onResponse(r, invoker, invocation);
                                } else {
                                    listener.onError(t, invoker, invocation);
                                }
                            }
                        });
                    }

                    @Override
                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }

        return last;
    }

此时invoker的每个filter轮流执行onResponse和onError方法

完毕后来到FailoverClusterInvoker的doInvoke

 public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        List<Invoker<T>> copyInvokers = invokers;
        checkInvokers(copyInvokers, invocation);
        String methodName = RpcUtils.getMethodName(invocation);
        int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
        if (len <= 0) {
            len = 1;
        }
        // retry loop.
        RpcException le = null; // last exception.
        List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); // invoked invokers.
        Set<String> providers = new HashSet<String>(len);
        for (int i = 0; i < len; i++) {
            //Reselect before retry to avoid a change of candidate `invokers`.
            //NOTE: if `invokers` changed, then `invoked` also lose accuracy.
            if (i > 0) {
                checkWhetherDestroyed();
                copyInvokers = list(invocation);
                // check again
                checkInvokers(copyInvokers, invocation);
            }
            Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
            invoked.add(invoker);
            RpcContext.getContext().setInvokers((List) invoked);
            try {
                Result result = invoker.invoke(invocation);
                if (le != null && logger.isWarnEnabled()) {
                    logger.warn("Although retry the method " + methodName
                            + " in the service " + getInterface().getName()
                            + " was successful by the provider " + invoker.getUrl().getAddress()
                            + ", but there have been failed providers " + providers
                            + " (" + providers.size() + "/" + copyInvokers.size()
                            + ") from the registry " + directory.getUrl().getAddress()
                            + " on the consumer " + NetUtils.getLocalHost()
                            + " using the dubbo version " + Version.getVersion() + ". Last error is: "
                            + le.getMessage(), le);
                }
                return result;
            } catch (RpcException e) {
                if (e.isBiz()) { // biz exception.
                    throw e;
                }
                le = e;
            } catch (Throwable e) {
                le = new RpcException(e.getMessage(), e);
            } finally {
                providers.add(invoker.getUrl().getAddress());
            }
        }
        throw new RpcException(le.getCode(), "Failed to invoke the method "
                + methodName + " in the service " + getInterface().getName()
                + ". Tried " + len + " times of the providers " + providers
                + " (" + providers.size() + "/" + copyInvokers.size()
                + ") from the registry " + directory.getUrl().getAddress()
                + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
                + Version.getVersion() + ". Last error is: "
                + le.getMessage(), le.getCause() != null ? le.getCause() : le);
    }

因为是失败重试这种策略,因此RpcException那么就根据重试次数进行重试
返回后进入

   public Result invoke(Invocation invocation) throws RpcException {
            Result asyncResult;
            try {
                interceptor.before(next, invocation);
                asyncResult = interceptor.intercept(next, invocation);
            } catch (Exception e) {
                // onError callback
                if (interceptor instanceof ClusterInterceptor.Listener) {
                    ClusterInterceptor.Listener listener = (ClusterInterceptor.Listener) interceptor;
                    listener.onError(e, clusterInvoker, invocation);
                }
                throw e;
            } finally {
                interceptor.after(next, invocation);
            }
            return asyncResult.whenCompleteWithContext((r, t) -> {
                // onResponse callback
                if (interceptor instanceof ClusterInterceptor.Listener) {
                    ClusterInterceptor.Listener listener = (ClusterInterceptor.Listener) interceptor;
                    if (t == null) {
                        listener.onMessage(r, clusterInvoker, invocation);
                    } else {
                        listener.onError(t, clusterInvoker, invocation);
                    }
                }
            });
        }

进替补执行FailoverClusterInvoker的filter,实际上就一个ConsumerContextClusterInterceptor
再返回就到了代理接口的InvokerInvocationHandler

   return invoker.invoke(rpcInvocation).recreate();

现在调用
org.apache.dubbo.rpc.AppResponse#recreate

@Override
public Object recreate() throws Throwable {
    if (exception != null) {
        // fix issue#619
        try {
            // get Throwable class
            Class clazz = exception.getClass();
            while (!clazz.getName().equals(Throwable.class.getName())) {
                clazz = clazz.getSuperclass();
            }
            // get stackTrace value
            Field stackTraceField = clazz.getDeclaredField("stackTrace");
            stackTraceField.setAccessible(true);
            Object stackTrace = stackTraceField.get(exception);
            if (stackTrace == null) {
                exception.setStackTrace(new StackTraceElement[0]);
            }
        } catch (Exception e) {
            // ignore
        }
        throw exception;
    }
    return result;
}

如果没有异常,直接返回值
如果返回的Result含有异常,那么抛出异常。
这个异常是什么时候放入的?
org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)

 public Object decode(Channel channel, InputStream input) throws IOException {
        if (log.isDebugEnabled()) {
            Thread thread = Thread.currentThread();
            log.debug("Decoding in thread -- [" + thread.getName() + "#" + thread.getId() + "]");
        }

        ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType)
                .deserialize(channel.getUrl(), input);

        byte flag = in.readByte();
        switch (flag) {
            case DubboCodec.RESPONSE_NULL_VALUE:
                break;
            case DubboCodec.RESPONSE_VALUE:
                handleValue(in);
                break;
            case DubboCodec.RESPONSE_WITH_EXCEPTION:
                handleException(in);
                break;
            case DubboCodec.RESPONSE_NULL_VALUE_WITH_ATTACHMENTS:
                handleAttachment(in);
                break;
            case DubboCodec.RESPONSE_VALUE_WITH_ATTACHMENTS:
                handleValue(in);
                handleAttachment(in);
                break;
            case DubboCodec.RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS:
                handleException(in);
                handleAttachment(in);
                break;
            default:
                throw new IOException("Unknown result flag, expect '0' '1' '2' '3' '4' '5', but received: " + flag);
        }
        if (in instanceof Cleanable) {
            ((Cleanable) in).cleanup();
        }
        return this;
    }

在接收都生产者回复后,解码时生成AppResponse是就会解析异常塞进 AppResponse。

你可能感兴趣的:(dubbo,dubbo,分布式,java)