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

本文详细介绍了Dubbo框架中的Script与File类型的路由器实现原理。Script路由器通过执行脚本逻辑进行路由,提供高度定制化的路由规则;File路由器则允许用户通过文件配置路由逻辑,增加了灵活性。

接上篇https://my.oschina.net/u/146130/blog/1592235

这篇分析下,script类型和file类型路由器。
目前,script类型和file路由规则,还不能通过dubbo的admin管理页面添加。可以通过java api添加。具体看这里
先说,script路由器,它由ScriptRouterFactory路由工厂创建如下:

public class ScriptRouterFactory implements RouterFactory {

    public static final String NAME = "script";

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

}

dubbo的脚本路由器,是通过执行一段脚本逻辑来执行路由规则,
它能定制出比condition路由规则更加灵活的路由规则。
先看下它接受的路由规则形式,如下:

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语法对象。

下面看下脚本路由器具体实现代码:

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数据结构内容如下图:

源码解析:

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接口实现类源码:

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);
    }
}

 

转载于:https://my.oschina.net/u/146130/blog/1596053

我的Dubbo Router功能完全不生效,我已经全部按照规范配置了: Router实现类: package com.htsc.gtp.quant.cm.domestic.router; import com.alibaba.dubbo.common.URL; import com.alibaba.dubbo.common.extension.Activate; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.RpcException; import com.alibaba.dubbo.rpc.cluster.Router; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import static com.htsc.gtp.quant.cm.domestic.enums.StrategyCodeEnum.DOM_GRID_COND_ORDER; /** * @Project: gtp-quant-cm-domestic * @Author: 022679 * @CreateTime: 2025-08-20 15:00 * @Description: * @Version: **/ @Slf4j @Activate(group = {"provider"}) public class StraTransServiceRouter implements Router { public StraTransServiceRouter() { log.info("StraTransServiceRouter loaded successfully!"); } @Override public URL getUrl() { return null; } @Override public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException { log.info("Enter StraTransServiceRouter"); String interfaceName = invocation.getInvoker().getInterface().getName(); if ("ICondStraTransService".equals(interfaceName)) { Object[] args = invocation.getArguments(); if (args != null && args.length == 1) { String reqDtoJsonStr = JSON.toJSONString(args[0]); JSONObject reqDtoJsonObject = JSON.parseObject(reqDtoJsonStr); Integer strategyCode = reqDtoJsonObject.getInteger("strategyCode"); if (Objects.nonNull(strategyCode) && strategyCode.equals(DOM_GRID_COND_ORDER.getVal())) { // 网格策略大厅跟单请求,筛选Service注解group为gridHall的实现类 return invokers.stream().filter(tInvoker -> "gridHall".equals(tInvoker.getUrl().getParameter("group"))) .collect(Collectors.toList()); } else { // 非网格策略大厅跟单请求,筛选Service注解group不为gridHall的实现类 return invokers.stream().filter(tInvoker -> !"gridHall".equals(tInvoker.getUrl().getParameter("group"))) .collect(Collectors.toList()); } } } // 非ICondStraTransService接口,不做路由 return invokers; } @Override public int getPriority() { return 0; } @Override public int compareTo(Router o) { return 0; } } RouterFactory实现类: package com.htsc.gtp.quant.cm.domestic.router; import com.alibaba.dubbo.common.URL; import com.alibaba.dubbo.common.extension.Activate; import com.alibaba.dubbo.rpc.cluster.Router; import com.alibaba.dubbo.rpc.cluster.RouterFactory; import org.springframework.stereotype.Component; /** * @Project: gtp-quant-cm-domestic * @Author: 022679 * @CreateTime: 2025-08-20 18:16 * @Description: * @Version: **/ @Activate(group = {"provider"}) public class StraTransServiceRouterFactory implements RouterFactory { @Override public Router getRouter(URL url) { return new StraTransServiceRouter(); } } SPI配置: D:\JavaProject\gtp-quant\gtp-quant-cm-domestic\gtp-quant-cm-domestic-server\src\main\resources\META-INF\dubbo\com.alibaba.dubbo.rpc.cluster.Router straTransServiceRouter=com.htsc.gtp.quant.cm.domestic.router.StraTransServiceRouter D:\JavaProject\gtp-quant\gtp-quant-cm-domestic\gtp-quant-cm-domestic-server\src\main\resources\META-INF\dubbo\com.alibaba.dubbo.rpc.cluster.RouterFactory straTransServiceRouterFactory=com.htsc.gtp.quant.cm.domestic.router.StraTransServiceRouterFactory dubbo-config.xml: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 应用信息配置 --> <dubbo:application name="${dubbo.application.name}" owner="${dubbo.application.owner}" logger="${dubbo.application.logger}" /> <!-- 服务注册中心配置 --> <dubbo:registry id="dubbo_zookeeper" address="${dubbo.registry.address}" register="true" subscribe="true" check="false" file="${dubbo.application.name}" session="${dubbo.session.timeout}"/> <!-- 灰度服务注册中心配置 --> <dubbo:registry id="dubbo_grey_zookeeper" address="${dubbo.grey.registry.address}" register="true" subscribe="true" check="false" file="${dubbo.application.name}" session="${dubbo.session.timeout}"/> <!-- 监控中心配置 --> <!-- <dubbo:monitor protocol="${dubbo.monitor.protocol}" /> --> <!-- 服务协议&端口 --> <dubbo:protocol name="dubbo" port="${dubbo.dubbo.port}" /> <!-- dubbo:protocol name="dubbo" port="${dubbo.dubbo.port}" serialization="${dubbo.dubbo.serialization}" / --> <!-- 服务协议&端口 --> <dubbo:protocol name="rest" port="${dubbo.rest.port}" contextpath="${dubbo.rest.contextpath}" server="${dubbo.rest.server}" extension=""/> <!-- dubbo:service 默认配置 --> <dubbo:provider filter="rpcinfocollect,globalFilter,logFilter,traceFilter" validation="false" registry="dubbo_zookeeper" protocol="${dubbo.protocol}" payload="883886080" retries="${dubbo.retries}" timeout="${dubbo.session.timeout}" executes="${dubbo.executes}" actives="${dubbo.actives}" loadbalance="${dubbo.loadbalance}" threads="${dubbo.threads}" accepts="${dubbo.accepts}" /> <!-- dubbo:reference 默认配置 --> <dubbo:consumer filter="rpcinfocollect,traceFilter" validation="false" check="false" registry="dubbo_zookeeper" retries="${dubbo.retries}" timeout="${dubbo.session.timeout}" loadbalance="${dubbo.loadbalance}" /> <!-- 跨系统调用接口 --> <dubbo:reference id="custRegisterApi" interface="com.htsc.gtp.quant.cust.api.CustRegisterApi" version="1.0.0" registry="dubbo_zookeeper"/> <dubbo:reference id="acctManagerApi" interface="com.htsc.gtp.quant.cust.api.AcctManagerApi" version="1.0.0" registry="dubbo_zookeeper"/> <dubbo:reference id="equityManagerApi" interface="com.htsc.gtp.quant.cust.api.EquityManagerApi" version="1.0.0" registry="dubbo_zookeeper"/> <dubbo:reference id="abnormalCliQueryApi" interface="com.htsc.gtp.quant.cust.api.AbnormalCliQueryApi" version="1.0.0" registry="dubbo_zookeeper"/> <!--<dubbo:reference id="quantStaticInfoService" interface="com.htsc.gtp.pmdsproxy.api.QuantStaticInfoService" version="${prdt.dubbo.version}" registry="dubbo_zookeeper"/>--> <dubbo:reference id="strategyCondOrderQueryApi" interface="com.htsc.gtp.sem.dqs.api.StrategyCondOrderQueryApi" version="1.1.0" registry="dubbo_zookeeper"/> <dubbo:reference id="strategyCondOrderHisQueryApi" interface="com.htsc.gtp.sem.dqs.api.StrategyCondOrderHisQueryApi" version="1.1.0" registry="dubbo_zookeeper"/> <!--旧微服务dubbo接口--> <dubbo:reference id="oldMaQuantOrderService" interface = "com.htsc.uf.api.IMaQuantOrderService" version="1.1.8" registry="dubbo_grey_zookeeper"/> <dubbo:reference id="oldCondAcctService" interface = "com.htsc.uf.api.ICondAcctService" registry="dubbo_grey_zookeeper"/> <!-- 对外接口 --> <dubbo:service ref = "maQuantOrderService" interface = "com.htsc.gtp.quant.cm.domestic.api.IMaQuantOrderService" version="1.1.8" registry="dubbo_grey_zookeeper"/> <!-- <dubbo:service ref = "condOrdSuitService" interface = "com.htsc.uf.api.ICondOrdSuitService" version="1.1.8" />--> <dubbo:service ref = "condAcctService" interface = "com.htsc.gtp.quant.cm.domestic.api.ICondAcctService" registry="dubbo_grey_zookeeper"/> <!-- <dubbo:service ref = "maQuoteService" interface = "com.htsc.uf.api.IMaQuoteService" version="1.1.8" />--> <!-- <dubbo:service ref = "condOrdQryService" interface = "com.htsc.uf.api.ICondOrdQryService" version="1.1.8" /> --> <dubbo:service ref = "maManageService" interface = "com.htsc.gtp.quant.cm.domestic.api.IMaManageService" registry="dubbo_grey_zookeeper"/> <dubbo:service ref = "condStraTransServiceImpl" group="default" interface = "com.htsc.gtp.quant.cm.domestic.api.ICondStraTransService" registry="dubbo_grey_zookeeper"/> <bean id="condStraTransServiceImpl" class="com.htsc.gtp.quant.cm.domestic.api.impl.CondStraTransServiceImpl"/> <dubbo:service ref = "gridHallCondStraTransServiceImpl" group="gridHall" interface = "com.htsc.gtp.quant.cm.domestic.api.ICondStraTransService" registry="dubbo_grey_zookeeper"/> <bean id="gridHallCondStraTransServiceImpl" class="com.htsc.gtp.quant.cm.domestic.api.impl.GridHallCondStraTransServiceImpl"/> </beans>
最新发布
08-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值