• 远程过程调用RPC 5:Dubbo路由


    prev:远程过程调用RPC 4:RPC服务框架Dubbo

    Dubbo路由概述

    dubbo2.7官方文档-旧路由规则
    Dubbo在服务治理方面提供了路由功能,主要是决定一次Dubbo服务调用时调用的目标服务器。
    dubbo主要支持三种路由方式:

    • 条件路由
    • 脚本路由
    • 标签路由
    • 扩展SPI实现自定义路由

    条件路由

    条件路由是基于条件表达式的路由规则。大致语法是以=>符号进行分隔:

    • => 之前的为消费者匹配条件,所有参数和消费者的 URL 进行对比,当消费者满足匹配条件时,对该消费者执行后面的过滤规则
    • => 之后为提供者地址列表的过滤条件,所有参数和提供者的 URL 进行对比,消费者最终只拿到过滤后的地址列表
    • 符号前后可以为空,表示对提供方/消费方进行不加限制/禁止访问

    举个简单的例子:

    # 表示允许IP为10.20.153.10访问IP为10.20.153.11的服务
    host = 10.20.153.10 => host = 10.20.153.11
    #表示允许IP为10.20.153.10访问IP为10.20.153.10、10.20.153.11之外的服务
    host = 10.20.153.10 => host != 10.20.153.10,10.20.153.11
    # 表示任何访问不得访问IP为172.22网段的服务
    => host != 172.22.*
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    此种路由方式支持对method、argument、protocol、host、port、application、organization上的参数进行区分

    脚本路由

    脚本路由无法在dubbo控制台上进行配置,脚本路由规则支持JDK脚本引擎的所有脚本,比如:javascript, jruby, groovy 等,通过type=javascript参数设置脚本类型,缺省为 javascript。

    demo

    本例中,涉及两个provider对外提供rpc服务,使用consumer对服务进行消费,使用的例子是之前远程过程调用RPC 4:RPC服务框架Dubbo的进一步深入。

    结构

    在这里插入图片描述
    主要涉及两个provider和一个consumer。分别是三个Spring Boot的模块。

    provider

    provider的提供的接口是ProviderService,定义在com.mxb.provider.service下:

    public interface ProviderService {
        String sayHello(String word);
    }
    
    • 1
    • 2
    • 3

    其实现功能为:返回一个字符串,内容为hello + 参数的word。实现在com.mxb.provider.service下:

    public class ProviderServiceImpl implements ProviderService{
        @Override
        public String sayHello(String word) {
            return "hello" + word;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    provider的配置文件定义在resource下的META-INF/spring文件夹下,内容为:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
           xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
        <!--唯一名称,计算依赖关系的标签-->
        <dubbo:application name="provider" owner="mxb"></dubbo:application>
        <dubbo:monitor protocol="registry"/>
        <!--dubbo服务所要暴露的服务地址所对应的注册中心-->
        <dubbo:registry address="zookeeper://127.0.0.1:2181" />
        <!--当前服务发布所依赖的协议;webserovice、Thrift、Hessain、http-->
        <dubbo:protocol name="dubbo" port="20881"/>
        <!--服务发布的配置,需要暴露的服务接口-->
        <dubbo:service
                interface="com.mxb.provider.service.ProviderService"
                ref="providerService"
        />
        <!--Bean bean定义-->
        <bean id="providerService" class="com.mxb.provider.service.ProviderServiceImpl"/>
    </beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    可以看出,注册在了我本地2181端口上的zookeeper上,provider01的端口为20881,服务名为provider。
    同样的,provider02的实现类似,不过返回的字符串为灰度测试hello+参数name,模拟提供灰度服务:

    public class ProviderServiceImpl implements ProviderService{
        @Override
        public String sayHello(String word) {
            return "灰度测试    hello" + word;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    provider01的端口为20882,服务名为provider
    启动类为:

    @SpringBootApplication
    public class ProviderApplication {
        public static void main(String[] args) throws IOException {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/myprovider.xml");
            context.start();
            System.in.read();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    拉起来两个服务后,dubbo控制台可以看到:
    在这里插入图片描述

    consumer

    在这里插入图片描述
    在消费端,消费这两个服务:

    public class ConsumerApplication {
    
        public static void main(String[] args) throws IOException, InterruptedException {
            ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("consumer.xml");
            context.start();
            ProviderService providerService = (ProviderService) context.getBean("providerService");
            while (true)
            {
                String str = providerService.sayHello(" mxb");
                System.out.println(str);
                str = providerService.sayHello(" sgj");
                System.out.println(str);
                Thread.sleep(1500);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    consumer.xml定义如下:

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
           xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
        <dubbo:application name="consumer" owner="mxb"/>
        <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
        <dubbo:monitor protocol="registry"/>
        <dubbo:reference id="providerService"
                         interface="com.mxb.provider.service.ProviderService"/>
    </beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在运行时,效果为随机执行两个服务的逻辑:
    在这里插入图片描述

    引入路由脚本

    为了简单起见,这里我们直接向zookeeper上写入router数据,zookeeper上路由配置在/dubbo/com.mxb.provider.service.ProviderService/routers里面,从客户端可以看出目前无路由策略:
    在这里插入图片描述
    直接将脚本注册到zookeeper上:

    import com.alibaba.dubbo.common.URL;
    import com.alibaba.dubbo.registry.Registry;
    import com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry;
    import com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistryFactory;
    import com.alibaba.dubbo.remoting.zookeeper.curator.CuratorZookeeperTransporter;
    
    public class ZkConfig {
        public static void main(String[] args) {
            URL registryUrl = URL.valueOf("zookeeper://127.0.0.1:2181");
            ZookeeperRegistryFactory zookeeperRegistryFactory = new
                    ZookeeperRegistryFactory();
            zookeeperRegistryFactory.setZookeeperTransporter(new
                    CuratorZookeeperTransporter());
            Registry zookeeperRegistry = (ZookeeperRegistry)
                    zookeeperRegistryFactory.createRegistry(registryUrl);
            URL routerURL =
                    URL.valueOf("script://0.0.0.0/com.mxb.provider.service.ProviderService?category=routers&dynamic=false&enabled=true&force=false&priority=0");
            routerURL = routerURL.addParameter("rule",URL.encode("(function route(invokers) {\n" +
                    "    var result = new java.util.ArrayList(invokers.size());\n" +
                    "    for (i = 0; i < invokers.size(); i ++) {\n" +
                    "        if (\"30.22.97.17:20882\".equals(invokers.get(i).getUrl().getAddress())) {\n" +
                    "            result.add(invokers.get(i));\n" +
                    "        }\n" +
                    "    }\n" +
                    "    return result;\n" +
                    "} (invokers));  ") );
            zookeeperRegistry.register(routerURL);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    这里的脚本如下:

    (function route(invokers) {
        var result = new java.util.ArrayList(invokers.size());
        for (i = 0; i < invokers.size(); i ++) {
            if ("30.22.97.17:20882".equals(invokers.get(i).getUrl().getAddress())) {
                result.add(invokers.get(i));
            }
        }
        return result;
    } (invokers));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里的invokers是全部的服务,这里脚本的意思是将address为30.22.96.238:20882的加入list并返回,表述只能访问30.22.96.238:20882的服务。
    执行后,可以看到zookeeper的/dubbo/com.mxb.provider.service.ProviderService/routers下已经有了信息:
    在这里插入图片描述
    那么在使用消费者的时候,只能访问端口20882下灰度的服务:
    在这里插入图片描述
    那么,能否实现根据参数的内容进行路由呢,采用下面的脚本进行测试:参考Dubbo服务治理之灰度发布方案(版本发布控制影响范围)

    (function route(invokers, invocation) {
        var serviceInvokers = new java.util.ArrayList(invokers.size());
        var grayInvokers = new java.util.ArrayList(invokers.size());
        for (i=0;i<invokers.size(); i++) {  
            if ("30.22.97.17:20881".equals(invokers.get(i).getUrl().getAddress())) {
                serviceInvokers.add(invokers.get(i));
            }else{
                grayInvokers.add(invokers.get(i));
            }
        }
        var invArguments = invocation.getArguments();
       // 进入了此分支
        if(invArguments == null || invArguments.length == 0) {
            return serviceInvokers;
        }
        var name = invArguments[0];
        if(name == "mxb") {
            return grayInvokers;
        }else{
            return serviceInvokers;
        }
    }(invokers, invocation) );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这里的invocation参数的invocation.getArguments()是获取接口的参数,详见:源码分析。
    但是在注释那块就进入了分支,也就是说并没有获得接口参数。尝试失败

    标签路由

    标签路由引入自dubbo2.7:官方文档-标签路由
    运行下面demo之前记得清楚zookeeper上的router规则:

    deleteall /dubbo/com.mxb.provider.service.ProviderService/routers
    
    • 1

    动态打标签

    来自官方文档的例子:

    force: false
      runtime: true
      enabled: true
      key: governance-tagrouter-provider
      tags:
        - name: tag1
          addresses: ["127.0.0.1:20880"]
        - name: tag2
          addresses: ["127.0.0.1:20881"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这样就给127.0.0.1:20880上的governance-tagrouter-provider服务打上了tag1标签。

    静态打标签

    修改配置文件,在本项目中,在provider02的配置文件中加入:

        <dubbo:provider tag="grey"/>
    
    • 1

    给provider02打上了grey标签
    在consumer调用的时候:

    public class ConsumerApplication {
        public static void main(String[] args) throws IOException, InterruptedException {
            ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("consumer.xml");
            context.start();
            ProviderService providerService = (ProviderService) context.getBean("providerService");
            while (true)
            {
                RpcContext.getContext().setAttachment(Constants.TAG_KEY,"grey");
                String str = providerService.sayHello(" mxb");
                System.out.println(str);
                str = providerService.sayHello(" sgj");
                System.out.println(str);
                Thread.sleep(1500);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    也就是在每次调用之前,加入Tag grey,因为这个标签仅一次有效,效果为应该是mxb的为灰度测试,sgj的为普通效果,如下:
    在这里插入图片描述
    可以看出tag发挥了作用

    扩展SPI自定义路由

    支持扩展SPI自定义路由规则:官方文档-路由扩展
    demo没跑通,在调试。

    参考

    官方文档-路由扩展
    官方文档-路由规则
    Dubbo自定义路由
    Dubbo3.0如何实现进行路由扩展
    深度解析dubbo集群路由之脚本路由
    Dubbo-Router条件路由、脚本路由使用
    dubbo设置自定义路由
    [Dubbo] 源码解析之服务路由策略
    Dubbo服务治理之灰度发布方案(版本发布控制影响范围)
    Curator基本操作
    Dubbo 路由规则之标签路由

  • 相关阅读:
    【计算机基础】优雅的PPT就应该这样设计
    开发工程师必备————【Day32】Django补充(七)
    毕业后做了两年软件测试,只会功能测试,难道真的要去工地搬砖或者去做销售吗?
    keil报错:Flash Download failed - Could not load file‘..\..\Output\Template.axf
    CSS位置偏移反爬虫绕过
    第3章-8 字符串逆序
    【mysql】linux安装安装 mysql
    Jetsonnano B01 笔记2:GPIO库-Pycharm远程调试-点灯
    MySQL学习笔记5
    快速安装protoc
  • 原文地址:https://blog.csdn.net/mxb1234567/article/details/125446091