Dubbo 標(biāo)簽路由

開(kāi)篇

  • Dubbo路由規(guī)則在發(fā)起一次RPC調(diào)用前起到過(guò)濾目標(biāo)服務(wù)器地址的作用,過(guò)濾后的地址列表,將作為消費(fèi)端最終發(fā)起RPC調(diào)用的備選地址。

  • 目前支持的路由包括:
    條件路由。支持以服務(wù)或Consumer應(yīng)用為粒度配置路由規(guī)則。
    標(biāo)簽路由。以Provider應(yīng)用為粒度配置路由規(guī)則。

  • 這篇文章的分析是基于Dubbo-2.6.6版本的,不同的版本實(shí)現(xiàn)方法會(huì)有些不一樣,官方的鏈接路由規(guī)則。


Dubbo標(biāo)簽路由

  • 標(biāo)簽路由通過(guò)將某一個(gè)或多個(gè)服務(wù)的提供者劃分到同一個(gè)分組,約束流量只在指定分組中流轉(zhuǎn),從而實(shí)現(xiàn)流量隔離的目的,可以作為藍(lán)綠發(fā)布、灰度發(fā)布等場(chǎng)景的能力基礎(chǔ)。

  • 標(biāo)簽主要是指對(duì)Provider端應(yīng)用實(shí)例的分組,目前有兩種方式可以完成實(shí)例分組,分別是動(dòng)態(tài)規(guī)則打標(biāo)和靜態(tài)規(guī)則打標(biāo),其中動(dòng)態(tài)規(guī)則相較于靜態(tài)規(guī)則優(yōu)先級(jí)更高,而當(dāng)兩種規(guī)則同時(shí)存在且出現(xiàn)沖突時(shí),將以動(dòng)態(tài)規(guī)則為準(zhǔn)。

  • 請(qǐng)求標(biāo)簽的作用域?yàn)槊恳淮?invocation,使用 attachment 來(lái)傳遞請(qǐng)求標(biāo)簽,注意保存在 attachment 中的值將會(huì)在一次完整的遠(yuǎn)程調(diào)用中持續(xù)傳遞,得益于這樣的特性,我們只需要在起始調(diào)用時(shí),通過(guò)一行代碼的設(shè)置,達(dá)到標(biāo)簽的持續(xù)傳遞。

  • 降級(jí)約定

consumer攜帶request.tag=tag1 時(shí)優(yōu)先選擇 標(biāo)記了tag=tag1 的 provider。若集群中不存在與請(qǐng)求標(biāo)記對(duì)應(yīng)的服務(wù),默認(rèn)將降級(jí)請(qǐng)求 tag為空的provider;如果要改變這種默認(rèn)行為,即找不到匹配tag1的provider返回異常,需設(shè)置request.tag.force=true。

comsumer側(cè)request.tag未設(shè)置時(shí),只會(huì)匹配tag為空的provider。即使集群中存在可用的服務(wù),若tag不匹配也就無(wú)法調(diào)用,這與約定1不同,攜帶標(biāo)簽的請(qǐng)求可以降級(jí)訪問(wèn)到無(wú)標(biāo)簽的服務(wù),但不攜帶標(biāo)簽/攜帶其他種類標(biāo)簽的請(qǐng)求永遠(yuǎn)無(wú)法訪問(wèn)到其他標(biāo)簽的服務(wù)。


Dubbo標(biāo)簽路由選擇過(guò)程

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)) {
            // 優(yōu)先選擇攜帶標(biāo)簽的provider
            for (Invoker<T> invoker : invokers) {
                if (tag.equals(invoker.getUrl().getParameter(Constants.TAG_KEY))) {
                    result.add(invoker);
                }
            }
        }
        // 如果未指定標(biāo)簽tag或者攜帶了標(biāo)簽但是未找到匹配的provider的情況
        if (result.isEmpty()) {
            // 未強(qiáng)制指定FORCE_USE_TAG的邏輯
            String forceTag = RpcContext.getContext().getAttachment(Constants.FORCE_USE_TAG);
            if (StringUtils.isEmpty(forceTag) || "false".equals(forceTag)) {
                for (Invoker<T> invoker : invokers) {
                    // 獲取沒(méi)有攜帶標(biāo)簽的provider對(duì)象
                    if (StringUtils.isEmpty(invoker.getUrl().getParameter(Constants.TAG_KEY))) {
                        result.add(invoker);
                    }
                }
            }
        }
        return result;
    }
}
  • 攜帶標(biāo)簽的請(qǐng)求優(yōu)先訪問(wèn)帶標(biāo)簽的provider,再不存在攜帶標(biāo)簽的情況下降級(jí)訪問(wèn)到無(wú)標(biāo)簽的provider。

  • 針對(duì)不攜帶標(biāo)簽的請(qǐng)求只能訪問(wèn)無(wú)標(biāo)簽的provider。


Dubbo標(biāo)簽路由的舉例

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標(biāo)簽路由的坑

@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側(cè)的Filter對(duì)象ConsumerContextFilter每次請(qǐng)求后都會(huì)清空Attachments,導(dǎo)致再次發(fā)起請(qǐng)求就無(wú)法找到tag,所以需要在整個(gè)生命周期內(nèi)保存tag。一般通過(guò)線程的ThreadLocal進(jìn)行實(shí)現(xiàn)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 部署服務(wù) 啟動(dòng)兩個(gè)provider,一個(gè)consumer。其中一個(gè)provider修改配置文件端口為20881打包...
    半個(gè)橙子閱讀 3,098評(píng)論 0 2
  • 官方文檔 快速啟動(dòng) 關(guān)鍵配置 啟動(dòng)類 XML配置-標(biāo)簽的含義-配置的優(yōu)先級(jí) dubbo-admin搭建-githu...
    巨子聯(lián)盟閱讀 20,010評(píng)論 3 7
  • 先看官網(wǎng)兩張圖【引用來(lái)自官網(wǎng)】:image.png 官網(wǎng)說(shuō)明: 1.首先 ReferenceConfig 類的 i...
    致慮閱讀 1,091評(píng)論 0 2
  • 一、Dubbo簡(jiǎn)介 Dubbo是Alibaba開(kāi)源的分布式服務(wù)框架,它按照分層的方式來(lái)架構(gòu),使用這種方式可以使各層...
    落地生涯閱讀 2,355評(píng)論 0 9
  • 2019-12-08 晴 星期天 運(yùn)動(dòng)X20 給自己定的目標(biāo)是元旦之前寫(xiě)一篇論文出來(lái)。 不知道是太久沒(méi)有看論文,還...
    茉莉莫閱讀 175評(píng)論 0 0

友情鏈接更多精彩內(nèi)容