Dubbo入门

Dubbo架构

Apache Dubbo 是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

Dubbo架构

async表示异步,sync表示同步

节点角色说明

节点 角色说明
Provider 暴露服务的服务提供方
Consumer 调用远程服务的服务消费方
Registry 服务注册与发现的注册中心
Monitor 统计服务的调用次数和调用时间的监控中心
Container 服务运行容器

调用关系说明

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

Dubbo 架构的特点

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

连通性

注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小

监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示

服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销

服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销

注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外

注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者

注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表

注册中心和监控中心都是可选的,服务消费者可以直连服务提供者

健壮性

监控中心宕掉不影响使用,只是丢失部分采样数据

数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务

注册中心对等集群,任意一台宕掉后,将自动切换到另一台

注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯

服务提供者无状态,任意一台宕掉后,不影响使用

服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

伸缩性

注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心

服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者

升级性

官方文档上有对未来扩展的设想和架构图。

Dubbo 解决的功能需求

当服务越来越多时,服务 URL 配置管理变得非常困难,F5 硬件负载均衡器的单点压力也越来越大。 此时需要一个服务注册中心,动态地注册和发现服务,使服务的位置透明。并通过在消费方获取服务提供方地址列表,实现软负载均衡和 Failover,降低对 F5 硬件负载均衡器的依赖,也能减少部分成本。

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

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

Dubbo 入门构建

构建接口中心 Dubbo-common

根据 Dubbo 服务化最佳实践:

建议将服务接口、服务模型、服务异常等均放在 API 包中,因为服务模型和异常也是 API 的一部分,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。

如果需要,也可以考虑在 API 包中放置一份 Spring 的引用配置,这样使用方只需在 Spring 加载过程中引用此配置即可。配置建议放在模块的包目录下,以免冲突,如:com/alibaba/china/xxx/dubbo-reference.xml

创建一个实体类

import java.io.Serializable;

/**
 * 因为需要在网络中传输,需要支持序列化,实现 Serializable 接口
 */
public class Employee implements Serializable {

    private static final long serialVersionUID = -8290432070775741887L;
    /**
     * 雇员ID
     */
    private Integer empId;
    /**
     * 雇员名称
     */
    private String empName;
    /**
     * 雇员工资
     */
    private Double salary;

    //省略构造方法和 getter 和 setter 方法
}

创建调用服务统一接口

import com.xm.dubbo.entity.Employee;

import java.util.List;

public interface EmployeeRemoteService {

    List<Employee> getEmployeeByConditionRemote(Employee employee);
}

这个类的 pom.xml 如下

主要用于在服务提供中心和消费方引入:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xm.dubbo</groupId>
    <artifactId>dubbo-common</artifactId>
    <version>1.0-SNAPSHOT</version>


</project>

构建服务提供中心 Dubbo-provider

pom.xml 文件如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xm.dubbo</groupId>
    <artifactId>dubbo-provider</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.8</version>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.3.0</version>
        </dependency>

        <dependency>
            <groupId>com.xm.dubbo</groupId>
            <artifactId>dubbo-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

创建接口实现类如下

//这里需要导入接口和实体类
import com.xm.dubbo.api.EmployeeRemoteService;
import com.xm.dubbo.entity.Employee;

import java.util.ArrayList;
import java.util.List;

public class EmployeeServiceImpl implements EmployeeRemoteService {
    @Override
    public List<Employee> getEmployeeByConditionRemote(Employee employee) {
        System.out.println(employee);

        List<Employee> empList = new ArrayList<>();

        empList.add(new Employee(111, "empName111", 111.11));
        empList.add(new Employee(222, "empName222", 222.22));
        empList.add(new Employee(333, "empName333", 333.33));

        return empList;
    }
}

用 Spring 配置声明暴露服务

<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-4.3.xsd
            http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!--1. 配置 Dubbo 提供方应用名称-->
    <dubbo:application name="xm-dubbo-provider"/>
    <!--2. 指定注册中心位置 -->
    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183"/>
    <!--3. 用dubbo协议在20880端口暴露服务-->
    <dubbo:protocol name="dubbo" port="20880"/>
    <!--4. 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.xm.dubbo.api.EmployeeRemoteService" ref="employeeServiceImpl"/>
    <!--5. 将实现类关联到接口 -->
    <bean id="employeeServiceImpl" class="com.xm.dubbo.service.impl.EmployeeServiceImpl"/>
</beans>

加载 Spring 配置,启动服务提供中心

需要注意应该提前启动 Zookeeper,这里 Zookeeper 已集群启动

import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;

public class Provider {
    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
        context.start();
        System.in.read();
    }
}

启动之后可以发现 Zookpper 中多了一个 dubbo 节点。

构建服务消费者 Dubbo-consumer

pom.xml 依赖如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xm</groupId>
    <artifactId>dubbo-consumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.xm.dubbo</groupId>
            <artifactId>dubbo-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.8</version>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.3.0</version>
        </dependency>

</dependencies>

</project>

通过 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://dubbo.apache.org/schema/dubbo"
       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">

    <!--Dubbo配置:消费方应用名 -->
    <dubbo:application name="xm-dubbo-consumer"  />

    <!--Dubbo配置:指定Dubbo注册中心  -->
    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183" />

    <!--Dubbo配置:引用接口代表的服务,生成远程服务代理 -->
    <dubbo:reference id="employeeRemoteService" interface="com.xm.dubbo.api.EmployeeRemoteService" />
</beans>

加载Spring配置,并调用远程服务

import com.xm.dubbo.api.EmployeeRemoteService;
import com.xm.dubbo.entity.Employee;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class Consumer {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
        context.start();
          //获取远程代理服务
        EmployeeRemoteService service = (EmployeeRemoteService) context.getBean("employeeRemoteService");
          //执行远程调用
        List<Employee> results = service.getEmployeeByConditionRemote(new Employee(1, "silverming", 10000.0));
        for ( Employee employ:results) {
            System.out.println(employ);
        }
    }
}

运行之后可以看到调用实现类的方法执行。

使用SpringBoot 整合 Dubbo

使用注解的方式

  1. 导入 dubbo-starter
  2. 在 application 配置文件中配置属性
  3. 使用 @EnableDubbo 开启 Dubbo 注解功能,使用 @Service 暴露服务,使用 @Reference 调用服务

创建统一接口中心

创建实体类

public class UserAddress implements Serializable {
    private Integer id;
    private String userAddress; // 用户地址
    private String userId; // 用户id
    private String consignee; // 收货人
    private String phoneNum; // 电话号码
    private String isDefault; // 是否为默认地址 yes / no
  
      //省略构造方法和getter、setter方法
}

创建两个服务接口

OrderService:

public interface OrderService {
    /**
     *  初始化订单
     **/
    List<UserAddress> initOrder(String userId);
}

UserService:

public interface UserService {

      /**
       * 获取用户信息列表
       **/
    List<UserAddress> getUserAddressList(String userId);
}

pom.xml 依赖如下:

                <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.8.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.2</version>
        </dependency>

创建服务提供者

pom.xml 如下:

         <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.silverming.dubbo</groupId>
    <artifactId>dubbo-boot-user-service-provider</artifactId>
    <version>1.0-SNAPSHOT</version>

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

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

        <dependency>
            <groupId>com.silverming.dubbo</groupId>
            <artifactId>dubbo-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>
    </dependencies>

服务接口实现

//注意导入的是 zookeeper 的 service
import com.alibaba.dubbo.config.annotation.Service;

@Component
@Service
public class UserServiceImpl implements UserService {
    @Override
    public List<UserAddress> getUserAddressList(String userId) {

        UserAddress address1 = new UserAddress(1, "河北省衡水市", "1", "carson","12345678911","Y");
        UserAddress address2 = new UserAddress(1, "山东省德州市", "2", "eason", "4562144", "Y");

        return Arrays.asList(address1,address2);
    }
}

服务提供方使用 @Service 注解暴露服务

服务提供者配置文件

dubbo:
  application:
    name: boot-order-service-provider
  registry:
    protocol: zookeeper
    address: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
server:
  port: 8080

创建启动类

@SpringBootApplication
@EnableDubbo //开启 Dubbo 注解功能
public class BootUserServiceProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootUserServiceProviderApplication.class,args);
    }
}

创建服务消费方

pom.xml 文件如下:与服务提供方一样

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>com.silverming.dubbo</groupId>
            <artifactId>dubbo-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

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

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

        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>
    </dependencies>

实现订单服务接口

订单服务接口会调用到用户服务接口,代码如下:

@Service //暴露服务
public class OrderServiceImpl  implements OrderService {

    /**
     * @Reference: 远程调用 UserService, 自己会去注册中心去发现
     **/
    @Reference
    UserService userService;


    @Override
    public List<UserAddress> initOrder(String userId) {
        System.out.println("用户id:"+userId);
        // 查询用户的收货地址
        List<UserAddress> userAddressList = userService.getUserAddressList(userId);
        return userAddressList;
    }
}

@Reference 注解用于引用服务

创建控制器

@Controller
public class OrderController {
    @Autowired
    private OrderService orderService;

    @ResponseBody
    @RequestMapping("/initOrder")
    public List<UserAddress> initOrder(@RequestParam("uid") String userId) {
        return orderService.initOrder(userId);
    }
}

配置文件

dubbo:
  application:
    name: boot-order-service-consumer
  registry:
    protocol: zookeeper
    address: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
  protocol:
    name: dubbo
    port: 20881
server:
  port: 8081

创建启动类

@EnableDubbo // 开启基于注解的dubbo功能
@SpringBootApplication
public class BootOrderServiceConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootOrderServiceConsumerApplication.class, args);
    }
}

使用 XML 配置文件的方式

按照上面原始的方式创建 XML 配置文件,然后在启动类使用 @ImportResouorce 注解导入配置文件

使用注解 API 的方式

将每一个组件手动创建到容器中。API 属性与配置项一对一,比如:ApplicationConfig.setName("xxx") 对应 <dubbo:application name="xxx"/>

示例如下,同时还有服务消费端相应的配置。

@Configuration
public class DubboCongig {

    @Bean
    // 当前应用配置
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        //对应dubbo.application.name
        applicationConfig.setName("application.yml");
        return applicationConfig;
    }

    @Bean
    // 连接注册中心配置
    public RegistryConfig registryConfig() {
        RegistryConfig registry = new RegistryConfig();
        registry.setProtocol("zookeeper");
        registry.setAddress("127.0.0.1:2181");
        return registry;
    }

    @Bean
    // 服务提供者暴露服务配置
    public ServiceConfig<UserService> serviceConfig(UserService userService) {
        ServiceConfig<UserService> service = new ServiceConfig<UserService>(); // 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏
        service.setInterface(UserService.class);
        service.setRef(userService);
        service.setVersion("1.0.0");

        MethodConfig methodConfig = new MethodConfig();
        methodConfig.setName("getUserAddressList");
        methodConfig.setTimeout(1000);

        //将 Method 设置保存的 Service 配置中
        List<MethodConfig> methodConfigList = new ArrayList<>();
        methodConfigList.add(methodConfig);
        service.setMethods(methodConfigList);
        // 暴露及注册服务
        service.export();
        return service;
    }
}

配置完之后,在启动类使用 @DubboComponentScan 或者 @EnableDubbo(scanBasePackages = "com.silverming.config") 注解。

Dubbo 配置

Dubbo 提供了 XML 、属性、API、注解等多种配置方式,可以通过官方文档查看配置属性有哪些。配置的优先级由高到低如下:

Dubbo 配置优先级

既虚拟机启动参数配置、外部化配置、Spring/API 配置、配置文件配置优先级依次降低。

相关常用配置标签

标签 用途 解释
<dubbo:service> 服务配置 用于暴露一个服务,定义服务的元信息,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心
<dubbo:reference/> 引用配置 用于创建一个远程服务代理,一个引用可以指向多个注册中心
<dubbo:protocol/> 协议配置 用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受
<dubbo:application/> 应用配置 用于配置当前应用信息,不管该应用是提供者还是消费者
<dubbo:module/ 模块配置 用于配置当前模块信息,可选
<dubbo:registry/> 注册中心配置 用于配置连接注册中心相关信息
<dubbo:monitor/> 监控中心配置 用于配置连接监控中心相关信息,可选
<dubbo:provider/> 提供方配置 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采用此缺省值,可选
<dubbo:consumer/> 消费方配置 当 ReferenceConfig 某属性没有配置时,采用此缺省值,可选
<dubbo:method/> 方法配置 用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息
<dubbo:argument/> 参数配置 用于指定方法参数配置

不同粒度配置的覆盖关系

timeout 为例,下图显示了配置的查找顺序,其它 retries, loadbalance, actives 等类似:

  • 方法级优先,接口级次之,全局配置再次之。
  • 如果级别一样,则消费方优先,提供方次之。

其中,服务提供方配置,通过 URL 经由注册中心传递给消费方。

Dubbo不同粒度配置的覆盖关系

建议由服务提供方设置超时,因为一个方法需要执行多长时间,服务提供方更清楚,如果一个消费方同时引用多个服务,就不需要关心每个服务的超时设置)。

理论上 ReferenceConfig 中除了interface这一项,其他所有配置项都可以缺省不配置,框架会自动使用ConsumerConfig,ServiceConfig, ProviderConfig 等提供的缺省配置。

配置相关机制

启动时检查

Dubbo 默认会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"

可以通过 check="false" 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。

另外,如果 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false",总是会返回引用,当服务恢复时,能自动连上。

通过 spring 配置文件关闭

关闭某个服务的启动时检查 (没有提供者时报错):

<dubbo:reference interface="com.foo.BarService" check="false" />

关闭所有服务的启动时检查 (没有提供者时报错):

<dubbo:consumer check="false" />

关闭注册中心启动时检查 (注册订阅失败时报错):

<dubbo:registry check="false" />

此外还可以通过 dubbo.properties ,虚拟机 -D 参数关闭。

配置的含义

dubbo.reference.check=false,强制改变所有 reference 的 check 值,就算配置中有声明,也会被覆盖。

dubbo.consumer.check=false,是设置 check 的缺省值,如果配置中有显式的声明,如:<dubbo:reference check="true"/>,不会受影响。

dubbo.registry.check=false,前面两个都是指订阅成功,但提供者列表是否为空是否报错,如果注册订阅失败时,也允许启动,需使用此选项,将在后台定时重试。

配置的相关参数

timeout 超时时间

远程服务调用超时时间(毫秒),大多数配置默认情况下是 1000 毫秒。

retries 重试次数

可以通过 retries 参数配置重试次数,不包含第一次调用,0 代表不重试。

对于幂等的操作,可以设置重试次数(如查询、删除、修改),对于非幂等的操作,不能设置重试(如增加)。

version 多版本

当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。

可以按照以下的步骤进行版本迁移:

  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" />

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

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

stub 本地存根

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

Dubbo本地存根

在 spring 配置文件中按以下方式配置:对应注解 @Service(stub = "com.silverming.service.stub.UserServiceStub")

<dubbo:service interface="com.silverming.service.UserService" stub="true" />

<dubbo:service interface="com.silverming.service.UserService" stub="com.silverming.service.stub.UserServiceStub" />

同时,还需要提供相应的 Stub 实现:

package com.silverming.service.stub;

import java.util.List;

public class UserServiceStub implements UserService {

    private final UserService userService;

    //构造函数传入真正的远程代理对象
    public UserServiceStub(UserService userService) {
        this.userService = userService;
    }

    @Override
    public List<UserAddress> getUserAddressList(String userId) {
        // 此代码在客户端执行, 可以在客户端做 ThreadLocal 本地缓存,或预先验证参数是否合法,等等
        System.out.println("本地存根调用");
        try {
            return userService.getUserAddressList(userId);
        } catch (Exception e) {
            // 可以容错,可以做任何AOP拦截事项
            return null;
        }
    }
}

Dubbo 高可用配置

Zookeeper 宕机与 Dubbo 直连

主要与 Dubbo 的健壮性挂钩,Zookeeper 注册中心宕机后,服务提供者和服务消费者仍能够通过本地缓存进行通讯。

Dubbo 健壮性包括以下几点:

  • 监控中心宕掉不影响使用,只是丢失部分采样数据

  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务

  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台

  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯

  • 服务提供者无状态,任意一台宕掉后,不影响使用

  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

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

如果是线上需求需要点对点,可在 <dubbo:reference> 中配置 url 指向提供者,将绕过注册中心,多个地址用分号隔开,配置如下 :

<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />

也可以通过 JVM 参数指定,在 JVM 启动参数中加入 -D 参数映射服务地址:

java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890

负载均衡

在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。

当有多个服务提供者时,默认情况下使用 random 并且权重相等随机调用。

负载均衡策略

Random LoadBalance

  • 随机,按权重设置随机概率。
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

RoundRobin LoadBalance

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

LeastActive LoadBalance

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

ConsistentHash LoadBalance

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

负载均衡配置方式

服务端服务级别

<dubbo:service interface="..." loadbalance="roundrobin" />

客户端服务级别

<dubbo:reference interface="..." loadbalance="roundrobin" />

服务端方法级别

<dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>

客户端方法级别

<dubbo:reference interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>

服务降级

当服务器压力剧增的情况下,根据实际业务流量及情况,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心业务正常运作或高效运作。

可以通过 mock 参数设置服务降级,与 stub 相似,设为true,表示使用缺省 Mock 类名,即:接口名 + Mock 后缀,服务接口调用失败 Mock 实现类,该 Mock 类必须有一个无参构造函数,与 Stub 的区别在于,Stub 总是被执行,而 Mock 只在出现非业务异常(比如超时,网络异常等)时执行,Stub 在远程调用之前执行,Mock 在远程调用后执行。

服务容错

在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。Dubbo 提供了以下几种容错模式:

Failover Cluster

失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。

重试次数配置如下:

<dubbo:service retries="2" />

<dubbo:reference retries="2" />

<dubbo:reference>
    <dubbo:method name="findFoo" retries="2" />
</dubbo:reference>

Failfast Cluster

快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

Failsafe Cluster

失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

Failback Cluster

失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

Forking Cluster

并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。

Broadcast Cluster

广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。

集群模式配置

按照以下示例在服务提供方和消费方配置集群模式

<dubbo:service cluster="failsafe" />

<dubbo:reference cluster="failsafe" />

服务容错应用(整合 Hystrix)

一般实际项目上会整合 Hystrix 组件进行服务容错实现。

  1. 首先导入 Hystrix 依赖

    在 provider 和 consumer 中导入 Hystrix 依赖,注意版本要和 Spring Boot 兼容

<dependency>
        <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.1.5.RELEASE</version>
</dependency>
  1. 启动类配置 @EnableHystrix

    在两者的启动类上配置 @EnableHystrix 表示开启 Hystrix。

  2. 对需要容错的方法使用 @HystrixCommand 注解

    此注解表示此方法是 hystrix 方法,其中 fallbackMethod 定义回退方法的名称。Spring Cloud 会自动将带有该批注的 Spring bean 包装在连接到 Hystrix 断路器的代理中。断路器计算何时断开和闭合电路,以及在发生故障时应采取的措施。

    服务提供者修改示例:

    @Component
    @Service(weight = 700)
    
    public class UserServiceImpl implements UserService {
        @Override
        @HystrixCommand
        public List<UserAddress> getUserAddressList(String userId) {
    
    
            System.out.println("服务提供者3");
    
            UserAddress address1 = new UserAddress(1, "河北省衡水市", "1", "carson","12345678911","Y");
            UserAddress address2 = new UserAddress(1, "山东省德州市", "2", "eason", "4562144", "Y");
    
              //测试异常
            if(Math.random() > 0.5) {
                throw new RuntimeException();
            }
    
            return Arrays.asList(address1,address2);
        }
    }
    

    服务消费者修改示例:

    @Controller
    public class OrderController {
        @Autowired
        private OrderService orderService;
    
        @ResponseBody
        @RequestMapping("/initOrder")
        @HystrixCommand(fallbackMethod = "fallbackMethod")
        public List<UserAddress> initOrder(@RequestParam("uid") String userId) {
            return orderService.initOrder(userId);
        }
    
          //出异常时调用的方法
        public List<UserAddress> fallbackMethod(@RequestParam("uid") String userId) {
            return Arrays.asList(new UserAddress(10,"测试地址","1","测试","测试","Y"));
        }
    }
    

参考资料

Dubbo 官方文档

Github——aaaTao66/dubbo-demo

Spring cloud系列十 使用@HystrixCommand使用Hystrix组件及@EnableCircuitBreaker原理介绍

Spring Cloud Netflix 官方文档

Dubbo教程-雷丰阳-尚硅谷