Motan服務的啓動

Motan是新浪於2016年開源的一個RPC框架,類似的已有的RPC框架有像阿里開源的Dubbo,還有後來在此基礎上做二次開源的當當網的dubbox。
Motan分爲服務提供方和服務調用方,服務提供方發佈服務,服務調用方調用服務。
看這樣一個簡單的例子:
1、創建一個接口,並且簡單實現:

public interface FooService {
    public String hello(String name);
}
public class FooServiceImpl implements FooService {
    public String hello(String name) {
        System.out.println("Invoke RPC service method, "+name);
        return "Hello, "+name;
    }
}

2、配置xm

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:motan="http://api.weibo.com/schema/motan"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   http://api.weibo.com/schema/motan http://api.weibo.com/schema/motan.xsd">
    <bean id="serviceImpl" class="quickstart.FooServiceImpl" />
    <motan:registry address="127.0.0.1:2181" regProtocol="zookeeper" name="myzk"></motan:registry>
    <motan:service interface="quickstart.FooService" ref="serviceImpl" registry="myzk" group="myMotanService1" export="8080"/>

</beans>

xml的配置也很簡單易懂:
先定義一個id爲serviceImpl的類,也就是我們的服務具體實現,然後將服務聲明爲zookeeper協議,並且註冊到本地2181端口上,最後將服務實現發佈成服務,暴露的端口爲8080.
3、啓動本地zk,端口默認爲2181.運行服務器程序:

public class ServerZK {
    public static void main(String[] args) {

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:motanServer.xml");
        MotanSwitcherUtil.setSwitcherValue(MotanConstants.REGISTRY_HEARTBEAT_SWITCHER,true);
        System.out.println("Server start...");
    }
}

運行輸出信息:Server start…
服務發佈成功了嗎?可以使用zkClient查看目錄:
/motan/myMotanService1/quickstart.FooService/server
發現有可用服務:
[192.168.0.6:8080]
運行main方法之後,這發生了什麼?
觀察我們的xml配置,有兩點值得說明:
1、該xml使用了名爲【motan】的Namespace
2、bean和registry的配置都被使用在了service配置中
對於第一點,由於是和Spring結合使用,所以motan命名空間肯定是會有相關聯的NamespaceHandler進行解析的,也一定可以在motan的jar包中找到對應的xsd約束(在motan-core.jar包中就可以找到)。
根據一個不成文的約定(很多命名空間都是這樣的,像Spring自帶的aop命名空間,解析的Handler就是AOPNamespaceHandler),查找MotanNamespaceHandler類。
這個類負責來解析xml到Motan所需要使用到的類,具體過程很繁瑣而無味。

service被解析成了ServiceConfigBean這個類,查看這個類,發現它的方法簽名:

public class ServiceConfigBean<T> extends ServiceConfig<T>
        implements
        BeanPostProcessor,
        BeanFactoryAware,
        InitializingBean,
        DisposableBean,
        ApplicationListener<ContextRefreshedEvent> 

實現了Spring暴露的擴展接口。
最後一個是ApplicationListener,這個類使得我們的ServiceConfigBean類監聽了Spring的ContextRefreshEvent,在ApplicationContext加載時(此處是ClasspathXmlApplicationContext)的finishRefresh方法觸發監聽器,調用ServiceConfigBean的onApplicationEvent方法。
可以看到,這個方法調用了export方法,也就是發佈服務的方法。

========分割線==========
實現InitializingBean的類,需要實現方法:

public void afterPropertiesSet() throws Exception {

        // 注意:basicConfig需要首先配置,因爲其他可能會依賴於basicConfig的配置
        checkAndConfigBasicConfig();
        checkAndConfigExport();
        checkAndConfigRegistry();

        // 等spring初始化完畢後,再export服務
        // export();
    }

三個方法調用,分別加載了basicService;
checkAndConfigExport檢查是否有export配置(如果沒有,則用basicService中配置的export配置);
checkAndConfigRegistry保證發佈的服務能有合適的registry,如果service配置了就使用該配置,如果沒有,則取basicService中配置的,如果basicService中沒有配置則從MotanNamespaceHandler.registryDefineNames中去加載(以防沒有成功生成對象),如果以上的操作都沒有獲取到,則使用默認的local配置。

講了這麼一堆,現在纔剛剛開始進入本節的主題,終於要開始發佈服務了。

public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!getExported().get()) {
            export();
        }
    }

每個ServiceConfigBean都有一個exported布爾值,表示該Service是不是已經被髮布過。如果發佈過,就不再發布。
接下來進入發佈的方法,export():

public synchronized void export() {
        //在同步塊中再次檢查是不是已經被髮布過了
        //此處的exported變量是J.C.U包中的原子變量,可以保證在多個線程之間及時可見
        if (exported.get()) {
            LoggerUtil.warn(String.format("%s has already been expoted, so ignore the export request!", interfaceClass.getName()));
            return;
        }
        //① 檢查 方法是不是在接口中存在
        checkInterfaceAndMethods(interfaceClass, methods);
        //② 加載註冊地址列表
        List<URL> registryUrls = loadRegistryUrls();
        if (registryUrls == null || registryUrls.size() == 0) {
            throw new IllegalStateException("Should set registry config for service:" + interfaceClass.getName());
        }
        //③ 生成協議和端口的鍵值對
        Map<String, Integer> protocolPorts = getProtocolAndPort();
        for (ProtocolConfig protocolConfig : protocols) {
            Integer port = protocolPorts.get(protocolConfig.getId());
            if (port == null) {
                throw new MotanServiceException(String.format("Unknow port in service:%s, protocol:%s", interfaceClass.getName(),
                        protocolConfig.getId()));
            }
            // ④ 在註冊的地址上,按協議和端口 進行服務發佈
            doExport(protocolConfig, port, registryUrls);
        }
        //⑤ 發佈之後的一些操作
        afterExport();
    }

幾個主要的步驟都被抽成方法,嗯,所以這個方法看上去還是挺簡潔易懂的。
1、檢查方法是不是在接口中存在
由於moton發佈service的時候可以指定的方法,所以需要在export之前,確認指定的接口是不是存在該方法。

2、生成註冊地址列表

protected List<URL> loadRegistryUrls() {
        List<URL> registryList = new ArrayList<URL>();
        if (registries != null && !registries.isEmpty()) {
            //遍歷registry配置信息
            for (RegistryConfig config : registries) {
                String address = config.getAddress();
                if (StringUtils.isBlank(address)) {
                    address = NetUtils.LOCALHOST + ":" + MotanConstants.DEFAULT_INT_VALUE;
                }
                Map<String, String> map = new HashMap<String, String>();
                //將registry的各個屬性,以鍵值對的形式存儲在map中
                config.appendConfigParams(map);

                map.put(URLParamType.application.getName(), getApplication());
                map.put(URLParamType.path.getName(), RegistryService.class.getName());
                map.put(URLParamType.refreshTimestamp.getName(), String.valueOf(System.currentTimeMillis()));

                // 設置默認的registry protocol,parse完protocol後,需要去掉該參數
                if (!map.containsKey(URLParamType.protocol.getName())) {
                    if (address.contains("://")) {
                        map.put(URLParamType.protocol.getName(), address.substring(0, address.indexOf("://")));
                    }
                    map.put(URLParamType.protocol.getName(), MotanConstants.REGISTRY_PROTOCOL_LOCAL);
                }
                // address內部可能包含多個註冊中心地址
                //將地址和參數構造成一個motan服務地址,類似於下面的格式:
                // zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc

                List<URL> urls = UrlUtils.parseURLs(address, map);
                if (urls != null && !urls.isEmpty()) {
                    for (URL url : urls) {

                        url.removeParameter(URLParamType.protocol.getName());
                        registryList.add(url);
                    }
                }
            }
        }
        return registryList;
    }

3、生成協議和端口的鍵值對
一個service可以按多個protocol提供服務,不同protocol使用不同port 利用export來設置protocol和port,格式如下:
protocol1:port1,protocol2:port2
4、發佈服務
Motan使用Netty做爲底層的服務發佈框架。
對於發佈服務來說,doExport方法重點在下面的兩句:

ConfigHandler configHandler = ExtensionLoader.getExtensionLoader(ConfigHandler.class).getExtension(MotanConstants.DEFAULT_VALUE);

        exporters.add(configHandler.export(interfaceClass, ref, urls));

ConfigHandler是一個很重要的對象,連接了配置和協議。
實現爲SimpleConfigHandler,它主要提供如下幾個方面的功能:
1、提供服務集羣模塊,可以增加過濾器
2、將service關聯到具體的Protocol對象,後者是提供Netty服務的對象
3、提供註冊和取消註冊的功能
4、提供服務的註銷功能
這個具體以後還會再提到。
configHandler的export方法將返回Export對象,該例子中使用的的”DefaultRpcExporter”實現;
Exporter是Motan中一個很重要的對象,它通過SPI配置創建EndpointFactory對象,並通過createServer方法暴露服務。
這部分的內容,之後還會詳細看。

-EOF-

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