dubbo-admin路由规则设置的bug以及dubbo路由详解(基于dubbo.2.7.3)

一、dubbo路由规则的读取详解

要想解决dubbo-admin路由规则设置的bug,那么就必须要搞懂dubbo是怎么读取admin设置的规则,源码必须要读。。。。
1、RegistryDirectory和RouterChain
RegistryDirectory是dubbo服务注册与发现机制,主要来看这里的代码(RouterChain就不写了):

private Optional<List<Router>> toRouters(List<URL> urls) {
        if (urls == null || urls.isEmpty()) {
            return Optional.empty();
        }

        List<Router> routers = new ArrayList<>();
        for (URL url : urls) {
            if (EMPTY_PROTOCOL.equals(url.getProtocol())) {
                continue;
            }
            String routerType = url.getParameter(ROUTER_KEY);
            if (routerType != null && routerType.length() > 0) {
                url = url.setProtocol(routerType);
            }
            try {
            	//从这里开始看
                Router router = ROUTER_FACTORY.getRouter(url);
                if (!routers.contains(router)) {
                    routers.add(router);
                }
            } catch (Throwable t) {
                logger.error("convert router url to router error, url: " + url, t);
            }
        }

        return Optional.of(routers);
    }

这里会把zk上面所有的服务url都去遍历是否有路由规则的存在。
从这段代码一直往下延申

Router router = ROUTER_FACTORY.getRouter(url);

来到CacheableRouterFactory(其他的支线,我就不一一说了)

@Override
    public Router getRouter(URL url) {
        routerMap.computeIfAbsent(url.getServiceKey(), k -> createRouter(url));
        return routerMap.get(url.getServiceKey());
    }

再来到ServiceRouterFactory

@Override
    protected Router createRouter(URL url) {
        return new ServiceRouter(DynamicConfiguration.getDynamicConfiguration(), url);
    }

紧接着来到服务路由的类ServiceRouter

public ServiceRouter(DynamicConfiguration configuration, URL url) {
        super(configuration, url, DynamicConfiguration.getRuleKey(url));
        this.priority = SERVICE_ROUTER_DEFAULT_PRIORITY;
    }

最重要的就是DynamicConfiguration.getRuleKey(url)这段代码,这是决定了dubbo去zk读取路由的key,这也是跟dubbo-admin设置路由路径不一致的地方。
我们顺着往下走,来到URL.java

/**
     * The format is "{interface}:[version]:[group]"
     * @return
     */
    public String getColonSeparatedKey() {
        StringBuilder serviceNameBuilder = new StringBuilder();
        append(serviceNameBuilder, INTERFACE_KEY, true);
        append(serviceNameBuilder, VERSION_KEY, false);
        append(serviceNameBuilder, GROUP_KEY, false);
        return serviceNameBuilder.toString();
    }

这里就很清晰了,dubbo是会拿出三个参数来拼接成ruleKey的,也就是说拼接的形式是interface:version:group(还不清楚的话可以看看append方法),即使version和group为空也会存在":",举例:com.zkd.baseInterface.route.RouteService::或者com.zkd.baseInterface.route.RouteService:😗
看一下继承的父类ListenableRouter情况

public ListenableRouter(DynamicConfiguration configuration, URL url, String ruleKey) {
        super(configuration, url);
        this.force = false;
        this.init(ruleKey);
    }
    private synchronized void init(String ruleKey) {
        if (StringUtils.isEmpty(ruleKey)) {
            return;
        }
        String routerKey = ruleKey + RULE_SUFFIX;
        configuration.addListener(routerKey, this);
        //进入这getRule的方法
        String rule = configuration.getRule(routerKey, DynamicConfiguration.DEFAULT_GROUP);
        if (StringUtils.isNotEmpty(rule)) {
            this.process(new ConfigChangeEvent(routerKey, rule));
        }
    }

这边就会根据上面拼接的ruleKey去dubbo/config/dubbo下找到相应配置文件名的路由配置
ConditionRouter.java

public void init(String rule) {
        try {
            if (rule == null || rule.trim().length() == 0) {
                throw new IllegalArgumentException("Illegal route rule!");
            }
            rule = rule.replace("consumer.", "").replace("provider.", "");
            int i = rule.indexOf("=>");
            String whenRule = i < 0 ? null : rule.substring(0, i).trim();
            String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
            Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
            Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
            // NOTE: It should be determined on the business level whether the `When condition` can be empty or not.
            this.whenCondition = when;
            this.thenCondition = then;
        } catch (ParseException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
    private static Map<String, MatchPair> parseRule(String rule)
            throws ParseException {
        Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
        if (StringUtils.isBlank(rule)) {
            return condition;
        }
        // Key-Value pair, stores both match and mismatch conditions
        MatchPair pair = null;
        // Multiple values
        Set<String> values = null;
        final Matcher matcher = ROUTE_PATTERN.matcher(rule);
        while (matcher.find()) { // Try to match one by one
            String separator = matcher.group(1);
            String content = matcher.group(2);
            // Start part of the condition expression.
            if (StringUtils.isEmpty(separator)) {
                pair = new MatchPair();
                condition.put(content, pair);
            }
            // The KV part of the condition expression
            else if ("&".equals(separator)) {
                if (condition.get(content) == null) {
                    pair = new MatchPair();
                    condition.put(content, pair);
                } else {
                    pair = condition.get(content);
                }
            }
            // The Value in the KV part.
            else if ("=".equals(separator)) {
                if (pair == null) {
                    throw new ParseException("Illegal route rule \""
                            + rule + "\", The error char '" + separator
                            + "' at index " + matcher.start() + " before \""
                            + content + "\".", matcher.start());
                }

                values = pair.matches;
                values.add(content);
            }
            // The Value in the KV part.
            else if ("!=".equals(separator)) {
                if (pair == null) {
                    throw new ParseException("Illegal route rule \""
                            + rule + "\", The error char '" + separator
                            + "' at index " + matcher.start() + " before \""
                            + content + "\".", matcher.start());
                }

                values = pair.mismatches;
                values.add(content);
            }
            // The Value in the KV part, if Value have more than one items.
            else if (",".equals(separator)) { // Should be separated by ','
                if (values == null || values.isEmpty()) {
                    throw new ParseException("Illegal route rule \""
                            + rule + "\", The error char '" + separator
                            + "' at index " + matcher.start() + " before \""
                            + content + "\".", matcher.start());
                }
                values.add(content);
            } else {
                throw new ParseException("Illegal route rule \"" + rule
                        + "\", The error char '" + separator + "' at index "
                        + matcher.start() + " before \"" + content + "\".", matcher.start());
            }
        }
        return condition;
    }

至此,路由配置规则的读取完成

二、dubbo-admin路由规则的写入

这是一个非常大的bug,总之一句话,dubbo-admin开发者根本就没有对照着dubbo读取路由规则的ruleKey拼接方式来生成路径,因此导致dubbo根本就读不到规则。
本人重写了dubbo-admin写入规则的代码
1、ConditionRoutesController.java

@RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    public boolean createRule(@RequestBody ConditionRouteDTO routeDTO, @PathVariable String env) {
        String serviceName = routeDTO.getService();
        String app = routeDTO.getApplication();
        if (StringUtils.isEmpty(serviceName) && StringUtils.isEmpty(app)) {
            throw new ParamValidationException("serviceName and app is Empty!");
        }
        if (StringUtils.isNotEmpty(app) && providerService.findVersionInApplication(app).equals("2.6")) {
            throw new VersionValidationException("dubbo 2.6 does not support application scope routing rule");
        }
//        routeService.createConditionRoute(routeDTO);
        //按照上面的配置写入方法,dubbo根本就读不到相应的规则配置
        //因此我写了下面的方法
        routeService.createConditionRouteByServices(routeDTO);
        return true;
    }

请接着看
RouteServiceImpl.java

/**
     * 这个方法有BUG,自写了一个
     * @param conditionRoute
     */
    @Override
    public void createConditionRoute(ConditionRouteDTO conditionRoute) {
        String id = ConvertUtil.getIdFromDTO(conditionRoute);
        String path = getPath(id, Constants.CONDITION_ROUTE);
        setRouterConfig(path,conditionRoute);
    }

    /**
     * 这个方法是替代上面的
     * 主要是生成路径名称的规则要跟dubbo保持一致,不然dubbo读不到
     * @param conditionRoute
     */
    @Override
    public void createConditionRouteByServices(ConditionRouteDTO conditionRoute) {
        //由于dubbo那边读取路由路径的规则是根据:
        // 1、interface
        // 2、version
        // 3、group
        //因此这里生成路由规则时,serviceId的拼接规则应该是interface:version:group
        String serviceId = ConvertUtil.getIdFromDTO(conditionRoute);
        String[] params = serviceId.split(":",3);
        String serviceName = params[0];
        String version = params[1];
        String group = params[2];
        List<Provider> services = providerService.findByService(serviceName);
        conditionRoute.setService(serviceName);
        for (Provider service : services) {
            String path = getPathWithProvider(service,version,group,Constants.CONDITION_ROUTE);
            if (StringUtils.isBlank(path)) {
                continue;
            }
            System.out.println("path:" + path);
            setRouterConfig(path,conditionRoute);
        }
    }
    /**
     * 由于dubbo获取路由的key是从provider的url上根据参数拼接的
     * 所以dubbo-admin生成路由文件的路径名也得这样肝
     * @param service
     * @param version
     * @param group
     * @param type
     * @return
     */
    private String getPathWithProvider(Provider service,String version
            , String group ,String type) {
        URL url = service.toUrl();
        if (!StringUtils.isBlank(version)
                && !version.equalsIgnoreCase(url.getParameter("version"))) {
            //设置的version不为空并跟service的version不匹配,则跳过
            return "";
        }
        if (!StringUtils.isBlank(group)
                && !group.equalsIgnoreCase(url.getParameter("group"))) {
            //设置的group不为空并跟service的group不匹配,则跳过
            return "";
        }
        String pathKey = url.getColonSeparatedKey();
        if (type.equals(Constants.CONDITION_ROUTE)) {
            return prefix + Constants.PATH_SEPARATOR + pathKey + Constants.CONDITION_RULE_SUFFIX;
        } else {
            return prefix + Constants.PATH_SEPARATOR + pathKey + Constants.TAG_RULE_SUFFIX;
        }
    }
    /**
     * 设置路由规则
     * @param path
     * @param conditionRoute
     */
    private void setRouterConfig(String path,ConditionRouteDTO conditionRoute) {
        String existConfig = dynamicConfiguration.getConfig(path);
        RoutingRule existRule = null;
        if (existConfig != null) {
            existRule = YamlParser.loadObject(existConfig, RoutingRule.class);
        }
        existRule = RouteUtils.insertConditionRule(existRule, conditionRoute);
        //register2.7
        dynamicConfiguration.setConfig(path, YamlParser.dumpObject(existRule));

        //register2.6
        if (StringUtils.isNotEmpty(conditionRoute.getService())) {
            for (Route old : convertRouteToOldRoute(conditionRoute)) {
                registry.register(old.toUrl().addParameter(Constants.COMPATIBLE_CONFIG, true));
            }
        }
    }

至此,bug修改结束
另外需要说明的时,按照这个修改方法,那么在dubbo-admin中编写规则的serviceId时,请按照interface:version:group的形式拼接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值