DUBBO配置規則詳解

DUBBO配置規則詳解

歡迎加入DUBBO交流羣:259566260

研究DUBBO也已經大半年了,對它的大部分源碼進行了分析,以及對它的內部機制有了比較深入的瞭解,以及各個模塊的實現。DUBBO包含很多內容,如果想了解DUBBO第一步就是啓動它,從而可以很好的使用它,那麼如何更好的使用呢?就需要知道DUBBO的各個配置項,以及它可以通過哪些途徑進行配置。個人對配置的理解,就好比時對動物的馴服,如何很好的馴服一頭猛獸,那就需要知道它各種習性,從而調整,已達到自己期望的結果。這篇不對DUBBO有哪些配置項可以配置,但是通過這篇文章,你應該能夠知道DUBBO可以進行哪些配置。本文會通過分析DUBBO加載配置源碼的分析,來使得大家對DUBBO的配置一塊有更加深入的瞭解。從而達到“馴服”DUBBO,以使得它成爲你們自己的DUBBO。

DUBBO在配置這一塊做的確實很完美,提供很很多參數,以及提供了多種渠道。下面進入正題,看看DUBBO怎麼加載配置的。在講這些之前,先給大家介紹一下在DUBBO源碼層面定義了哪些類來存儲各個模塊的配置項,從而瞭解DUBBO可以對哪些模塊進行配置。

哪些東西可以配置

由於大部分項目都會使用Spring,而且DUBBO也提供了通過Spring來進行配置,那麼先從這裏進行着手。DUBBO加載Spring的集成時在dubbo-config下面的dubbo-config-spring模塊下面,其中有一個類DubboNamespaceHandler,它實現了Spring提供的接口NamespaceHandlerSupport。那麼Spring怎麼發現整個實現類的呢?在該模塊的META-INF文件夾下有兩個文件: spring.handlers和spring.schemas,這兩個文件裏面制定了dubbo的namespace的XSD文件的位置以及dubbo的namespace由DubboNamespaceHandler來處理解析。說了這麼多廢話,只是想說明Spring是怎麼解析<dubbo:.../>配置的。

知道了DUBBO和Spring關於配置一塊時怎麼整合的之後,那麼你應該就不會詫異Spring怎麼那麼聰明,能夠解析dubbo的namespace。接下來看看DubboNamespaceHandler類裏面有什麼東西。

<!--lang:java-->
public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
    }

}

可以看到再init方法裏面都是調用一個方法registerBeanDefinitionParser,但是參數略微有些不同。registerBeanDefinitionParser方法的第一個參數是dubbo的namespace下面節點名稱,第二個參數時該節點由誰來進行解析。例如:registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));是解析<dubbo:application../>配置信息的,依此類推通過Spring可以配置<dubbo:module../>,<dubbo:registry../>等等,就不一一列舉了。至於每個標籤配置的作用,由於不是本篇的內容,所以這裏就不做過多的介紹。

通過上面應該清楚知道DUBBO可以配置哪些?這個問題應該不會再困擾你了。下面看看DUBBO是怎麼加載這些配置項的。

如何讀取我們的配置

通過Spring的Bean配置讀取

在項目中,會配置Spring的XML(雖然DUBBO也支持註解形式,但是個人不是很推崇,因爲這樣會將DUBBO整合到你的項目源碼裏面,而我的建議是不要講DUBBO和你的項目綁的太緊,我的建議是DUBBO的配置和項目隔離,從而方便管理,加入以後項目不使用DUBBO,而使用其他的分佈式RPC框架,對項目的調整會比較小),比如經常會通過下面的配置項引用一個遠程的服務:

<!--lang:xml-->
<dubbo:reference id="demoService" interface="com.alibaba.dubbo.demo.DemoService" timeout="2000" check="false"/>

通過上面的內容,應該知道dubbo:reference將會由new DubboBeanDefinitionParser(ReferenceBean.class, false)來解析(雖然看上去貌似所有配置都是用DubboBeanDefinitionParser來解析,但是有很大的不同)。那看看類DubboBeanDefinitionParser裏面做了什麼事情。

<!--lang:java-->
public class DubboBeanDefinitionParser implements BeanDefinitionParser {

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

private final Class<?> beanClass;

private final boolean required;

public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
    this.beanClass = beanClass;
    this.required = required;
}

public BeanDefinition parse(Element element, ParserContext parserContext) {
    return parse(element, parserContext, beanClass, required);
}
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {....}
}

首先看類的定義,DubboBeanDefinitionParser實現了Spring的BeanDefinitionParser接口,該接口時專門用來解析Bean的定義的(一看類名就應該知道),並且實現了public BeanDefinition parse(Element element, ParserContext parserContext)方法(別看整個方法返回了一個BeanDefinition對象,其實Spring並沒有利用整個返回的對象,具體你可以看看Spring的源碼,所以要把Bean的定義注入到Spring容器中,就需要手動的往Spring中注入,因爲Spring沒有給我們來做這件事請。),然後調用了靜態方法private static BeanDefinition parse(...),那麼該類主要就是在靜態的方法parse上了。由於該方法內容實在是太長了,不便粘貼出全部內容,我只分析主要的部分。在DubboBeanDefinitionParser構造方法參數上有一個Class<?> beanClass參數,它就是指定講當前標籤配置內容轉換成對應類的BeanDefinition並且注入到Spring容器中。

<!--lang:java-->
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
    RootBeanDefinition beanDefinition = new RootBeanDefinition();
    beanDefinition.setBeanClass(beanClass);
    beanDefinition.setLazyInit(false);
    String id = element.getAttribute("id");
    if ((id == null || id.length() == 0) && required) {
        //構造一個Bean的ID
    }
    ....
    parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
    ....
     for (Method setter : beanClass.getMethods()) {
        String name = setter.getName();
        if (name.length() > 3 && name.startsWith("set")
                && Modifier.isPublic(setter.getModifiers())
                && setter.getParameterTypes().length == 1) {
            Class<?> type = setter.getParameterTypes()[0];
            String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-");
            props.add(property);
            Method getter = null;
            try {
                getter = beanClass.getMethod("get" + name.substring(3), new Class<?>[0]);
            } catch (NoSuchMethodException e) {
                try {
                    getter = beanClass.getMethod("is" + name.substring(3), new Class<?>[0]);
                } catch (NoSuchMethodException e2) {
                }
            }
            if (getter == null 
                    || ! Modifier.isPublic(getter.getModifiers())
                    || ! type.equals(getter.getReturnType())) {
                continue;
            }
            if ("parameters".equals(property)) {
                parameters = parseParameters(element.getChildNodes(), beanDefinition);
            } else if ("methods".equals(property)) {
                parseMethods(id, element.getChildNodes(), beanDefinition, parserContext);
            } else if ("arguments".equals(property)) {
                parseArguments(id, element.getChildNodes(), beanDefinition, parserContext);
            }
        .....
        beanDefinition.getPropertyValues().addPropertyValue(property, reference);
        ......

上面代碼很顯然看到是通過反射的形式獲取類的get/set方法然,從而判斷該參數是否可以通過Spring注入進去,最後添加到beanDefinition中,並且注入到Spring容器中。上面則是從Spring中加載配置的機制。

通過java運行命令-D以及properties中獲取配置

上面內容看到DUBBO可以對哪些模塊進行配置,並且通過哪些類來存儲這些配置信息,例如:ReferenceBean,RegistryConfig,ServiceBean等,如果進去看看這些類的定義會發現他們都繼承了AbstractConfig抽象類,該抽象類中定義了protected static void appendProperties(AbstractConfig config)方法,參數時一個實現它自己的子類,接下來看看它做了什麼:

<!--lang:java-->
protected static void appendProperties(AbstractConfig config) {
    if (config == null) {
        return;
    }
    String prefix = "dubbo." + getTagName(config.getClass()) + ".";
    Method[] methods = config.getClass().getMethods();
    for (Method method : methods) {
        try {
            String name = method.getName();
            if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(method.getModifiers()) 
                    && method.getParameterTypes().length == 1 && isPrimitive(method.getParameterTypes()[0])) {
                String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-");

                String value = null;
                if (config.getId() != null && config.getId().length() > 0) {
                    String pn = prefix + config.getId() + "." + property;
                    value = System.getProperty(pn);
                    if(! StringUtils.isBlank(value)) {
                        logger.info("Use System Property " + pn + " to config dubbo");
                    }
                }
                if (value == null || value.length() == 0) {
                    String pn = prefix + property;
                    value = System.getProperty(pn);
                    if(! StringUtils.isBlank(value)) {
                        logger.info("Use System Property " + pn + " to config dubbo");
                    }
                }
                if (value == null || value.length() == 0) {
                    Method getter;
                    try {
                        getter = config.getClass().getMethod("get" + name.substring(3), new Class<?>[0]);
                    } catch (NoSuchMethodException e) {
                        try {
                            getter = config.getClass().getMethod("is" + name.substring(3), new Class<?>[0]);
                        } catch (NoSuchMethodException e2) {
                            getter = null;
                        }
                    }
                    if (getter != null) {
                        if (getter.invoke(config, new Object[0]) == null) {
                            if (config.getId() != null && config.getId().length() > 0) {
                                value = ConfigUtils.getProperty(prefix + config.getId() + "." + property);
                            }
                            if (value == null || value.length() == 0) {
                                value = ConfigUtils.getProperty(prefix + property);
                            }
                            if (value == null || value.length() == 0) {
                                String legacyKey = legacyProperties.get(prefix + property);
                                if (legacyKey != null && legacyKey.length() > 0) {
                                    value = convertLegacyValue(legacyKey, ConfigUtils.getProperty(legacyKey));
                                }
                            }

                        }
                    }
                }
                if (value != null && value.length() > 0) {
                    method.invoke(config, new Object[] {convertPrimitive(method.getParameterTypes()[0], value)});
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }
}

上面方法一開始是生產當前配置的前綴String prefix = "dubbo." + getTagName(config.getClass()) + ".",此處可以看到dubbo的所有配置項都是以dubbo.開始的,接下來是通過getTagName方法生成當前配置類的配置名稱,進去看看getTagName是怎麼爲各個配置類生成各自的配置名的。

<!--lang:java-->
private static final String[] SUFFIXS = new String[] {"Config", "Bean"};
private static String getTagName(Class<?> cls) {
    String tag = cls.getSimpleName();
    for (String suffix : SUFFIXS) {
        if (tag.endsWith(suffix)) {
            tag = tag.substring(0, tag.length() - suffix.length());
            break;
        }
    }
    tag = tag.toLowerCase();
    return tag;
}

分析上面的代碼很清楚的知道是怎麼生成各自配置類的配置名的,比如:ReferenceBean通過該方法會返回reference。所以關於引用遠程Bean的配置都是以dubbo.reference.開頭的。生成當前配置類的前綴之後,那麼還是按照管理反射出當前類的所有set方法,接下來就是從System.getProperty中取,如果其中沒有則會從ConfigUtils.getProperty中取。其中需要注意的是:從System.getProperty中取值並沒有判斷當前屬性是否有值(通過spring注入的),而從ConfigUtils.getProperty中取會判斷是否有值,如果沒有才會從其中取,這裏可以說明DUBBO配置項的優先級java -D優先於Spring配置,Spring配置優先於properties文件的配置,這也符合一般項目的規則。不知道大家注意到沒,不管是System.getProperty還是ConfigUtils.getProperty都會取兩次,一次是String pn = prefix + config.getId() + "." + property,另一次是String pn = prefix + property,這兩種的區別就是多了一個config.getId(),可能會好奇config.getId()是什麼內容呢?在介紹Spring加載配置的時候,應該知道config對象其實是Spring容器裏面的,那麼這裏的getId,其實就是我們在配置Spring的Bean時候配置的ID。例如:

<!--lang:java-->
<dubbo:reference id="demoService" interface="com.alibaba.dubbo.demo.DemoService" timeout="2000" check="false" />

該配置會產生一個ReferenceBean對象,那麼此時的config.getId()就是demoService。所以String pn = prefix + config.getId() + "." + property配置的目的是有沒有針對當前某個Bean的配置項。比如:配置dubbo消費端的超時時間,一般通過dubbo.reference.timeout,這其實是指定消費端所有Bean的超時時間。有時候我們需要指定某個Bean超時時間可以通過dubbo.reference.{beanId}.timeout來指定,例如上面的可以通過dubbo.reference.demoService.timeout來配置該Bean的超時時間。

到此對DUBBO所有配置途徑以及其原理進行了分析和介紹,貌似還沒說怎麼取發現dubbo有哪些配置。這裏教大家怎麼去發現DUBBO有哪些配置。上面介紹過,DUBBO將配置項設置到各個配置類的實體中都是通過判斷是否有get/set方法,到這裏大家應該清楚怎麼去發現了吧?就是如果想了解dubbo某個模塊的配置,直接到對應的配置類中看它有哪些字段,知道它的字段名,以及確定你要配置的範圍,是全局還是某個bean,還有你想通過什麼途徑來配置,按照dubbo提供的規則很好確定對應的字段怎麼來進行配置。那麼你可能會說,字段時一個單詞知道是怎麼配置,那麼如果字段時多個單詞組合的呢?由於java的編碼風格一般時駝峯格式第:第一個單詞首字母小寫後面單詞首字母大寫,那麼這種情況對應dubbo來說怎麼獲取該配置項呢?看下面:

<!--lang:java-->
  //name是get/set方法名
 String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-");

不難發現對於駝峯,dubbo時通過-字符來分割各個單詞。其實到這裏基本上可以很好的配置DUBBO了,但是有一個上面的途徑如果需要調整參數都需要重啓應用,達不到動態調整,於是dubbo爲了能夠滿足在項目不重啓的情況下可以動態的調整配置參數。

通過DUBBO管理應用添加動態配置

這種方式主要原理就是通過管理器將動態參數發佈到註冊中心(zookeeper)中,然後各個節點可以獲得最新的配置變更,然後進行動態調整。想知道這一塊內容,需要取看看類RegistryDirectory的實現,該類它實現了NotifyListener。那麼它就可以監聽到註冊中心的任何變更。再瞭解RegistryDirectory之前,先看看DUBBO對每個服務在註冊中心存儲了哪些信息?如果用的時zookeeper,那麼你會在每個服務節點目錄下面看到一下幾個目錄consumers,providers,configurators,routers。其中動態配置時放在configurators節點目錄下。服務消費端會監聽configurators目錄變更,如果變更則會調用RegistryDirectoryvoid notify(List<URL> urls)方法。監聽configurators目錄變更觸發的void notify(List<URL> urls)方法時,urls的是類似override://...,表示將覆蓋調用該服務的某些配置(dubbo中對所有的調用配置都是通過URL的形式來展示的),講這些URL上面的參數信息替換到調用服務端的URL上面取,並且重新構造該服務的Invoke對象,從而達到更新參數的目的。

可以給接口的方法配置參數

整個場景在實際項目中是存在的,比如一個接口存在多個方法,有時候講參數配置現在再接口層面還是不夠的,需要精確到方法級別,例如對接口調用的超時時間設置。dubbo對與方法級別的配置提供了兩種途徑。

Spring Bean的方式配置

如下進行配置:

<!--lang:xml-->
<dubbo:reference id="demoService" interface="com.alibaba.dubbo.demo.DemoService" timeout="2000" check="false" >
        <dubbo:method name="sayHello" timeout="1000"/>
</dubbo:reference>

通過多嵌套一個dubbo:method標籤來實現。

動態配置

在DUBBO的管理器提供了動態配置的功能,通過添加動態配置,以及指定通知的消費端來指定某個服務的某個方法調整參數。它那裏配置的規則時方法名加配置項的名稱,例如:key配置成:sayHello.timeout,value=”10000”。那麼管理器會在註冊中心的對應服務的configurators添加一條override://...?sayHello.timeout=10000節點,指定消費端會監聽到這個變更,執行上面內容的流程。

到此關於DUBBO配置已經介紹完畢,可能有一些地方還時沒說明清楚,存在疑慮。如果有疑問歡迎提問或者自己取看看相關的DUBBO源碼,那一定會了解更加清楚。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章