dubbo路由代碼分析4(script路由器file路由器)

接上篇http://www.javashuo.com/article/p-fjteedha-gw.htmljavascript

這篇分析下,script類型和file類型路由器。
目前,script類型和file路由規則,還不能經過dubbo的admin管理頁面添加。能夠經過java api添加。具體看這裏
先說,script路由器,它由ScriptRouterFactory路由工廠建立以下:java

public class ScriptRouterFactory implements RouterFactory {

    public static final String NAME = "script";

    public Router getRouter(URL url) {
        return new ScriptRouter(url);
    }

}

dubbo的腳本路由器,是經過執行一段腳本邏輯來執行路由規則,
它能定製出比condition路由規則更加靈活的路由規則。
先看下它接受的路由規則形式,以下:api

URL SCRIPT_URL = URL.valueOf("script://javascript?type=javascript&rule=function route(op1,op2){return op1} route(invokers)");

這個url中,type=javascript,表示腳本的語言使用javascript。
rule=function route(op1,op2){return op1} route(invokers),表示具體的腳本內容。route(invokers)表示當即執行route函數。緩存

dubbo腳本路由實現,依賴jdk對腳本引擎的實現。題外話,
從jdk1.6,根據JSR223,引入腳本引擎,目前jdk 用java只實現了一個叫Rhino的javasrcipt腳本引擎。
實際根據JSR 223標準,任何實現了jdk裏,AbstractScriptEngine抽象類等配套接口的腳本引擎,均可以集成到java程序中來,被jvm加載執行。
Rhino腳本引擎真的比較神奇,看duboo官方給的一個路由函數:數據結構

function route(invokers) {
	var result = new java.util.ArrayList(invokers.size());
	for (i = 0; i < invokers.size(); i ++) {
	if ("10.20.153.10".equals(invokers.get(i).getUrl().getHost())) {
	result.add(invokers.get(i));
       }
}
return result;
} (invokers); // 表示當即執行方法

裏面還能有java語法對象。jvm

下面看下腳本路由器具體實現代碼:函數

public class ScriptRouter implements Router {

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

    private static final Map<String, ScriptEngine> engines = new ConcurrentHashMap<String, ScriptEngine>();

    private final ScriptEngine engine;

    private final int priority;

    private final String rule;

    private final URL url;

    public ScriptRouter(URL url) {
        this.url = url;
        //經過type key,獲取腳本的語言類型,是用來初始化腳本引擎的。
        String type = url.getParameter(Constants.TYPE_KEY);
        //獲取優先級,路由之間排序用
        this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);
        //經過 rule key,獲取具體的腳本函數字符串
        String rule = url.getParameterAndDecoded(Constants.RULE_KEY);
        //type 沒取到值,默認是javascript類型
        if (type == null || type.length() == 0) {
            type = Constants.DEFAULT_SCRIPT_TYPE_KEY;
        }
        //沒有具體的規則,則拋出異常
        if (rule == null || rule.length() == 0) {
            throw new IllegalStateException(new IllegalStateException("route rule can not be empty. rule:" + rule));
        }
        //根據type,獲取java 的腳本類型。這裏用了map作緩存。
        ScriptEngine engine = engines.get(type);
        if (engine == null) {
            //根據type獲取java 的腳本類型,這塊得熟悉下,java對腳本的支持,
            engine = new ScriptEngineManager().getEngineByName(type);

            if (engine == null) {
                throw new IllegalStateException(new IllegalStateException("Unsupported route rule type: " + type + ", rule: " + rule));
            }
            engines.put(type, engine);
        }
        this.engine = engine;
        this.rule = rule;
    }

    public URL getUrl() {
        return url;
    }
    /***
    *
    *執行路由規則的邏輯
    */
    @SuppressWarnings("unchecked")
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
        try {
            //copy一份,原始invokers
            List<Invoker<T>> invokersCopy = new ArrayList<Invoker<T>>(invokers);
            Compilable compilable = (Compilable) engine;
            Bindings bindings = engine.createBindings();
            //綁定3個參數,也是在rule規則字串最後,調用函數時,傳遞的參數名稱。
            bindings.put("invokers", invokersCopy);
            bindings.put("invocation", invocation);
            bindings.put("context", RpcContext.getContext());
            //根據rule 規則字串,編譯腳本
            CompiledScript function = compilable.compile(rule);
            //執行腳本
            Object obj = function.eval(bindings);
            //把結果轉型,返回
            if (obj instanceof Invoker[]) {
                invokersCopy = Arrays.asList((Invoker<T>[]) obj);
            } else if (obj instanceof Object[]) {
                invokersCopy = new ArrayList<Invoker<T>>();
                for (Object inv : (Object[]) obj) {
                    invokersCopy.add((Invoker<T>) inv);
                }
            } else {
                invokersCopy = (List<Invoker<T>>) obj;
            }
            return invokersCopy;
        } catch (ScriptException e) {
            //fail then ignore rule .invokers.
            logger.error("route error , rule has been ignored. rule: " + rule + ", method:" + invocation.getMethodName() + ", url: " + RpcContext.getContext().getUrl(), e);
            return invokers;
        }
    }

    /***
     * 路由排序,實現Comparable接口方法
     * @param o
     * @return
     */
    public int compareTo(Router o) {
        if (o == null || o.getClass() != ScriptRouter.class) {
            return 1;
        }
        ScriptRouter c = (ScriptRouter) o;
        return this.priority == c.priority ? rule.compareTo(c.rule) : (this.priority > c.priority ? 1 : -1);
    }

}

接下來看下,file類型路由器。
file路由器,使dubbo能夠讀取使用放在文件裏的路由腳本邏輯。
這樣用戶能夠把路由腳本放在文件中,因爲路由邏輯在consumer方執行,因此文件要放在consumer能讀取的路徑裏。
看看它的代碼實現原理。
file路由器由FileRouterFactory路由工廠構造。
先看下file路由規則形式。以下
URL FILE_URL = URL.valueOf("file:///d:/path/to/route.js?router=script");
能夠看到構造的url數據結構內容以下圖:this

源碼解析:url

public class FileRouterFactory implements RouterFactory {

    public static final String NAME = "file";

    private RouterFactory routerFactory;

    public void setRouterFactory(RouterFactory routerFactory) {
        this.routerFactory = routerFactory;
    }

    public Router getRouter(URL url) {
        try {
            // File URL 轉換成 其它Route URL,而後Load
            // file:///d:/path/to/route.js?router=script ==> script:///d:/path/to/route.js?type=js&rule=<file-content>
            //經過router 獲取路由類型, 默認是script類型路由
            String protocol = url.getParameter(Constants.ROUTER_KEY, ScriptRouterFactory.NAME); // 將原類型轉爲協議
            String type = null; // 使用文件後綴作爲類型
            //獲取路由規則文件路徑
            String path = url.getPath();
            if (path != null) {
                int i = path.lastIndexOf('.');
                if (i > 0) {
                    type = path.substring(i + 1);
                }
            }
            //經過路徑,讀取腳本文件的內容
            String rule = IOUtils.read(new FileReader(new File(url.getAbsolutePath())));
            //設置路由類型到protocol裏,這裏protocol就是script。把規則字串放在url裏的rule key裏,
            URL script = url.setProtocol(protocol).addParameter(Constants.TYPE_KEY, type).addParameterAndEncoded(Constants.RULE_KEY, rule);
            //這裏routerFactory,實際上是dubbo根據spi機制生成的自適應類對象,
            //routerFactory實現的getRouter方法,會根據協議類型,自動構造相應類型路由器,下面有dubbo spi機制動態構造生成的RouterFactory接口實現類
            //這裏protocol是script
            return routerFactory.getRouter(script);
        } catch (IOException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

}

spi機制動態構造生成的RouterFactory接口實現類源碼:.net

package com.alibaba.dubbo.rpc.cluster;

import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class RouterFactory$Adpative implements com.alibaba.dubbo.rpc.cluster.RouterFactory {
    public com.alibaba.dubbo.rpc.cluster.Router getRouter(com.alibaba.dubbo.common.URL arg0) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
	//根據協議類型,獲取路由類型工廠類型
        String extName = url.getProtocol();
        if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.cluster.RouterFactory) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.cluster.RouterFactory extension = (com.alibaba.dubbo.rpc.cluster.RouterFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.RouterFactory.class).getExtension(extName);
        return extension.getRouter(arg0);
    }
}
相關文章
相關標籤/搜索