Dubbo 自定义路由

Dubbo 提供了丰富的流量管控策略:

  • 地址发现与负载均衡,地址发现支持服务实例动态上下线,负载均衡确保流量均匀的分布到每个实例上。
  • 基于路由规则的流量管控,路由规则对每次请求进行条件匹配,并将符合条件的请求路由到特定的地址子集。

服务发现保证调用方看到最新的提供方实例地址,服务发现机制依赖注册中心 (Zookeeper、Nacos、Istio 等)
实现。在消费端,Dubbo 提供了多种负载均衡策略,如随机负载均衡策略、一致性哈希负载、基于权重的轮询、最小活跃度优先、P2C
等。

Dubbo
的流量管控规则可以基于应用、服务、方法、参数等粒度精准的控制流量走向,根据请求的目标服务、方法以及请求体中的其他附加参数进行匹配,符合匹配条件的流量会进一步的按照特定规则转发到一个地址子集。流量管控规则有以下几种:

  • 条件路由规则
  • 标签路由规则
  • 脚本路由规则
  • 动态配置规则

说明

由于体验dubbo admin,当前对不具备生产使用的能力。线上误操作容易错。本章节对其成熟稳定的功能进行测试。

  • 标签路由:静态打标的方式
  • 自定义路由:日常我们所需的就近路由。

静态标签路由

provider配置标签,只能有一个。默认场景下会自动找到按标签匹配。

原理逻辑在TagStateRouter,如果未找到默认会找不携带 tag 的provider.

dubbo:
  provider:
    tag: tag-1

consumer配置

dubbo:
  consumer:
    tag: tag-1

实际作为消费者有时候需要强制路由到对应tag。可以添加dubbo.tag.force: true的parameters参数。

按dubbo parameters参数设计,可以放到application ,consumer,reference 节点。

dubbo:
  application:
    name: ${spring.application.name}
    # wait ms
    shut-wait: 30000
  registry:
    address: nacos://localhost:8848
    parameters:
      # 纯消费者时候可以注册上去
      register-consumer-url: true
  consumer:
    # ms
    timeout: 2000
    retries: 0
    # 优先级比parameters高,但实际使用通常针对单个provider设置
    tag: tag-1
  reference:
    com.seezoon.protocol.user.server.domain.UserService:
      # 直连使用url
      #url: tri://192.168.1.28:9000
      parameters:
        dubbo.tag: tagxxx
        dubbo.force.tag: true

consumer也可以通过编程方式设置tag

// 优先级高
RpcContext.getContext().setAttachment(Constants.TAG_KEY,"tag1");
RpcContext.getContext().setAttachment(Constants.FORCE_USE_TAG,"true");

自定义路由

和插件扩展原理一样,创建文件META-INF/dubbo/org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory
放入文本文件

metaRouter=com.seezoon.dubbo.router.MetaStateRouterFactory

主要是继承CacheableStateRouterFactory

/**
 * 元数据路由
 *
 * @author dfenghuang
 * @date 2023/3/27 23:40
 */
@Activate
public class MetaStateRouterFactory extends CacheableStateRouterFactory {

    @Override
    protected <T> StateRouter<T> createRouter(Class<T> interfaceClass, URL url) {
        Map<String, String> routerMetas = url.getParameters(v -> v.startsWith("router.meta"));
        RouterMeta routerMeta = new RouterMeta();
        Map<String, String> customMeta = new HashMap<>(routerMetas.size());
        routerMetas.forEach((k, v) -> {
            if (RouterMeta.CONSUMER_SET_KEY.equals(k)) {
                routerMeta.setSet(v);
            } else if (RouterMeta.CONSUMER_IDC_KEY.equals(k)) {
                routerMeta.setIdc(v);
            } else if (RouterMeta.CONSUMER_CITY_KEY.equals(k)) {
                routerMeta.setCity(v);
            } else if (RouterMeta.NEARBY_ROUTE_KEY.equals(k)) {
                routerMeta.setNearbyRoute(
                        StringUtils.isNotEmpty(v) ? Boolean.parseBoolean(v) : true);
            } else {
                customMeta.put(k.substring(RouterMeta.ROUTER_PREFIX.length()), v);
            }
        });
        routerMeta.setCustomMeta(customMeta);
        routerMeta.setNearbyMetaEmpty(StringUtils.isEmpty(routerMeta.getSet())
                && StringUtils.isEmpty(routerMeta.getIdc())
                && StringUtils.isEmpty(routerMeta.getCity()));
        routerMeta.setEmpty(routerMeta.isNearbyMetaEmpty() && customMeta.isEmpty());
        return new MetaStateRouter(routerMeta, url);
    }
}

主要继承AbstractStateRouter

/**
 * 元数据路由
 * </p>
 *
 * <pre>
 * 1. 如果设置了自定义的元数据,元数据需要严格匹配;
 * 2. 如果设置了就近路由元数据,按set->idc->city的维度逐步找
 * 3. 如果关闭就近路由,就近的元数据存在的话,也需要严格匹配到
 * </pre>
 *
 * @author dfenghuang
 * @date 2023/3/27 23:42
 */
@Slf4j
public class MetaStateRouter<T> extends AbstractStateRouter<T> {

    private static final int size = 5;
    private final RouterMeta routerMeta;

    public MetaStateRouter(RouterMeta routerMeta, URL url) {
        super(url);
        this.routerMeta = routerMeta;
    }

    @Override
    protected BitList<Invoker<T>> doRoute(BitList<Invoker<T>> invokers, URL url,
            Invocation invocation,
            boolean needToPrintMessage, Holder<RouterSnapshotNode<T>> routerSnapshotNodeHolder,
            Holder<String> messageHolder) throws RpcException {
        if (routerMeta.isEmpty()) {
            return invokers;
        }
        if (log.isDebugEnabled()) {
            log.debug("consumer:{},router meta:{}", url, routerMeta);
        }
        int minSize = Math.min(size, invokers.size());
        List<Invoker<T>> result = new ArrayList<>(invokers.size());
        List<Invoker<T>> setResult = new ArrayList<>(minSize);
        List<Invoker<T>> idcResult = new ArrayList<>(minSize);
        List<Invoker<T>> cityResult = new ArrayList<>(minSize);
        String consumerSet = invocation.getAttachment(RouterMeta.CONSUMER_SET_KEY,
                routerMeta.getSet());
        String consumerIdc = invocation.getAttachment(RouterMeta.CONSUMER_IDC_KEY,
                routerMeta.getIdc());
        String consumerCity = invocation.getAttachment(RouterMeta.CONSUMER_CITY_KEY,
                routerMeta.getCity());
        for (Invoker<T> invoker : invokers) {
            Map<String, String> parameters = invoker.getUrl().getParameters();
            // 自定义的元数据必须匹配
            boolean customMetaMatched = true;
            for (Entry<String, String> entry : routerMeta.getCustomMeta().entrySet()) {
                if (!Objects.equals(entry.getValue(), parameters.get(entry.getKey()))) {
                    customMetaMatched = false;
                }
            }
            if (!customMetaMatched) {
                continue;
            }
            if (routerMeta.isNearbyMetaEmpty()) {
                continue;
            }
            String providerSet = parameters.get(RouterMeta.SET);
            String providerIdc = parameters.get(RouterMeta.IDC);
            String providerCity = parameters.get(RouterMeta.CITY);
            // 不开就近,就需要全匹配
            if (!routerMeta.isNearbyRoute()) {
                if (StringUtils.isNotEmpty(consumerSet) && !Objects.equals(consumerSet,
                        providerSet)) {
                    continue;
                }
                if (StringUtils.isNotEmpty(consumerIdc) && !Objects.equals(consumerIdc,
                        providerIdc)) {
                    continue;
                }
                if (StringUtils.isNotEmpty(consumerCity) && !Objects.equals(consumerCity,
                        providerCity)) {
                    continue;
                }
                result.add(invoker);
                continue;
            }
            if (StringUtils.isNotEmpty(consumerSet) && Objects.equals(consumerSet,
                    providerSet)) {
                setResult.add(invoker);
                continue;
            }
            if (StringUtils.isNotEmpty(consumerIdc) && Objects.equals(consumerIdc,
                    providerIdc)) {
                idcResult.add(invoker);
                continue;
            }
            if (StringUtils.isNotEmpty(consumerCity) && Objects.equals(consumerCity,
                    providerCity)) {
                cityResult.add(invoker);
            }
        }
        // 如果不开就近
        if (!routerMeta.isNearbyRoute()) {
            return new BitList<>(result);
        }
        // 或者距离标签配置则返回全量
        if (routerMeta.isNearbyMetaEmpty()) {
            return new BitList<>(result);
        }

        // 就近路由
        if (!setResult.isEmpty()) {
            log.debug("use set nearby meta:{}", consumerSet);
            return new BitList<>(setResult);
        }
        if (!idcResult.isEmpty()) {
            log.debug("use idc nearby meta:{}", consumerIdc);
            return new BitList<>(idcResult);
        }
        if (!cityResult.isEmpty()) {
            log.debug("use city nearby meta:{}", consumerCity);
            return new BitList<>(cityResult);
        }
        log.debug("can not find any nearby provider");
        return new BitList<>(result);
    }

    @Override
    public void stop() {
        super.stop();
    }

    @Override
    public boolean isRuntime() {
        return false;
    }

    @Override
    public boolean isForce() {
        return true;
    }
}

示例

Provider 配置

dubbo: 
  provider:
    parameters:
      # 服务提供者元数据,消费者根据这个路由
      meta.set: set1  # 单元
      meta.idc: idc1  # 机房
      meta.city: city1 #城市
      meta.tenant: custom-meta

Consumer 配置

dubbo:
  consumer:
    # ms
    timeout: 2000
    retries: 0
    tag: consumerTag
    parameters:
      # 路由元数据
      router.meta.set: set1
      router.meta.idc: idc1
      router.meta.city: city1
      # 默认是true
      router.meta.nearbyRoute: false
      router.meta.tenant: custom-meta
  reference:
    com.seezoon.protocol.user.server.domain.UserService:
      parameters:
        # 路由元数据
        router.meta.set: set1
        router.meta.idc: idc1
        router.meta.city: city1
        # 默认是true
        router.meta.nearbyRoute: false
        router.meta.tenant: custom-meta