美文网首页dubbo
Dubbo 标签路由

Dubbo 标签路由

作者: 晴天哥_王志 | 来源:发表于2019-12-28 13:54 被阅读0次

    开篇

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

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

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

    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进行实现。

    相关文章

      网友评论

        本文标题:Dubbo 标签路由

        本文链接:https://www.haomeiwen.com/subject/hczdoctx.html