Dubbo 标签路由

开篇

  • Dubbo路由规则在发起一次RPC调用前起到过滤目标服务器地址的作用,过滤后的地址列表,将作为消费端最终发起RPC调用的备选地址。

  • 目前支持的路由包括:
    条件路由。支持以服务或Consumer应用为粒度配置路由规则。
    标签路由。以Provider应用为粒度配置路由规则。

  • 这篇文章的分析是基于Dubbo-2.6.6版本的,不同的版本实现方法会有些不一样,官方的链接路由规则


Dubbo标签路由

  • 标签路由通过将某一个或多个服务的提供者划分到同一个分组,约束流量只在指定分组中流转,从而实现流量隔离的目的,可以作为蓝绿发布、灰度发布等场景的能力基础。

  • 标签主要是指对Provider端应用实例的分组,目前有两种方式可以完成实例分组,分别是动态规则打标和静态规则打标,其中动态规则相较于静态规则优先级更高,而当两种规则同时存在且出现冲突时,将以动态规则为准。

  • 请求标签的作用域为每一次 invocation,使用 attachment 来传递请求标签,注意保存在 attachment 中的值将会在一次完整的远程调用中持续传递,得益于这样的特性,我们只需要在起始调用时,通过一行代码的设置,达到标签的持续传递。

  • 降级约定

consumer携带request.tag=tag1 时优先选择 标记了tag=tag1 的 provider。若集群中不存在与请求标记对应的服务,默认将降级请求 tag为空的provider;如果要改变这种默认行为,即找不到匹配tag1的provider返回异常,需设置request.tag.force=true。

comsumer侧request.tag未设置时,只会匹配tag为空的provider。即使集群中存在可用的服务,若tag不匹配也就无法调用,这与约定1不同,携带标签的请求可以降级访问到无标签的服务,但不携带标签/携带其他种类标签的请求永远无法访问到其他标签的服务。


Dubbo标签路由选择过程

public class TagRouter extends AbstractRouter {

    @Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
        // filter
        List<Invoker<T>> result = new ArrayList<Invoker<T>>();
        // Dynamic param
        String tag = RpcContext.getContext().getAttachment(Constants.TAG_KEY);
        // 处理consumer携带tag的情况
        if (!StringUtils.isEmpty(tag)) {
            // 优先选择携带标签的provider
            for (Invoker<T> invoker : invokers) {
                if (tag.equals(invoker.getUrl().getParameter(Constants.TAG_KEY))) {
                    result.add(invoker);
                }
            }
        }
        // 如果未指定标签tag或者携带了标签但是未找到匹配的provider的情况
        if (result.isEmpty()) {
            // 未强制指定FORCE_USE_TAG的逻辑
            String forceTag = RpcContext.getContext().getAttachment(Constants.FORCE_USE_TAG);
            if (StringUtils.isEmpty(forceTag) || "false".equals(forceTag)) {
                for (Invoker<T> invoker : invokers) {
                    // 获取没有携带标签的provider对象
                    if (StringUtils.isEmpty(invoker.getUrl().getParameter(Constants.TAG_KEY))) {
                        result.add(invoker);
                    }
                }
            }
        }
        return result;
    }
}
  • 携带标签的请求优先访问带标签的provider,再不存在携带标签的情况下降级访问到无标签的provider。

  • 针对不携带标签的请求只能访问无标签的provider。


Dubbo标签路由的举例

Provider

public class DemoServiceImpl implements DemoService {

    @Override
    public String sayHello(String name) {
        System.out.println("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
        return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
    }

}


public class Provider {

    public static void main(String[] args) throws Exception {
        //Prevent to get IPV6 address,this way only work in debug mode
        //But you can pass use -Djava.net.preferIPv4Stack=true,then it work well whether in debug mode or not
        System.setProperty("java.net.preferIPv4Stack", "true");
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-provider.xml"});
        context.start();

        System.in.read(); // press any key to exit
    }

}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- provider's application name, used for tracing dependency relationship -->
    <dubbo:application name="demo-provider"/>

    <!-- use multicast registry center to export service -->
    <dubbo:registry address="multicast://224.5.6.7:1234"/>

    <!-- use dubbo protocol to export service on port 20880 -->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!-- service implementation, as same as regular local bean -->
    <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>

    <!-- declare the service interface to be exported -->
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" tag="zzzz"/>

</beans>

consumer

public class Consumer {

    public static void main(String[] args) {
        //Prevent to get IPV6 address,this way only work in debug mode
        //But you can pass use -Djava.net.preferIPv4Stack=true,then it work well whether in debug mode or not
        System.setProperty("java.net.preferIPv4Stack", "true");
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-consumer.xml"});
        context.start();
        DemoService demoService = (DemoService) context.getBean("demoService"); // get remote service proxy
        // 携带tag路由
        RpcContext.getContext().setAttachment("dubbo.tag", "zzzz");

        while (true) {
            try {
                Thread.sleep(1000);
                String hello = demoService.sayHello("world"); // call remote method
                System.out.println(hello); // get result

            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }

    }
}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- consumer's application name, used for tracing dependency relationship (not a matching criterion),
    don't set it same as provider -->
    <dubbo:application name="demo-consumer"/>

    <!-- use multicast registry center to discover service -->
    <dubbo:registry address="multicast://224.5.6.7:1234"/>

    <!-- generate proxy for the remote service, then demoService can be used in the same way as the
    local regular interface -->
    <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService"/>

</beans>
dubbo://192.168.1.5:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true
&application=demo-provider
&bean.name=com.alibaba.dubbo.demo.DemoService&default.dubbo.tag=xxx&dubbo=2.0.2
&dubbo.tag=zzzzzz&generic=false
&interface=com.alibaba.dubbo.demo.DemoService
&methods=sayHello&pid=82561&side=provider&timestamp=1577466134212


Dubbo标签路由的坑

@Activate(group = Constants.CONSUMER, order = -10000)
public class ConsumerContextFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        RpcContext.getContext()
                .setInvoker(invoker)
                .setInvocation(invocation)
                .setLocalAddress(NetUtils.getLocalHost(), 0)
                .setRemoteAddress(invoker.getUrl().getHost(),
                        invoker.getUrl().getPort());
        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(invoker);
        }
        try {
            RpcResult result = (RpcResult) invoker.invoke(invocation);
            RpcContext.getServerContext().setAttachments(result.getAttachments());
            return result;
        } finally {
            // 清空attachments
            RpcContext.getContext().clearAttachments();
        }
    }

}
  • dubbo的consumer侧的Filter对象ConsumerContextFilter每次请求后都会清空Attachments,导致再次发起请求就无法找到tag,所以需要在整个生命周期内保存tag。一般通过线程的ThreadLocal进行实现。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 部署服务 启动两个provider,一个consumer。其中一个provider修改配置文件端口为20881打包...
    半个橙子阅读 3,009评论 0 2
  • 官方文档 快速启动 关键配置 启动类 XML配置-标签的含义-配置的优先级 dubbo-admin搭建-githu...
    巨子联盟阅读 19,759评论 3 7
  • 先看官网两张图【引用来自官网】:image.png 官网说明: 1.首先 ReferenceConfig 类的 i...
    致虑阅读 1,021评论 0 2
  • 一、Dubbo简介 Dubbo是Alibaba开源的分布式服务框架,它按照分层的方式来架构,使用这种方式可以使各层...
    落地生涯阅读 2,212评论 0 9
  • 2019-12-08 晴 星期天 运动X20 给自己定的目标是元旦之前写一篇论文出来。 不知道是太久没有看论文,还...
    茉莉莫阅读 114评论 0 0