Dubbo 编程指南-完整版本

Apache Dubbo

介绍

背景

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。
在这里插入图片描述

单一应用架构 - 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

垂直应用架构 - 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

分布式服务架构 - 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

流动计算架构 - 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

问题

在大规模服务化之前,应用可能只是通过 RMI 或 Hessian 等工具,简单的暴露和引用远程服务,通过配置服务的URL地址进行调用,通过 F5 等硬件进行负载均衡。当服务越来越多时,服务 URL 配置管理变得非常困难,F5 硬件负载均衡器的单点压力也越来越大。 此时需要一个服务注册中心,动态地注册和发现服务,使服务的位置透明。并通过在消费方获取服务提供方地址列表,实现软负载均衡和 Failover,降低对 F5 硬件负载均衡器的依赖,也能减少部分成本。

当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。 这时,需要自动画出应用间的依赖关系图,以帮助架构师理清关系。

接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阈值,记录此时的访问量,再以此访问量乘以机器数反推总容量。
在这里插入图片描述

Apache Dubbo 是一款高性能、轻量级的开源 Java 服务框架,Apache Dubbo |ˈdʌbəʊ| 提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。
在这里插入图片描述

架构

在这里插入图片描述

角色作用
Provider暴露服务的服务提供方
Consumer调用远程服务的服务消费方
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心
Container服务运行容器
  1. 服务容器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。

连通性

  • 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
  • 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
  • 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
  • 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
  • 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
  • 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
  • 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
  • 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者

健壮性

  • 监控中心宕掉不影响使用,只是丢失部分采样数据
  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

伸缩性

  • 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
  • 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者

升级性

当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构:
在这里插入图片描述

节点角色说明

节点角色说明
Deployer自动部署服务的本地代理
Repository仓库用于存储服务应用发布包
Scheduler调度中心基于访问压力自动增减服务提供者
Admin统一管理控制台
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心

快速入门

1、引入依赖

<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.7.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.8.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.16.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.25</version>
</dependency>

2、导入log4j.properties

###set log levels###
log4j.rootLogger=info, stdout
###output to the console###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%d{dd/MM/yy hh:mm:ss:sss z}] %t %5p %c{2}: %m%

配置版本

①服务提供者

application.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="com.baizhi.service.impl.UserService"/>
    <!--引入Dubbo服务-->
    <import resource="dubbo-provider.xml"/>
</beans>

dubbo-provider.xml

<?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://dubbo.apache.org/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application id="app1" name="dubbo-user-service"/>
    <dubbo:registry id="zk_registry" address="zookeeper://CentOS7:2181" client="curator"/>
    <dubbo:protocol id="dubbo_protocol" name="dubbo"  port="20880"/>

    <dubbo:service interface="com.baizhi.service.IUserService" ref="userService"
                   protocol="dubbo_protocol"
                   application="app1"
                   registry="zk_registry"/>

</beans>

启动服务

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
ctx.start();
System.out.println("Provider started!");
System.in.read();

②消费者

application.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--引入Dubbo服务-->
    <import resource="dubbo-consumer.xml"/>
</beans>

dubbo-consumer.xml

<?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://dubbo.apache.org/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application id="app1" name="dubbo-user-consumer" >
        <dubbo:parameter key="qos.enable" value="false"/>
    </dubbo:application>
    <dubbo:registry id="zk_registry" address="zookeeper://CentOS7:2181" client="curator"/>
    
    <dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
                     registry="zk_registry"
                     application="app1"/>
</beans>

测试服务

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
ctx.start();
IUserService userService = ctx.getBean("userService", IUserService.class);
userService.save(new User());

注解版测试

①服务提供者

DubboApplicationConfiguration.java

@Configuration
@ComponentScans(value = {@ComponentScan(basePackages = {"com.baizhi"})})
@EnableDubbo(scanBasePackages = {"com.baizhi.service.impl"})
public class DubboApplicationConfiguration {
    @Bean(name = "application")
    public ApplicationConfig application(){
        ApplicationConfig application = new ApplicationConfig();
        application.setName("user-service-annotation");
        application.setQosEnable(false);
        return application;
    }
    @Bean(name="registry")
    public RegistryConfig registry(){
        RegistryConfig registry = new RegistryConfig();
        registry.setProtocol("zookeeper");
        registry.setAddress("CentOS7:2181");
        registry.setClient("curator");
        return registry;
    }
    @Bean(name="dubbo_protocal")
    public ProtocolConfig protocol(){
        ProtocolConfig protocol = new ProtocolConfig();
        protocol.setName("dubbo");
        protocol.setPort(20880);
        protocol.setThreads(150);
        return protocol;
    }
}

这里的配置的三个Bean对象可以通过dubbo-provider.properties替代

dubbo.application.id=app1
dubbo.application.name=annotation-properties-provider

dubbo.registry.id=zk_registry
dubbo.registry.protocol=zookeeper
dubbo.registry.address=CentOS7:2181
dubbo.registry.client=curator

dubbo.protocol.id=dubbo_protocol
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

注意目前properties版本在配置多个注册中心和多个协议的解析上存在bug,大家可以参考http://dubbo.apache.org/docs/v2.7/user/configuration/properties/说明,在这里https://github.com/apache/dubbo/issues/419问题依旧没有解决!

然后在配置类上添加@PropertySource(value = "classpath:dubbo-provider.properties")

@Configuration
@ComponentScans(value = {@ComponentScan(basePackages = {"com.baizhi"})})
@EnableDubbo(scanBasePackages = {"com.baizhi.service.impl"})
@PropertySource(value = "classpath:dubbo-provider.properties")
public class DubboApplicationConfiguration {
    
}

UserService.java

@Service
@DubboService(protocol = "dubbo_protocal",registry = "zk_registry",application = "app1")
public class UserService implements IUserService {
    public void save(User user) {
        System.out.println(user+" saved...");
    }
}

启动服务

AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(DubboApplicationConfiguration.class);
ctx.start();
System.out.println("Provider started!");
System.in.read();

②消费者

DubboApplicationConfiguration.java

@Configuration
@EnableDubbo(scanBasePackages = {"com.baizhi.service"})
public class DubboApplicationConfiguration {

    @DubboReference(id = "userService",application="application",registry = "zookeeper_registry")
    private IUserService userService;

    @Bean(name = "application")
    public ApplicationConfig application(){
        ApplicationConfig application = new ApplicationConfig();
        application.setName("user-service-annotation");
        application.setQosEnable(false);
        return application;
    }
    @Bean(name="zookeeper_registry")
    public RegistryConfig registry(){
        RegistryConfig registry = new RegistryConfig();
        registry.setProtocol("zookeeper");
        registry.setAddress("CentOS7:2181");
        registry.setClient("curator");
        return registry;
    }
}

或者

dubbo-consumer.properties

dubbo.application.name=annotation-properties-provider
dubbo.application.qosEnable=false

dubbo.registry.id=zk_registry
dubbo.registry.address=zookeeper://CentOS7:2181?client=curator

注意目前properties版本在配置多个注册中心和多个协议的解析上存在bug,大家可以参考http://dubbo.apache.org/docs/v2.7/user/configuration/properties/说明,在这里https://github.com/apache/dubbo/issues/419问题依旧没有解决!

com.baizhi.DubboApplicationConfiguration

@Configuration
@EnableDubbo(scanBasePackages = {"com.baizhi.service"})
@PropertySource(value = "classpath:dubbo-consumer.properties")
public class DubboApplicationConfiguration {

   @DubboReference(id = "userService",application="application",registry = "zk_registry")
   private IUserService userService;
    
}

测试服务

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(DubboApplicationConfiguration.class);
ctx.start();
IUserService userService = ctx.getBean("userService", IUserService.class);
userService.save(new User());
ctx.close();

SpringBoot集成

①服务提供者

1、maven依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
</parent>

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.8</version>
    </dependency>
    <!--curator 组件介绍-->
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>2.8.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>2.8.0</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

2、application.properties

# dubbo应用信息
dubbo.application.id = app1
dubbo.application.name = dubbo-provider
# 等价配置@EnableDubbo
dubbo.scan.base-packages=com.baizhi.service
dubbo.application.qos-enable=false

# 配置dubbo协议
dubbo.protocols.dubbo_protocol.id=dubbo_protocol
dubbo.protocols.dubbo_protocol.name = dubbo
dubbo.protocols.dubbo_protocol.port = 20880

# 配置注册中心
dubbo.registries.zk_registry.id=zk_registry
dubbo.registries.zk_registry.address=zookeeper://192.168.119.138:2181
dubbo.registries.zk_registry.client=curator

3、在服务实现上添加如下注解

@Service
@DubboService(application = "app1",protocol = "dubbo_protocol",registry = "zk_registry")
public class UserService implements IUserService {
    public void save(User user) {
        System.out.println(user+" saved...");
    }
}

4、启动SpringBootDubboApplicationEntry

@SpringBootApplication
public class SpringBootDubboApplicationEntry {
    public static void main(String[] args) throws IOException {
        SpringApplication.run(SpringBootDubboApplicationEntry.class,args);
        System.out.println("Provider started!");
        System.in.read();
    }
}

②服务消费者

1、maven依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
</parent>

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.8</version>
    </dependency>
    <!--curator 组件介绍-->
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>2.8.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>2.8.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

2、application.properties

# dubbo应用信息
dubbo.application.id = app1
dubbo.application.name = dubbo-consumer
dubbo.application.qos-enable=false

# 配置注册中心
dubbo.registries.zk_registry.id=zk_registry
dubbo.registries.zk_registry.address=zookeeper://192.168.119.138:2181
dubbo.registries.zk_registry.client=curator

3、在服务引用配置类上添加如下注解

@Configuration
public class DubboReferenceBeanFactory {
    @DubboReference(id = "userService",application="application",registry = "zk_registry")
    private IUserService userService;
}

4、启动入口SpringBootDubboApplicationEntry

@SpringBootApplication
public class SpringBootDubboApplicationEntry {
    public static void main(String[] args) throws IOException {
        SpringApplication.run(SpringBootDubboApplicationEntry.class,args);
    }
}

5、测试引用

@SpringBootTest(classes = {SpringBootDubboApplicationEntry.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringBootDubboApplicationTests {
    @Autowired
    private IUserService userService;
    @Test
    public void testSaveUser(){
        userService.save(new User());
    }
}

经过整合和对比我们不难发现使用XML配置版本配置Dubbo是最舒服的,因此后续版本笔者会优先使用XML配置版本介绍和演示Dubbo的一些特性。

Dubbo特性分析

Preflight Check

默认情况下,dubbo将在启动时检查相关服务是否可用。当服务不可用时,它将引发异常以阻止Spring完全初始化,以便您可以在发布应用程序之前及早发现问题,默认设置为check = true。

1、单个配置(高优先级)

<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1" 
                 check="false"
                 />

2、全局配置(低优先级)

<dubbo:consumer check="false" registry="zk_registry" application="app1" />

这里注意设置check=false系统在启动的时候不会去检查服务是否可用,除了通过设置为check属性,用户还可以设置spring的bean的懒加载方式,这个时候由于Spring并不会立即初始化系统使用的Bean对象,因此就不会检查服务是否可用。在Dubbo启动之后会去检查引用的服务是否可用之外,还可以设置注册中心的启动检查。

<dubbo:registry id="zk_registry" address="zookeeper://CentOS7:2181" client="curator" check="false"/>

Fault Tolerance Strategy

当集群调用失败时,Dubbo提供了多种容错方案,并具有默认的故障转移重试功能。
在这里插入图片描述

  • This Invoker is the callable Service’s abstract of theProvider, and the Invoker packaging theProvider’s address and Service’s interface.
  • The Directory represent multiple Invoker,You can think of it as List<Invoker>,But unlike List,its value can be dynamically changing.such as registry push changes
  • The Cluster disguises multipleInvoker in Directory as aInvoker,The upper transparent, masquerade process contains fault-tolerant logic, call failed, try another
  • The Router is responsible for selecting subsets according to routing rules from multiple Invokers, such as read-write separation, application isolation, etc.
  • LoadBalance is responsible for selecting a specific one from multipleInvoker for this call. The selection process includes the load balancing algorithm. If the call fails, it needs to be re-selected

策略详解

  • Failover Cluster - 故障自动切换,出现故障时,请重试其他服务器(默认)。通常用于读取操作,但是重试会导致更长的延迟。重试次数可以通过重试= 2设置(不包括第一次)。
<!--默认配置-低优先级-->
<dubbo:consumer check="true" registry="zk_registry" application="app1" retries="2" timeout="1000" />

<!--实例中局部配置-中优先级-->
<dubbo:reference id="userService" interface="com.baizhi.service.IUserService" retries="2"
                 registry="zk_registry"
                 application="app1" 
                 timeout="1000">
    <!--方法配置-高优先级-->
    <dubbo:method name="save" retries="2" timeout="500"/>
</dubbo:reference>

或者

<!--默认配置-低优先级-->
<dubbo:provider retries="2" protocol="dubbo_protocol" application="app1" registry="zk_registry"/>
<!--实例中局部配置-中优先级-->
<dubbo:service interface="com.baizhi.service.IUserService" ref="userService"
               protocol="dubbo_protocol"
               application="app1"
               registry="zk_registry"
               retries="2" >
    <!--方法配置-高优先级-->
    <dubbo:method name="save" retries="2"/>

</dubbo:service>

这里需要注意一般我们只需要在消费者那边配置消费属性,一般来说配置的优先级消费者高于服务提供者。我们可以尝试在服务提供者提供的服务中使用Thread.sleep(5000)造成服务调用的超时,继而引发客户端这边发生调用超时故障。这里我们需要在消费端标签设置timeout标签设置延迟时间为500毫秒观察调用的故障转移的效果。

  • Failfast Cluster - 快速失败,仅发送一次调用,失败立即出错。通常用于非等幂写入操作,例如添加记录。
<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 cluster="failfast" >
    <dubbo:method name="save"  timeout="100" retries="10"/>
</dubbo:reference>

如果使用了FailFast故障转移策略我们前面配置的retries属性就不在起作用了,因为系统仅仅只发起一次调度如果失败就不在发起调度 。

  • Failsafe Cluster - 安全故障,异常情况,直接忽略。通常用于编写审核日志和其他操作。
<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 cluster="failsafe " >
    <dubbo:method name="save"  timeout="100" retries="10"/>
</dubbo:reference>

如果调用失败,系统仅仅会将错误信息已错误日志形式输出,并不会抛出调度异常信息,导致后续程序无法正常运行。

  • Failback Cluster - 故障自动恢复,无法记录后台请求,定期重新传输。通常用于消息通知操作。
<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failback"  >
    <dubbo:method name="save"  timeout="100" retries="5"/>
</dubbo:reference>

有点类似Failover的容错策略,但是与Failover不同点在于该策略重试完retries次数之后,如果依旧失败系统仅仅以错误日志的形式输出,并不会终止程序。这里有个bug目前Dubbo并不会解析<dubble:method/> 上的retries属性.

  • Forking Cluster - 并行调用多个服务器,只需要一台服务器成功返回即可。通常用于实时要求苛刻的读取操作,但需要浪费更多的服务资源。可以使用forks = 3设置最大并行度。
<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="forking"
                 forks="3" >
    <dubbo:method name="save"  timeout="100"  />
</dubbo:reference>

这里注意<dubble:method/> 目前还不支持forks属性,因此目前仅仅只能在<dubbo:provider/>或者<dubbo:reference上进行配置forks属性.

  • Broadcast Cluster - 呼叫所有提供者的广播,一个接一个,报告任何错误(2.1.0+)。它通常用于通知所有提供程序更新本地资源信息,例如缓存或日志。
<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="broadcast"
                 forks="3" >
    <dubbo:method name="save"  timeout="100"  />
</dubbo:reference>

Load Balance

Dubbo提供了许多用于群集负载平衡的平衡策略,默认为random。

策略详解

  • Random LoadBalance - 随机,按重量设置随机概率。并且当基于概率使用权重时,分布证明是均匀的,这也有助于动态调整提供者权重。

1、服务消费方

<dubbo:consumer check="true" 
                registry="zk_registry" 
                application="app1"  
                retries="3" 
                timeout="1000" 
                cluster="failover" 
                loadbalance="random" />
<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 forks="3"
                 loadbalance="random">
    <dubbo:method name="save"  timeout="1000" loadbalance="random"  />
</dubbo:reference>

2、服务提供方

<!--服务级别权重 weight-->
<dubbo:provider  protocol="dubbo_protocol" 
                 application="app1" 
                 retries="2" 
                 registry="zk_registry" 
                 weight="100"
                loadbalance="random" />
<!--服务权重 weight-->
<dubbo:service interface="com.baizhi.service.IUserService" ref="userService"
               protocol="dubbo_protocol"
               application="app1"
               registry="zk_registry"
               retries="2" weight="100"
               loadbalance="random" >
    <dubbo:method name="save" retries="2" loadbalance="random"/>
</dubbo:service>
  • RoundRobin LoadBalance

按公约后的权重设置轮询比率。存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

<dubbo:consumer check="true" 
                registry="zk_registry" 
                application="app1"  
                retries="3" 
                timeout="1000" 
                cluster="failover" 
                loadbalance="random" />
<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 forks="3"
                 loadbalance="random">
    <dubbo:method name="save"  timeout="1000" loadbalance="roundrobin"  />
</dubbo:reference>
  • LeastActive LoadBalance

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

<dubbo:consumer check="true" registry="zk_registry" application="app1"  retries="3" timeout="1000" cluster="failover" loadbalance="random" />
<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 forks="3"
                 loadbalance="random">
    <dubbo:method name="save"  timeout="5000" loadbalance="leastactive"  />
</dubbo:reference>

优先按照最少活跃的机器,每个服务提供者都有一个调用计算器,统计服务器目前正在处理的请求,处理完之后就会将计数器减1,消费者在调用的时候优先调用计数器小的机器,如果计算器小的不止有一个服务,那么就会按照设置的权重去调用即可。

  • ConsistentHash LoadBalance

一致性 Hash,相同参数的请求总是发到同一提供者当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。缺省只对第一个参数 Hash,如果要修改,请配置 <dubbo:parameter key="hash.arguments" value="0,1" />缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key="hash.nodes" value="320" />

<dubbo:consumer check="true" registry="zk_registry" application="app1"  retries="3" timeout="1000" cluster="failover" loadbalance="consistenthash" >
    <dubbo:parameter key="hash.nodes" value="320"/>
    <dubbo:parameter key="hash.arguments" value="0,1"/>
</dubbo:consumer>
<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 forks="3"
                 loadbalance="consistenthash">
    <dubbo:method name="save"  timeout="5000" loadbalance="consistenthash"  />
</dubbo:reference>

Thread Model

配置 Dubbo 中的线程模型,如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程上处理更快,因为减少了线程池调度。但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则 IO 线程阻塞,将导致不能接收其它请求。如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。
在这里插入图片描述

因此,需要通过不同的派发策略和不同的线程池配置的组合来应对不同的场景:

<dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" />

Dispatcher

  • all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
  • direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行。
  • message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
  • execution 只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
  • connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。

ThreadPool

  • fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
  • cached 缓存线程池,空闲一分钟自动删除,需要时重建。
  • limited 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
  • eager 优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相比于cached:cached在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列)

Explicit Target

Dubbo 中点对点的直连方式在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。

①通过 XML 配置

<dubbo:reference id="userService" interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 loadbalance="consistenthash"
                 url="dubbo://localhost:20880">
    <dubbo:method name="save"  timeout="5000" loadbalance="consistenthash"   />
</dubbo:reference>

②通过 -D 参数指定

-Dcom.baizhi.service.IUserService=dubbo://localhost:20882

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ZOT4Pma-1608021424478)(assets/image-20201210145036946.png)]

③通过文件映射

如果服务比较多,也可以用文件映射,用 -Ddubbo.resolve.file 指定映射文件路径,此配置优先级高于 <dubbo:reference> 中的配置,如:

java -Ddubbo.resolve.file=xxx.properties

在这里插入图片描述

然后在映射文件 xxx.properties 中加入配置,其中 key 为服务名,value 为服务提供者 URL:

com.alibaba.xxx.XxxService=dubbo://localhost:20890

1.0.15 及以上版本支持,2.0 以上版本自动加载 ${user.home}/dubbo-resolve.properties文件,不需要配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OuTNs0US-1608021424481)(assets/image-20201210145335157.png)]

Subscribe Only

只订阅不注册,为了方便开发测试,经常会在线下共用一个所有服务可用的注册中心,这时,如果一个正在开发中的服务提供者注册,可能会影响消费者不能正常运行。可以让服务提供者开发方,只订阅服务(开发的服务可能依赖其它服务),而不注册正在开发的服务,通过直连测试正在开发的服务。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mWld3n6U-1608021424482)(assets/subscribe-only.jpg)]

<dubbo:registry id="zk_registry" 
                address="zookeeper://CentOS7:2181" 
                client="curator" 
                check="false" 
                register="false"/>

Multiple Protocols

在 Dubbbo 中配置多协议,Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。

①不同服务不同协议

不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议。

<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="1099" />
<!-- 使用dubbo协议暴露服务 -->
<dubbo:service interface="com.alibaba.hello.api.HelloService"  ref="helloService" protocol="dubbo" />
<!-- 使用rmi协议暴露服务 -->
<dubbo:service interface="com.alibaba.hello.api.DemoService"   ref="demoService" protocol="rmi" /> 

②多协议暴露服务

例如需要与 http 客户端互操作

<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="hessian" port="8080" />
<!-- 使用多个协议暴露服务 -->
<dubbo:service interface="com.alibaba.hello.api.HelloService"  protocol="dubbo,hessian" />

需要注意使用Hessian协议传输数据时候需要额外引入以下依赖

<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-rpc-hessian -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-rpc-hessian</artifactId>
    <version>2.7.8</version>
</dependency>

Multiple Registries

在 Dubbo 中把同一个服务注册到多个注册中心上,Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。

①多注册中心注册

例如中文站有些服务来不及在青岛部署,只在杭州部署,而青岛的其它应用需要引用此服务,就可以将服务同时注册到两个注册中心。

<!-- 多注册中心配置 -->
<dubbo:registry id="hangzhouRegistry" address="10.20.141.150:9090" />
<dubbo:registry id="qingdaoRegistry" address="10.20.141.151:9010" default="false" />
<!-- 向多个注册中心注册 -->
<dubbo:service interface="com.alibaba.hello.api.HelloService" ref="helloService" registry="hangzhouRegistry,qingdaoRegistry" />

②不同服务使用不同注册中心

比如:CRM 有些服务是专门为国际站设计的,有些服务是专门为中文站设计的。

<!-- 多注册中心配置 -->
<dubbo:registry id="chinaRegistry" address="10.20.141.150:9090" />
<dubbo:registry id="intlRegistry" address="10.20.154.177:9010" default="false" />
<!-- 向中文站注册中心注册 -->
<dubbo:service interface="com.alibaba.hello.api.HelloService" ref="helloService" registry="chinaRegistry" />
<!-- 向国际站注册中心注册 -->
<dubbo:service interface="com.alibaba.hello.api.DemoService"  ref="demoService" registry="intlRegistry" />

③多注册中心引用

比如:CRM 需同时调用中文站和国际站的 PC2 服务,PC2 在中文站和国际站均有部署,接口及版本号都一样,但连的数据库不一样。

<!-- 多注册中心配置 -->
<dubbo:registry id="chinaRegistry" address="10.20.141.150:9090" />
<dubbo:registry id="intlRegistry" address="10.20.154.177:9010" default="false" />
<!-- 引用中文站服务 -->
<dubbo:reference id="chinaHelloService" interface="com.alibaba.hello.api.HelloService"  registry="chinaRegistry" />
<!-- 引用国际站站服务 -->
<dubbo:reference id="intlHelloService" interface="com.alibaba.hello.api.HelloService"  registry="intlRegistry" />

Service Group

使用服务分组区分服务接口的不同实现,当一个接口有多种实现时,可以用 group 区分。

①服务

<dubbo:service group="feedback" interface="com.xxx.IndexService" ref="..."/>
<dubbo:service group="member" interface="com.xxx.IndexService" ref="..."/>

②引用

<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.xxx.IndexService" />
<dubbo:reference id="memberIndexService" group="member" interface="com.xxx.IndexService" />

任意组:

<dubbo:reference id="barService" interface="com.foo.BarService" group="*" />

2.2.0 以上版本支持,总是只调一个可用组的实现

Static Service

将 Dubbo 服务标识为非动态管理模式,有时候希望人工管理服务提供者的上线和下线,此时需将注册中心标识为非动态管理模式。

<dubbo:registry id="zk_registry" address="zookeeper://CentOS7:2181" client="curator" dynamic="false"/>

服务提供者初次注册时为禁用状态,需人工启用。断线时,将不会被自动删除,需人工禁用。如果是一个第三方服务提供者,比如 memcached,可以直接向注册中心写入提供者地址信息,消费者正常使用.

①安装服务

1、下载memcached-1.6.9.tar.gz安装包,然后安装memcache服务

[root@CentOS ~]# yum install -y libevent-devel
[root@CentOS ~]# tar -zxf memcached-1.6.9.tar.gz
[root@CentOS ~]# cd memcached-1.6.9
[root@CentOS memcached-1.6.9]# ./configure --prefix=/usr/memcached
[root@CentOS memcached-1.6.9]# make
[root@CentOS memcached-1.6.9]# make install

2、启动memcahced服务

[root@CentOS ~]# cd /usr/memcached/
[root@CentOS memcached]# ./bin/memcached -p 11211 -vvv -u root

3、在需要引用memcache的服务端引入以下服务

<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-rpc-memcached -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-rpc-memcached</artifactId>
    <version>2.7.8</version>
</dependency>

4、在dubbo的引用端声明如下配置

<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-rpc-memcached -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-rpc-memcached</artifactId>
    <version>2.7.8</version>
</dependency>
<dubbo:reference id="cache" interface="java.util.Map"  url="memcached://192.168.119.138:11211"/>

5、注册一个memcached服务的提供暴露

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://192.168.119.138:2181"));
registry.register(URL.valueOf("memcached://192.168.119.138:11211/java.util.Map?category=providers&dynamic=false&application=foo"));
registry.destroy();

6、测试服务

Map cache=ctx.getBean("cache", Map.class);
//cache.put("user","jiangzz");
Object user = cache.get("user");
System.out.println(user)

Multiple Versions

在 Dubbo 中为同一个服务配置多个版本,当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。可以按照以下的步骤进行版本迁移:

  1. 在低压力时间段,先升级一半提供者为新版本
  2. 再将所有消费者升级为新版本
  3. 然后将剩下的一半提供者升级为新版本

老版本服务提供者配置:

<dubbo:service interface="com.foo.BarService" version="1.0.0" />

新版本服务提供者配置:

<dubbo:service interface="com.foo.BarService" version="2.0.0" />

老版本服务消费者配置:

<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />

新版本服务消费者配置:

<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />

如果不需要区分版本,可以按照以下的方式配置2.2.0:

<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />

Group Merger

通过分组聚合按组合并返回结果,按组合并返回结果,比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项。

①搜索所有分组

<dubbo:reference interface="com.xxx.MenuService" group="*" merger="true" />

②合并指定分组

<dubbo:reference interface="com.xxx.MenuService" group="aaa,bbb" merger="true" />

③指定方法合并结果,其它未指定的方法,将只调用一个 Group

<dubbo:reference interface="com.xxx.MenuService" group="*">
    <dubbo:method name="getMenuItems" merger="true" />
</dubbo:reference>

④某个方法不合并结果,其它都合并结果

<dubbo:reference interface="com.xxx.MenuService" group="*" merger="true">
    <dubbo:method name="getMenuItems" merger="false" />
</dubbo:reference>

⑤指定合并策略,缺省根据返回值类型自动匹配,Dubbo已知的合并有如下ArrayMerger、ListMerger、SetMerger、MapMerger实现,如果同一类型有两个合并器时,需指定合并器的名称.

<dubbo:reference id="userService"  interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 loadbalance="consistenthash"
                 group="*" >

    <dubbo:method name="save"  timeout="5000" loadbalance="consistenthash"  merger="true" />
    <dubbo:method name="queryAll"  timeout="5000" loadbalance="consistenthash"  merger="true"  />
    <dubbo:method name="queryUserById"  timeout="5000" loadbalance="consistenthash"  merger="UserMerger"  />
</dubbo:reference>
import org.apache.dubbo.rpc.cluster.Merger;

import java.util.Arrays;


public class UserMereger implements Merger<User> {

    public User merge(User... items) {
        User firstUser = items[0];
        String names = Arrays.stream(items)
                .map(user -> user.getName())
                .reduce((v1, v2) -> v1 + "," + v2)
                .get();
        firstUser.setName(names);
        return firstUser;
    }
}

在Resource资源目录创建如下UserMerger扩展
在这里插入图片描述

⑥指定合并方法,将调用返回结果的指定方法进行合并,合并方法的参数类型必须是返回结果类型本身.

<dubbo:reference id="userService"  interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 loadbalance="consistenthash"
                 group="*" >

    <dubbo:method name="save"  timeout="5000" loadbalance="consistenthash"  merger="false" />
    <dubbo:method name="queryAll"  timeout="5000" loadbalance="consistenthash"  merger="true"  />
    <dubbo:method name="queryUserById"  timeout="5000" loadbalance="consistenthash"  merger=".mergerUser"  />
</dubbo:reference>
public class User implements Serializable {
    private Integer id;
    private String name;
    private Boolean sex;
    private Date birthDay;

    public User mergerUser(User user){
        this.setName(name+","+user.getName());
        return this;
    }
    ...
}

Parameter Validation

在 Dubbo 中进行参数验证,参数验证功能是基于 JSR303 实现的,用户只需标识 JSR303 标准的验证 annotation,并通过声明 filter 来实现验证。

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.0.0.GA</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>4.2.0.Final</version>
</dependency>
public class User implements Serializable {
    @Min(1)
    private Integer id;
    @NotNull
    private String name;
    private Boolean sex;
    @Future
    private Date birthDay;
    ...
}
<dubbo:reference id="userService"  interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 loadbalance="consistenthash"
                 group="*"
                 validation="true">
   <dubbo:method name="save"  timeout="5000" loadbalance="consistenthash"  merger="true" validation="true" />
</dubbo:reference>

Cache Result

通过缓存结果加速访问速度,结果缓存,用于加速热门数据的访问速度,Dubbo 提供声明式缓存,以减少用户加缓存的工作量。

缓存类型

  • lru 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。
  • threadlocal 当前线程缓存,比如一个页面渲染,用到很多 portal,每个 portal 都要去查用户信息,通过线程缓存,可以减少这种多余访问。
  • jcache 与 JSR107集成,可以桥接各种缓存实现。

配置

<dubbo:reference interface="com.foo.BarService" cache="lru" />

或者

<dubbo:reference interface="com.foo.BarService">
    <dubbo:method name="findBar" cache="lru" />
</dubbo:reference>

这里举例使用Ehcache实现

<!-- https://mvnrepository.com/artifact/javax.cache/cache-api -->
<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
    <version>1.1.0</version>
</dependency>
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.1.3</version>
</dependency>
<dubbo:reference id="userService"  interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 loadbalance="consistenthash"
                 group="*"
                 validation="true">

  <dubbo:method name="queryAll"  timeout="5000" loadbalance="consistenthash"  merger="true" cache="jcache" />
</dubbo:reference>

Generic Reference

实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。基本类型以及Date,List,Map等不需要转换,直接调用 。

<dubbo:reference id="userService"  
                 interface="com.baizhi.service.IUserService" group="*" generic="true" >
    <dubbo:method name="save" merger="false"/>
</dubbo:reference>
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
ctx.start();

GenericService userService = (GenericService) ctx.getBean("userService");
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("class","com.baizhi.entities.User");
params.put("id",1);
params.put("name","jiangzz");
params.put("birthDay",new Date());
//参数名 参数类型 参数
userService.$invoke("save",new String[]{"com.baizhi.entities.User"},new Object[]{params});

ctx.stop();

Dubbo的参数验证无法验证泛华引用类型。

Java API调用

ReferenceConfig<GenericService> referenceConfig=new ReferenceConfig<>();
referenceConfig.setInterface("com.baizhi.service.IUserService");
referenceConfig.setGroup("*");
referenceConfig.setGeneric(true);

HashMap<String, Object> params = new HashMap<String, Object>();
params.put("class","com.baizhi.entities.User");
params.put("id",1);
params.put("name","jiangzz");
params.put("birthDay",new Date());
params.put("sex",false);
referenceConfig.get().$invoke("save",new String[]{"com.baizhi.entities.User"},new Object[]{params});

Protobuf

当前 Dubbo 的服务定义和具体的编程语言绑定,没有提供一种语言中立的服务描述格式,比如 Java 就是定义 Interface 接口,到了其他语言又得重新以另外的格式定义一遍。 2.7.5 版本通过支持 Protobuf IDL 实现了语言中立的服务定义。日后,不论我们使用什么语言版本来开发 Dubbo 服务,都可以直接使用 IDL 定义如下服务。

①在项目java平级的目录下创建proto文件夹,然后在该文件夹下创建DemoService.proto文本文件

syntax = "proto3";

option java_package = "com.baizhi.proto";
option java_multiple_files = true;
option java_outer_classname = "DemoServiceProto";
option optimize_for = SPEED;

// The demo service definition.
service DemoService {
  rpc query (Request) returns (Response){}
}

// The request message containing the user's name.
message Request {
  string name = 1;
}

// The response message containing the greetings
message Response {
  string message = 1;
}

②在消费端和服务端分别引入以下依赖

<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-serialization-protobuf -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-serialization-protobuf</artifactId>
    <version>2.7.8</version>
</dependency>
<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.6.1</version>
        </extension>
    </extensions>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.7.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.5.1</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact>
                <outputDirectory>src/main/java</outputDirectory>
                <clearOutputDirectory>false</clearOutputDirectory>
                <protocPlugins>
                    <protocPlugin>
                        <id>dubbo</id>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-compiler</artifactId>
                        <version>0.0.1</version>
                        <mainClass>org.apache.dubbo.gen.dubbo.DubboGenerator</mainClass>
                    </protocPlugin>
                </protocPlugins>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

③执行protobuf:compile指令分别在消费端和服务提供端生成相关的代码

④在服务端编写DemoService的实现

public class ProtoBufDemoService implements DemoServiceDubbo.IDemoService {
    @Override
    public Response query(Request request) {
        System.out.println("接收来自客户端消息:"+request.getName());
        Response.Builder builder = Response.newBuilder();
        builder.setMessage("服务端响应");
        return builder.build();
    }

    @Override
    public CompletableFuture<Response> queryAsync(Request request) {
        return CompletableFuture.completedFuture(query(request));
    }
}

⑤配置该Bean,并且使用Dubbo暴露该服务

<bean id="protoBufDemoService" class="com.baizhi.service.impl.ProtoBufDemoService"/>
<dubbo:service interface="com.baizhi.proto.DemoServiceDubbo$IDemoService" ref="protoBufDemoService"/>

注意这里的serialization属性配置的是元素的序列化方式,这里我们必须选择protobuf即可

⑥配置服务引用

<dubbo:reference id="demoService" interface="com.baizhi.proto.DemoServiceDubbo$IDemoService"/>

⑦测试调用远程服务

DemoServiceDubbo.IDemoService demoService = (DemoServiceDubbo.IDemoService) ctx.getBean("demoService");
Request req = Request.newBuilder().setName("测试用户").build();
Response response = demoService.query(req);

System.out.println(response.getMessage());

Generic Service

泛接口实现方式主要用于服务器端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的远程服务 Mock 框架,可通过实现 GenericService 接口处理所有服务请求。

①编写泛化服务bin注册

public class UserDefineGenericService implements GenericService {

    @Override
    public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException {
        if ("sayHello".equals(method)) {
            return "Welcome " + args[0];
        }else{
            return null;
        }
    }
}

<bean id="genericService" class="com.baizhi.service.impl.UserDefineGenericService"/>
<dubbo:service interface="com.baizhi.service.impl.XxxService" ref="genericService"/>

②泛化服务调用

<dubbo:reference   id="xxxService" generic="true" interface="com.baizhi.service.impl.XxxService"/>
GenericService genericSerivce = (GenericService) ctx.getBean("xxxService");
Object result=genericSerivce.$invoke("sayHello",new String[]{"java.lang.String"},new Object[]{"张三"});
System.out.println(result);

Echo Test

通过回声测试检测 Dubbo 服务是否可用,回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控。所有服务自动实现 EchoService 接口,只需将任意服务引用强制转型为 EchoService,即可使用。

<dubbo:reference id="userService"  interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 loadbalance="consistenthash"
                 group="*"
                 validation="true">

 <dubbo:method name="save"  timeout="5000" loadbalance="consistenthash"  merger="true" validation="true" />
 <dubbo:method name="queryAll"  timeout="5000" loadbalance="consistenthash"  merger="true" cache="jcache"  />
 <dubbo:method name="queryUserById"  timeout="5000" loadbalance="consistenthash"  merger=".mergerUser"  />
</dubbo:reference>
EchoService echoService = (EchoService) ctx.getBean("userService");
Object result = echoService.$echo("hello");
System.out.println(result);

这里需要注意仅仅只有非泛化的引用才能使用回声测试。

Context

通过上下文存放当前调用过程中所需的环境信息,上下文中存放的是当前调用过程中所需的环境信息。所有配置信息都将转换为 URL 的参数,参见 schema 配置参考手册 中的对应URL参数一列。RpcContext 是一个 Thread Local的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。比如:A 调 B,B 再调 C,则 B 机器上,在 B 调 C 之前,RpcContext 记录的是 A 调 B 的信息,在 B 调 C 之后,RpcContext 记录的是 B 调 C 的信息。

①服务消费方

// 远程调用
xxxService.xxx();
// 本端是否为消费端,这里会返回true
boolean isConsumerSide = RpcContext.getContext().isConsumerSide();
// 获取最后一次调用的提供方IP地址
String serverIP = RpcContext.getContext().getRemoteHost();
// 获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("application");
// 注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();

②服务提供方

// 本端是否为提供端,这里会返回true
boolean isProviderSide = RpcContext.getContext().isProviderSide();
// 获取调用方IP地址
String clientIP = RpcContext.getContext().getRemoteHost();
// 获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("application");
// 注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();
// 此时本端变成消费端,这里会返回false
boolean isProviderSide = RpcContext.getContext().isProviderSide();

Implicit parameters

通过 Dubbo 中的 Attachment 在服务消费方和提供方之间隐式传递参数,可以通过 RpcContext 上的 setAttachmentgetAttachment 在服务消费方和提供方之间进行参数的隐式传递。

path, group, version, dubbo, token, timeout 几个 key 是保留字段,请使用其它值。
在这里插入图片描述
①在服务消费方端设置隐式参数

RpcContext.getContext().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用
xxxService.xxx(); // 远程调用
// ...

②在服务提供方端获取隐式参数

public class XxxServiceImpl implements XxxService {
    public void xxx() {
        // 获取客户端隐式传入的参数,用于框架集成,不建议常规业务使用
        String index = RpcContext.getContext().getAttachment("index"); 
    }
}

Callback parameter

通过参数回调从服务器端调用客户端逻辑,参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。

public interface IUserService {
    ...
    public void saveUserWithCallBack(User user ,UserCallback userCallback);
}
public interface UserCallback {
    public User  modifyUser(User user);
}

①服务端实现

public class UserServiceImp01 implements IUserService {
   //...

    public void saveUserWithCallBack(User user, UserCallback userCallback) {
        System.out.println("saveUserWithCallBack:"+user);
        user.setName("zhangsan");
        user=userCallback.modifyUser(user);
        System.out.println(user);
    }
}
<dubbo:service interface="com.baizhi.service.IUserService" ref="userService01"
               protocol="dubbo_protocol"
               application="app1"
               registry="zk_registry"
               retries="2"
               weight="100"
               group="g1"
               loadbalance="random">
    ...
    <!--配置回调参数-->
    <dubbo:method name="saveUserWithCallBack" >
        <dubbo:argument index="1" callback="true"/>
    </dubbo:method>
</dubbo:service>

②消费端实现

IUserService userSerivce = ctx.getBean("userService", IUserService.class);
userSerivce.saveUserWithCallBack(new User(), new UserCallback() {
    @Override
    public User modifyUser(User user) {
        System.out.println("服务端回调");
        user.setName(user.getName()+"_客户端追加!");
        return user;
    }
});

可以简单的理解为当设置为回调参数的时候,相当于服务端那边创建一个代理直接回调客户端的代码,继而实现客户端向服务端传递代码逻辑!

Event Notification

在调用之前、调用之后、出现异常时的时间通知,在调用之前、调用之后、出现异常时,会触发 oninvokeonreturnonthrow 三个事件,可以配置当事件发生时,通知哪个类的哪个方法。

public interface Notify {
    public void onreturn(Object msg, Integer id);
    public void onthrow(Throwable ex, Integer id);
    public void oninvoke(Integer id);
}
public class UserNotify implements Notify {
    public void onreturn(Object msg, Integer id) {
        System.out.println("onreturn"+"\t"+msg+"\t"+id);
    }
    public void onthrow(Throwable ex, Integer id) {
        System.out.println("onthrow:"+ex+"\t"+id);
    }
    public void oninvoke(Integer id) {
        System.out.println("oninvoke:"+id);
    }
}

①消费者引用消费服务

<dubbo:reference id="userService"  interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 loadbalance="consistenthash"
                 group="g1"
                 validation="true">
    <dubbo:method name="queryUserById"  
                  onreturn="notify.onreturn" 
                  onthrow="notify.onthrow" 
                  oninvoke="notify.oninvoke" />
</dubbo:reference>

这里面有个bug,如果消费者这边存在同时merger多个分组的结果,此时系统不会做时间通知。

Local-stub

在 Dubbo 中利用本地存根在客户端执行部分逻辑,调用远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HUwfSmlZ-1608021424488)(assets/stub.jpg)]

①客户端代码

public interface IUserService {
    public void save(User user);
    public User queryUserById(Integer id);
    public List<User> queryAll();
    public void saveUserWithCallBack(User user ,UserCallback userCallback);
}
public class UserServiceStub implements IUserService {
    private IUserService userService;
    public UserServiceStub(IUserService userService){
        System.out.println("UserServiceStub");
        this.userService=userService;
    }
    public void save(User user) {
        try {
            userService.save(user);
        } catch (Exception e) {

        }
    }

    public User queryUserById(Integer id) {
        try {
            return userService.queryUserById(id);
        } catch (Exception e) {
            return new User(id,"stub数据",true,new Date());
        }
    }

    public List<User> queryAll() {
        try {
            return userService.queryAll();
        } catch (Exception e) {
            return Arrays.asList();
        }
    }

    public void saveUserWithCallBack(User user, UserCallback userCallback) {
        try {
            userService.saveUserWithCallBack(user,userCallback);
        } catch (Exception e) {

        }
    }
}

②配置Stub服务

<dubbo:reference id="userService"  interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 loadbalance="consistenthash"
                 group="g2"
                 validation="true" 
                 stub="com.baizhi.service.impl.UserServiceStub">
    <dubbo:method name="queryUserById" timeout="10" onreturn="notify.onreturn" onthrow="notify.onthrow" oninvoke="notify.oninvoke"  />
</dubbo:reference>

这里的本地存根,有点类似SpringCloud的hystrix服务,在服务调用失败的时候回自动调用本地存根的

Local-mock

如何在 Dubbo 中利用本地伪装实现服务降级,本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。

public interface IUserService {
    public void save(User user);
    public User queryUserById(Integer id);
    public List<User> queryAll();
    public void saveUserWithCallBack(User user ,UserCallback userCallback);
}
public class UseServiceMock implements IUserService {
    public void save(User user) {
        System.out.println("保存结果!");
    }

    public User queryUserById(Integer id) {
        return new User(id,"mock user",true,new Date());
    }

    public List<User> queryAll() {
        return Arrays.asList();
    }

    public void saveUserWithCallBack(User user, UserCallback userCallback) {
        userCallback.modifyUser(user);
        System.out.println(user);
    }
}

①配置服务mock配置

<dubbo:reference id="userService"  interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 loadbalance="consistenthash"
                 group="g2"
                 validation="true"
                 stub="com.baizhi.service.impl.UserServiceStub"
                 mock="com.baizhi.service.impl.UseServiceMock">
    <dubbo:method name="queryUserById" timeout="2" onreturn="notify.onreturn" onthrow="notify.onthrow" oninvoke="notify.oninvoke"  />
</dubbo:reference>

进阶用法

return

使用 return 来返回一个字符串表示的对象,作为 Mock 的返回值。合法的字符串可以是:

  • empty: 代表空,基本类型的默认值,或者集合类的空值
  • null: null
  • true: true
  • false: false
  • JSON 格式: 反序列化 JSON 所得到的对象

throw

使用 throw 来返回一个 Exception 对象,作为 Mock 的返回值。当调用出错时,抛出一个默认的 RPCException:

<dubbo:reference interface="com.foo.BarService" mock="throw" />

当调用出错时,抛出指定的 Exception:

<dubbo:reference interface="com.foo.BarService" mock="throw com.foo.MockException" />

force 和 fail

2.6.6 以上的版本,可以开始在 Spring XML 配置文件中使用 fail:force:force: 代表强制使用 Mock 行为,在这种情况下不会走远程调用。fail: 与默认行为一致,只有当远程调用发生错误时才使用 Mock 行为。force:fail: 都支持与 throw 或者 return 组合使用。

强制返回指定值:

<dubbo:reference interface="com.foo.BarService" mock="force:return fake" />

强制抛出指定异常:

<dubbo:reference interface="com.foo.BarService" mock="force:throw com.foo.MockException" />

在方法级别配置 Mock

Mock 可以在方法级别上指定,假定 com.foo.BarService 上有好几个方法,我们可以单独为 sayHello() 方法指定 Mock 行为。具体配置如下所示,在本例中,只要 sayHello() 被调用到时,强制返回 “fake”:

<dubbo:reference id="demoService" check="false" interface="com.foo.BarService">
    <dubbo:parameter key="sayHello.mock" value="force:return fake"/>
</dubbo:reference>
  1. Mock 是 Stub 的一个子集,便于服务提供方在客户端执行容错逻辑,因经常需要在出现 RpcException (比如网络失败,超时等)时进行容错,而在出现业务异常(比如登录用户名密码错误)时不需要容错,如果用 Stub,可能就需要捕获并依赖 RpcException 类,而用 Mock 就可以不依赖 RpcException,因为它的约定就是只有出现 RpcException 时才执行。
  2. 在 interface 旁放一个 Mock 实现,它实现 BarService 接口,并有一个无参构造函数.

Delay-publish

如果你的服务需要预热时间,比如初始化缓存,等待相关资源就位等,可以使用 delay 进行延迟暴露。我们在 Dubbo 2.6.5 版本中对服务延迟暴露逻辑进行了细微的调整,将需要延迟暴露(delay > 0)服务的倒计时动作推迟到了 Spring 初始化完成后进行。你在使用 Dubbo 的过程中,并不会感知到此变化,因此请放心使用。

Dubbo 2.6.5 之前版本

  • 延迟到 Spring 初始化完成后,再暴露服务
<dubbo:service delay="-1" />
  • 延迟 5 秒暴露服务
<dubbo:service delay="5000" />

Dubbo 2.6.5 及以后版本

所有服务都将在 Spring 初始化完成后进行暴露,如果你不需要延迟暴露服务,无需配置 delay。延迟 5 秒暴露服务.

<dubbo:service delay="5000" />

Spring 2.x 初始化死锁问题

  • 触发条件

在 Spring 解析到 <dubbo:service /> 时,就已经向外暴露了服务,而 Spring 还在接着初始化其它 Bean。如果这时有请求进来,并且服务的实现类里有调用 applicationContext.getBean() 的用法。

1、请求线程的 applicationContext.getBean() 调用,先同步 singletonObjects 判断 Bean 是否存在,不存在就同步 beanDefinitionMap 进行初始化,并再次同步 singletonObjects 写入 Bean 实例缓存。

2、而 Spring 初始化线程,因不需要判断 Bean 的存在,直接同步 beanDefinitionMap 进行初始化,并同步 singletonObjects 写入 Bean 实例缓存。

这样就导致 getBean 线程,先锁 singletonObjects,再锁 beanDefinitionMap,再次锁 singletonObjects。而 Spring 初始化线程,先锁 beanDefinitionMap,再锁 singletonObjects。反向锁导致线程死锁,不能提供服务,启动不了。

规避办法

  1. 强烈建议不要在服务的实现类中有 applicationContext.getBean() 的调用,全部采用 IoC 注入的方式使用 Spring的Bean。
  2. 如果实在要调 getBean(),可以将 Dubbo 的配置放在 Spring 的最后加载。
  3. 如果不想依赖配置顺序,可以使用 <dubbo:provider delay=”-1” />,使 Dubbo 在 Spring 容器初始化完后,再暴露服务。
  4. 如果大量使用 getBean(),相当于已经把 Spring 退化为工厂模式在用,可以将 Dubbo 的服务隔离单独的 Spring 容器。

Concurrency-control

样例 1

限制 com.foo.BarService 的每个方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个:

<dubbo:service interface="com.foo.BarService" executes="10" />

样例 2

限制 com.foo.BarServicesayHello 方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个:

<dubbo:service interface="com.foo.BarService">
    <dubbo:method name="sayHello" executes="10" />
</dubbo:service>

样例 3

限制 com.foo.BarService 的每个方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个:

<dubbo:service interface="com.foo.BarService" actives="10" />

或者

<dubbo:reference interface="com.foo.BarService" actives="10" />

样例 4

限制 com.foo.BarServicesayHello 方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个:

<dubbo:service interface="com.foo.BarService">
    <dubbo:method name="sayHello" actives="10" />
</dubbo:service>

<dubbo:reference interface="com.foo.BarService">
    <dubbo:method name="sayHello" actives="10" />
</dubbo:service>

如果 <dubbo:service><dubbo:reference> 都配了actives,<dubbo:reference> 优先.

Config-connections

Dubbo 中服务端和客户端的连接控制;

  • 服务端连接控制
<dubbo:provider protocol="dubbo" accepts="10" />

<dubbo:protocol name="dubbo" accepts="10" />
  • 客户端连接控制

限制客户端服务使用连接不能超过 10 个

<dubbo:reference interface="com.foo.BarService" connections="10" />

<dubbo:service interface="com.foo.BarService" connections="10" />

如果 <dubbo:service><dubbo:reference> 都配了 connections,<dubbo:reference> 优先

lazy-connect

在 Dubbo 中配置延迟连接,延迟连接用于减少长连接数。当有调用发起时,再创建长连接。

<dubbo:protocol name="dubbo" lazy="true" />

该配置只对使用长连接的 dubbo 协议生效。

stickiness

为有状态服务配置粘滞连接,粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。粘滞连接将自动开启延迟连接,以减少长连接数。

<dubbo:reference id="xxxService" interface="com.xxx.XxxService" sticky="true" />

Dubbo 支持方法级别的粘滞连接,如果你想进行更细粒度的控制,还可以这样配置。

<dubbo:reference id="xxxService" interface="com.xxx.XxxService">
    <dubbo:mothod name="sayHello" sticky="true" />
</dubbo:reference>

token-authorization

通过令牌验证在注册中心控制权限,通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者.
在这里插入图片描述

可以全局设置开启令牌验证:

<!--随机token令牌,使用UUID生成-->
<dubbo:provider  token="true" />

<!--固定token令牌,相当于密码-->
<dubbo:provider interface="com.foo.BarService" token="123456" />

也可在服务级别设置:

<!--随机token令牌,使用UUID生成-->
<dubbo:service  token="true" />

或者

<!--固定token令牌,相当于密码-->
<dubbo:service  token="123456" />

routing-rule

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

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

后续我们计划在 2.6.x 版本的基础上继续增强脚本路由功能。

条件路由
  • 应用粒度

    # app1的消费者只能消费所有端口为20880的服务实例
    # app2的消费者只能消费所有端口为20881的服务实例
    ---
    scope: application
    force: true
    runtime: true
    enabled: true
    key: governance-conditionrouter-consumer
    conditions:
      - application=app1 => address=*:20880
      - application=app2 => address=*:20881
    ...
    
  • 服务粒度

    # DemoService的sayHello方法只能消费所有端口为20880的服务实例
    # DemoService的sayHi方法只能消费所有端口为20881的服务实例
    ---
    scope: service
    force: true
    runtime: true
    enabled: true
    key: org.apache.dubbo.samples.governance.api.DemoService
    conditions:
      - method=sayHello => address=*:20880
      - method=sayHi => address=*:20881
    ...
    

各字段含义

  • scope【必填】表示路由规则的作用粒度,scope的取值会决定key的取值。
    • service 服务粒度
    • application 应用粒度
  • Key【必填】明确规则体作用在哪个服务或应用。
    • scope=service时,key取值为{service}:{version}:{group}的组合
    • scope=application时,key取值为服务消费者的application名称
  • enabled=true 当前路由规则是否生效,可不填,缺省生效。
  • force=false 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false
  • runtime=false 是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为 true,需要注意设置会影响调用的性能,可不填,缺省为 false
  • priority=1 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0
  • conditions 定义具体的路由规则内容。必填

这里需要注意目前dubbo-admin服务存在bug,不建议大家使用dubbo-admin提交路由规则。

  • 应用粒度
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.24</version>
</dependency>

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.12.0</version>
</dependency>
public class RoutingRule {
    private String key;
    private String scope;
    private int priority;
    private boolean enabled;
    private boolean force;
    private boolean runtime;
    private List<String> conditions;
    ...
}
public class YamlParser {

    private static Yaml yaml;

    static {
        Representer representer = new Representer() {

            protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
                if (propertyValue == null) {
                    return null;
                }
                else {
                    return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
                }
            }
        };
        yaml = new Yaml(representer);
    }

    public static String dumpObject(Object object) {
        return yaml.dumpAsMap(object);
    }

    public static <T> T loadObject(String content, Class<T> type) {
        return yaml.loadAs(content, type);
    }
}
# dubbo-user-consumer下的消费者不可以访问20880和20881服务
---
conditions:
- => address!=*:20880
- => address!=*:20881
enabled: true
force: true
key: dubbo-user-consumer
priority: 0
runtime: true
scope: application

这些配置必须写在/dubbo/config/dubbo/{消费者应用名}.condition-router目录下

public static void setOrUpdateCondationRule(String consumerApplication, RoutingRule routingRule) throws Exception {
    String path="/dubbo/config/dubbo/"+consumerApplication+".condition-router";
    routingRule.setKey(consumerApplication);
    CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("CentOS7:2181",
                                                                          new RetryNTimes(5,1000));
    curatorFramework.start();

    Stat stat = curatorFramework.checkExists().forPath(path);
    //创建path路径
    if(stat==null){
        curatorFramework.create().creatingParentContainersIfNeeded().forPath(path);
    }
    //写出路由配置
    curatorFramework.setData().forPath(path, YamlParser.dumpObject(routingRule).getBytes());

    curatorFramework.close();
}
RoutingRule routingRule = new RoutingRule();

routingRule.setScope("application");
routingRule.setEnabled(true);
routingRule.setForce(true);
routingRule.setRuntime(true);
routingRule.setPriority(0);
routingRule.setConditions(Arrays.asList("=> address!=*:20880","=> address!=*:20881"));

setOrUpdateCondationRule("dubbo-user-consumer",routingRule);
  • 服务粒度
conditions:
- => address!=*:20880
- => address!=*:20881
enabled: true
force: true
key: com.baizhi.service.IUserService::g1
priority: 0
runtime: true
scope: service
public static void setOrUpdateServiceCondationRule(String group,String service,String version, RoutingRule routingRule) throws Exception {

    String path="/dubbo/config/dubbo/"+service+":"+version+":"+group+".condition-router";
    routingRule.setScope("service");
    routingRule.setKey(service+":"+version+":"+group);
    CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("CentOS7:2181",
                                                                          new RetryNTimes(5,1000));
    curatorFramework.start();

    Stat stat = curatorFramework.checkExists().forPath(path);
    //创建path路径
    if(stat==null){
        curatorFramework.create().creatingParentContainersIfNeeded().forPath(path);
    }
    //写出路由配置
    curatorFramework.setData().forPath(path, YamlParser.dumpObject(routingRule).getBytes());

    curatorFramework.close();
}

RoutingRule routingRule = new RoutingRule();

routingRule.setEnabled(true);
routingRule.setForce(false);
routingRule.setRuntime(true);
routingRule.setPriority(0);
routingRule.setConditions(Arrays.asList("=> address!=*:20880","=> address!=*:20881"));

setOrUpdateServiceCondationRule("g1","com.baizhi.service.IUserService","",routingRule);
setOrUpdateServiceCondationRule("g2","com.baizhi.service.IUserService","",routingRule);

Conditions规则

conditions部分是规则的主体,由1到任意多条规则组成,下面我们就每个规则的配置语法做详细说明:

  1. 格式
  • => 之前的为消费者匹配条件,所有参数和消费者的 URL 进行对比,当消费者满足匹配条件时,对该消费者执行后面的过滤规则。
  • => 之后为提供者地址列表的过滤条件,所有参数和提供者的 URL 进行对比,消费者最终只拿到过滤后的地址列表。
  • 如果匹配条件为空,表示对所有消费方应用,如:=> host != 10.20.153.11
  • 如果过滤条件为空,表示禁止访问,如:host = 10.20.153.10 =>
  1. 表达式

参数支持:

  • 服务调用信息,如:method, argument 等,暂不支持参数路由
  • URL 本身的字段,如:protocol, host, port 等
  • 以及 URL 上的所有参数,如:application, organization 等

条件支持:

  • 等号 = 表示"匹配",如:host = 10.20.153.10
  • 不等号 != 表示"不匹配",如:host != 10.20.153.10

值支持:

  • 以逗号 , 分隔多个值,如:host != 10.20.153.10,10.20.153.11
  • 以星号 * 结尾,表示通配,如:host != 10.20.*
  • 以美元符 $ 开头,表示引用消费者参数,如:host = $host
  1. Condition示例
  • 排除预发布机:
=> host != 172.22.3.91
  • 白名单:
host != 10.20.153.10,10.20.153.11 =>

注意 一个服务只能有一条白名单规则,否则两条规则交叉,就都被筛选掉了

  • 黑名单:
host = 10.20.153.10,10.20.153.11 =>
  • 服务寄宿在应用上,只暴露一部分的机器,防止整个集群挂掉:
=> host = 172.22.3.1*,172.22.3.2*
  • 为重要应用提供额外的机器:
application != kylin => host != 172.22.3.95,172.22.3.96
  • 读写分离:
method = find*,list*,get*,is* => host = 172.22.3.94,172.22.3.95,172.22.3.96
method != find*,list*,get*,is* => host = 172.22.3.97,172.22.3.98
  • 前后台分离:
application = bops => host = 172.22.3.91,172.22.3.92,172.22.3.93
application != bops => host = 172.22.3.94,172.22.3.95,172.22.3.96
  • 隔离不同机房网段:
host != 172.22.3.* => host != 172.22.3.*
  • 提供者与消费者部署在同集群内,本机只访问本机的服务:
=> host = $host
标签路由

标签路由通过将某一个或多个服务的提供者划分到同一个分组,约束流量只在指定分组中流转,从而实现流量隔离的目的,可以作为蓝绿发布、灰度发布等场景的能力基础。标签主要是指对Provider端应用实例的分组,目前有两种方式可以完成实例分组,分别是动态规则打标静态规则打标,其中动态规则相较于静态规则优先级更高,而当两种规则同时存在且出现冲突时,将以动态规则为准。

  • 动态规则打标,可随时在服务治理控制台下发标签归组规则
enabled: true
force: false
runtime: true
key: dubbo-user-service
tags:
  - name: tag1
    addresses:
      - '192.168.31.87:20880'
  - name: tag2
    addresses:
      - '192.168.31.87:20881'

注意默认dubbo会在路径/dubbo/config/dubbo/{服务提供端}.tag-router路径下写标签路由

规则详解

1、格式

  • Key明确规则体作用到哪个应用。必填
  • enabled=true 当前路由规则是否生效,可不填,缺省生效。
  • force=false 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false
  • runtime=false 是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为 true,需要注意设置会影响调用的性能,可不填,缺省为 false
  • priority=1 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0
  • tags 定义具体的标签分组内容,可定义任意n(n>=1)个标签并为每个标签指定实例列表。必填
    • name, 标签名称
  • addresses, 当前标签包含的实例列表

2、降级约定

  1. dubbo.tag=tag1 时优先选择 标记了tag=tag1 的 provider。若集群中不存在与请求标记对应的服务,默认将降级请求 tag为空的provider;如果要改变这种默认行为,即找不到匹配tag1的provider返回异常,需设置request.tag.force=true
  2. dubbo.tag未设置时,只会匹配tag为空的provider。即使集群中存在可用的服务,若 tag 不匹配也就无法调用,这与约定1不同,携带标签的请求可以降级访问到无标签的服务,但不携带标签/携带其他种类标签的请求永远无法访问到其他标签的服务。
  • 静态打标
<dubbo:provider tag="tag1"/>

<dubbo:service tag="tag1"/>

java -jar xxx-provider.jar -Ddubbo.provider.tag={the tag you want, may come from OS ENV}

Consumer

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

目前仅仅支持 hardcoding 的方式设置 requestTag。注意到 RpcContext 是线程绑定的,优雅的使用 TagRouter 特性,建议通过 servlet 过滤器(在 web 环境下),或者定制的 SPI 过滤器设置 requestTag。

测试案例

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.24</version>
</dependency>

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.12.0</version>
</dependency>
public class TagRoute {
    private int priority;
    private boolean enabled;
    private boolean force;
    private boolean runtime;
    private String key;
    private List<Tag> tags;
    ...
}
public class Tag {
    String name;
    String[] addresses;
    ...
}
public class YamlParser {

    private static Yaml yaml;

    static {
        Representer representer = new Representer() {

            protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
                if (propertyValue == null) {
                    return null;
                }
                else {
                    return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
                }
            }
        };
        yaml = new Yaml(representer);
    }

    public static String dumpObject(Object object) {
        return yaml.dumpAsMap(object);
    }

    public static <T> T loadObject(String content, Class<T> type) {
        return yaml.loadAs(content, type);
    }
}
public static void setOrUpdateApplicatioTagRule(String providerApplication, List<Tag> tags) throws Exception {
    String path="/dubbo/config/dubbo/"+providerApplication+".tag-router";

    TagRoute route = new TagRoute();
    route.setKey(providerApplication);
    route.setEnabled(true);
    route.setForce(false);
    route.setPriority(0);
    route.setRuntime(true);
    route.setTags(tags);


    CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("CentOS7:2181",
                                                                          new RetryNTimes(5,1000));
    curatorFramework.start();

    Stat stat = curatorFramework.checkExists().forPath(path);
    //创建path路径
    if(stat==null){
        curatorFramework.create().creatingParentContainersIfNeeded().forPath(path);
    }
    //写出路由配置
    curatorFramework.setData().forPath(path, YamlParser.dumpObject(route).getBytes());

    curatorFramework.close();
}
String providerApplication="dubbo-user-service";

List<Tag> tags=new ArrayList<>();
tags.add(new Tag("tag1",new String[]{"192.168.31.87:20880"}));
tags.add(new Tag("tag2",new String[]{"192.168.31.87:20881"}));

setOrUpdateApplicatioTagRule(providerApplication,tags);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值