当前位置: 代码迷 >> 综合 >> dubbo-admin路由规则设置的bug以及dubbo路由详解(基于dubbo.2.7.3)
  详细解决方案

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

热度:77   发布时间:2023-10-17 02:40:04.0

一、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(其他的支线,我就不一一说了)

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

再来到ServiceRouterFactory

@Overrideprotected 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 conditionsMatchPair pair = null;// Multiple valuesSet<String> values = null;final Matcher matcher = ROUTE_PATTERN.matcher(rule);while (matcher.find()) {
     // Try to match one by oneString 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 expressionelse 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*/@Overridepublic void createConditionRoute(ConditionRouteDTO conditionRoute) {
    String id = ConvertUtil.getIdFromDTO(conditionRoute);String path = getPath(id, Constants.CONDITION_ROUTE);setRouterConfig(path,conditionRoute);}/*** 这个方法是替代上面的* 主要是生成路径名称的规则要跟dubbo保持一致,不然dubbo读不到* @param conditionRoute*/@Overridepublic void createConditionRouteByServices(ConditionRouteDTO conditionRoute) {
    //由于dubbo那边读取路由路径的规则是根据:// 1、interface// 2、version// 3、group//因此这里生成路由规则时,serviceId的拼接规则应该是interface:version:groupString 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.7dynamicConfiguration.setConfig(path, YamlParser.dumpObject(existRule));//register2.6if (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的形式拼接

  相关解决方案