Dubbo 自定义路由

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

/**
 * 元数据路由
 * 

* *
 * 1. 如果设置了自定义的元数据,元数据需要严格匹配;
 * 2. 如果设置了就近路由元数据,按set->idc->city的维度逐步找
 * 3. 如果关闭就近路由,就近的元数据存在的话,也需要严格匹配到
 * 
* * @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

源代码

dubbo自定义就近路由

你可能感兴趣的:(框架,dubbo,java)