【Dubbo】RPC框架dubbo入门

发布时间:2024年01月03日

说明:本文内容摘自官网,更详细的内容请参考官网。

官网:https://cn.dubbo.apache.org/zh-cn/index.html

Dubbo 架构概述

Dubbo是什么

Apache Dubbo是一款易用、高性能的 WEB 和 RPC 框架。其前身是阿里巴巴公司开源的一个高性能、轻量级的开源Java RPC框架,可以和Spring框架无缝集成。为构建企业级微服务提供服务发现、流量治理、可观测、认证鉴权等能力、工具与最佳实践。

Dubbo能做什么

按照微服务架构的定义,采用它的组织能够很好的提高业务迭代效率与系统稳定性,但前提是要先能保证微服务按照期望的方式运行,要做到这一点需要解决服务拆分与定义、数据通信、地址发现、流量管理、数据一致性、系统容错能力等一系列问题。

Dubbo 可以帮助解决如下微服务实践问题:

  • 微服务编程范式和工具

Dubbo 支持基于 IDL 或语言特定方式的服务定义,提供多种形式的服务调用形式(如同步、异步、流式等)

  • 高性能的 RPC 通信

Dubbo 帮助解决微服务组件之间的通信问题,提供了基于 HTTP、HTTP/2、TCP 等的多种高性能通信协议实现,并支持序列化协议扩展,在实现上解决网络连接管理、数据传输等基础问题。

  • 微服务监控与治理

Dubbo 官方提供的服务发现、动态配置、负载均衡、流量路由等基础组件可以很好的帮助解决微服务基础实践的问题。除此之外,您还可以用 Admin 控制台监控微服务状态,通过周边生态完成限流降级、数据一致性、链路追踪等能力。

  • 部署在多种环境

Dubbo 服务可以直接部署在容器、Kubernetes、Service Mesh等多种架构下。

  • 活跃的社区

Dubbo 项目托管在 Apache 社区,有来自国际、国内的活跃贡献者维护着超 10 个生态项目,贡献者包括来自海外、阿里巴巴、工商银行、携程、蚂蚁、腾讯等知名企业技术专家,确保 Dubbo 及时解决项目缺陷、需求及安全漏洞,跟进业界最新技术发展趋势。

  • 庞大的用户群体

Dubbo3 已在阿里巴巴成功落地,实现了对老版本 HSF2 框架全面升级,成为阿里集团面向云原生时代的统一服务框架底座,庞大的用户群体是 Dubbo 保持稳定性、需求来源、先进性的基础。

Dubbo 核心概念和架构

在这里插入图片描述

以上是 Dubbo 的工作原理图,从抽象架构上分为两层:服务治理抽象控制面Dubbo 数据面

  • 服务治理控制面。服务治理控制面不是特指如注册中心类的单个具体组件,而是对 Dubbo 治理体系的抽象表达。控制面包含协调服务发现的注册中心、流量管控策略、Dubbo Admin 控制台等,如果采用了 Service Mesh 架构则还包含 Istio 等服务网格控制面。
  • Dubbo 数据面。数据面代表集群部署的所有 Dubbo 进程,进程之间通过 RPC 协议实现数据交换,Dubbo 定义了微服务应用开发与调用规范并负责完成数据传输的编解码工作。
    • 服务消费者 (Dubbo Consumer),发起业务调用或 RPC 通信的 Dubbo 进程
    • 服务提供者 (Dubbo Provider),接收业务调用或 RPC 通信的 Dubbo 进程

Dubbo 数据面

从数据面视角,Dubbo 帮助解决了微服务实践中的以下问题:

  • Dubbo 作为 服务开发框架 约束了微服务定义、开发与调用的规范,定义了服务治理流程及适配模式
  • Dubbo 作为 RPC 通信协议实现 解决服务间数据传输的编解码问题
服务开发框架

微服务的目标是构建足够小的、自包含的、独立演进的、可以随时部署运行的分布式应用程序,几乎每个语言都有类似的应用开发框架来帮助开发者快速构建此类微服务应用,比如 Java 微服务体系的 Spring Boot,它帮 Java 微服务开发者以最少的配置、最轻量的方式快速开发、打包、部署与运行应用。

微服务的分布式特性,使得应用间的依赖、网络交互、数据传输变得更频繁,因此不同的应用需要定义、暴露或调用 RPC 服务,那么这些 RPC 服务如何定义、如何与应用开发框架结合、服务调用行为如何控制?这就是 Dubbo 服务开发框架的含义,Dubbo 在微服务应用开发框架之上抽象了一套 RPC 服务定义、暴露、调用与治理的编程范式,比如 Dubbo Java 作为服务开发框架,当运行在 Spring 体系时就是构建在 Spring Boot 应用开发框架之上的微服务开发框架,并在此之上抽象了一套 RPC 服务定义、暴露、调用与治理的编程范式。

在这里插入图片描述

Dubbo 作为服务开发框架包含的具体内容如下:

  • RPC 服务定义、开发范式。比如 Dubbo 支持通过 IDL 定义服务,也支持编程语言特有的服务开发定义方式,如通过 Java Interface 定义服务。
  • RPC 服务发布与调用 API。Dubbo 支持同步、异步、Reactive Streaming 等服务调用编程模式,还支持请求上下文 API、设置超时时间等。
  • 服务治理策略、流程与适配方式等。作为服务框架数据面,Dubbo 定义了服务地址发现、负载均衡策略、基于规则的流量路由、Metrics 指标采集等服务治理抽象,并适配到特定的产品实现。
通信协议

Dubbo 从设计上不绑定任何一款特定通信协议,HTTP/2、REST、gRPC、JsonRPC、Thrift、Hessian2 等几乎所有主流的通信协议,Dubbo 框架都可以提供支持。 这样的 Protocol 设计模式给构建微服务带来了最大的灵活性,开发者可以根据需要如性能、通用型等选择不同的通信协议,不再需要任何的代理来实现协议转换,甚至你还可以通过 Dubbo 实现不同协议间的迁移。

在这里插入图片描述

Dubbo Protocol 被设计支持扩展,您可以将内部私有协议适配到 Dubbo 框架上,进而将私有协议接入 Dubbo 体系,以享用 Dubbo 的开发体验与服务治理能力。比如 Dubbo3 的典型用户阿里巴巴,就是通过扩展支持 HSF 协议实现了内部 HSF 框架到 Dubbo3 框架的整体迁移。

Dubbo 还支持多协议暴露,您可以在单个端口上暴露多个协议,Dubbo Server 能够自动识别并确保请求被正确处理,也可以将同一个 RPC 服务发布在不同的端口(协议),为不同技术栈的调用方服务。

Dubbo 提供了两款内置高性能 Dubbo2、Triple (兼容 gRPC) 协议实现,以满足部分微服务用户对高性能通信的诉求,两者最开始都设计和诞生于阿里巴巴内部的高性能通信业务场景。

  • Dubbo2 协议是在 TCP 传输层协议之上设计的二进制通信协议
  • Triple 则是基于 HTTP/2 之上构建的支持流式模式的通信协议,并且 Triple 完全兼容 gRPC 但实现上做了更多的符合 Dubbo 框架特点的优化。

总的来说,Dubbo 对通信协议的支持具有以下特点:

  • 不绑定通信协议
  • 提供高性能通信协议实现
  • 支持流式通信模型
  • 不绑定序列化协议
  • 支持单个服务的多协议暴露
  • 支持单端口多协议发布
  • 支持一个应用内多个服务使用不同通信协议

Dubbo 服务治理

服务开发框架解决了开发与通信的问题,但在微服务集群环境下,我们仍需要解决无状态服务节点动态变化、外部化配置、日志跟踪、可观测性、流量管理、高可用性、数据一致性等一系列问题,我们将这些问题统称为服务治理。

Dubbo 抽象了一套微服务治理模式并发布了对应的官方实现,服务治理可帮助简化微服务开发与运维,让开发者更专注在微服务业务本身。

服务治理抽象

以下展示了 Dubbo 核心的服务治理功能定义

在这里插入图片描述

  • 地址发现

Dubbo 服务发现具备高性能、支持大规模集群、服务级元数据配置等优势,默认提供 Nacos、Zookeeper、Consul 等多种注册中心适配,与 Spring Cloud、Kubernetes Service 模型打通,支持自定义扩展。

  • 负载均衡

Dubbo 默认提供加权随机、加权轮询、最少活跃请求数优先、最短响应时间优先、一致性哈希和自适应负载等策略

  • 流量路由

Dubbo 支持通过一系列流量规则控制服务调用的流量分布与行为,基于这些规则可以实现基于权重的比例流量分发、灰度验证、金丝雀发布、按请求参数的路由、同区域优先、超时配置、重试、限流降级等能力。

  • 链路追踪

Dubbo 官方通过适配 OpenTelemetry 提供了对 Tracing 全链路追踪支持,用户可以接入支持 OpenTelemetry 标准的产品如 Skywalking、Zipkin 等。另外,很多社区如 Skywalking、Zipkin 等在官方也提供了对 Dubbo 的适配。

  • 可观测性

Dubbo 实例通过 Prometheus 等上报 QPS、RT、请求次数、成功率、异常次数等多维度的可观测指标帮助了解服务运行状态,通过接入 Grafana、Admin 控制台帮助实现数据指标可视化展示。

Dubbo 服务治理生态还提供了对 API 网关限流降级数据一致性认证鉴权等场景的适配支持。

Dubbo Admin

Admin 控制台提供了 Dubbo 集群的可视化视图,通过 Admin 你可以完成集群的几乎所有管控工作。

  • 查询服务、应用或机器状态
  • 创建项目、服务测试、文档管理等
  • 查看集群实时流量、定位异常问题等
  • 流量比例分发、参数路由等流量管控规则下发

在这里插入图片描述

服务网格

将 Dubbo 接入 Istio 等服务网格治理体系。

在这里插入图片描述

Dubbo入门开发实战

实战案例介绍

在Dubbo中所有的的服务调用都是基于接口去进行双方交互的。双方协定好Dubbo调用中的接口,提供者来提供实现类并且注册到注册中心上。

调用方则只需要引入该接口,并且同样注册到相同的注册中心上(消费者)。即可利用注册中心来实现集群感知功能,之后消费者即可对提供者进行调用。

我们所有的项目都是基于Maven去进行创建,这样相互在引用的时候只需要以依赖的形式进行展现就可以了。并且这里我们会通过maven的父工程来统一依赖的版本。

程序实现分为以下几步骤:

  1. 建立maven工程 并且 创建API模块: 用于规范双方接口协定
  2. 提供provider模块,引入API模块,并且对其中的服务进行实现。将其注册到注册中心上,对外来统一提供服务。
  3. 提供consumer模块,引入API模块,并且引入与提供者相同的注册中心。再进行服务调用。

下面就以Zookeeper为注册中心,使用springBoot搭建项目,来看下具体的步骤。

基于Spring Boot Starter开发

安装Zookeeper

以Zookeeper为注册中心,要先安装Zookeeper。本文是使用decker安装单机模式。

具体安装步骤可参考网上其他文章。

父工程dubbo-demo

创建maven工程dubbo-demo。

添加依赖

<properties>
  <dubbo.version>3.2.0-beta.4</dubbo.version>
  <spring-boot.version>2.7.8</spring-boot.version>
  <maven.compiler.source>17</maven.compiler.source>
  <maven.compiler.target>17</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencyManagement>
  <dependencies>
    <!-- Spring Boot -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>${spring-boot.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>

    <!-- Dubbo -->
    <dependency>
      <groupId>org.apache.dubbo</groupId>
      <artifactId>dubbo-bom</artifactId>
      <version>${dubbo.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>

    <dependency>
      <groupId>org.apache.dubbo</groupId>
      <artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
      <version>${dubbo.version}</version>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>


<build>
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>${spring-boot.version}</version>
      </plugin>
    </plugins>
  </pluginManagement>
</build>

在这份配置中,定义了 dubbo 和 zookeeper(以及对应的连接器 curator)的依赖。

添加了上述的配置以后,可以通过 IDEA 的 Maven - Reload All Maven Projects 刷新依赖。

接口服务模块service-api

  1. 创建maven模块service-api

  2. 定义接口

这里为了方便,只是写一个基本的方法

package com.cys.service;


public interface HelloService {

    /**
     * 接口方法
     *
     * @param name
     * @return
     */
    String sayHello(String name);
}

HelloService 中,定义了 sayHello 这个方法。后续服务端发布的服务,消费端订阅的服务都是围绕着 HelloService 接口展开的。

接口提供者service-provider

  1. 创建maven模块service-provider

引入相关依赖

<dependencies>
  <dependency>
    <groupId>com.cys</groupId>
    <artifactId>service-api</artifactId>
    <version>1.0-SNAPSHOT</version>
  </dependency>

  <!-- dubbo -->
  <dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
  </dependency>
  <dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
    <type>pom</type>
    <exclusions>
      <exclusion>
        <artifactId>slf4j-reload4j</artifactId>
        <groupId>org.slf4j</groupId>
      </exclusion>
    </exclusions>
  </dependency>

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

</dependencies>

在这份配置中,定义了 dubbo 和 zookeeper(以及对应的连接器 curator)的依赖。

添加了上述的配置以后,可以通过 IDEA 的 Maven - Reload All Maven Projects 刷新依赖。

  1. 配置文件
server:
  port: 9000
dubbo:
  application:
    name: dubbo-springboot-demo-provider
    qosEnable: false  # qos是运维使用的后台,默认自动启动,端口为22222,可以关闭,免得和消费者冲突
  protocol:
    name: dubbo
    port:  20891
  registry:
    address: zookeeper://${zookeeper.address:127.0.0.1}:2181

在这个配置文件中,定义了 Dubbo 的应用名、Dubbo 协议信息、Dubbo 使用的注册中心地址。

  1. 实现服务接口HelloService

使用@DubboService注解,代码如下:

package com.cys.service.impl;

import com.cys.service.HelloService;
import org.apache.dubbo.config.annotation.DubboService;


@DubboService
public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String name) {
        return "hello:"+name;
    }
}

HelloServiceImpl 中,实现了 HelloService 接口,对于 sayHello 方法返回 "hello:"+name

注:在HelloServiceImpl 类中添加了 @DubboService 注解,通过这个配置可以基于 Spring Boot 去发布 Dubbo 服务。

  1. 新增提供者启动类ServiceProviderApplication

代码如下:

package com.cys.service;

import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


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

正常启动即可。

接口消费者service-consumer

  1. 创建maven模块service-consumer

添加依赖:

<dependencies>
    <dependency>
        <groupId>com.cys</groupId>
        <artifactId>service-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <!-- dubbo -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
        <type>pom</type>
        <exclusions>
            <exclusion>
                <artifactId>slf4j-reload4j</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- spring boot starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>RELEASE</version>
        <scope>compile</scope>
    </dependency>

</dependencies>
  1. 配置文件
server:
  port: 8088
dubbo:
  application:
    name: dubbo-springboot-demo-consumer
    qosEnable: false
  protocol:
    name: dubbo
    port: 20890
  registry:
    address: zookeeper://${zookeeper.address:127.0.0.1}:2181

在这个配置文件中,定义了 Dubbo 的应用名、Dubbo 协议信息、Dubbo 使用的注册中心地址。

  1. 创建controller

使用注解@DubboReference表明当前引用为dubbo服务的引用。

package com.cys.service.controller;

import com.cys.service.HelloService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class HelloController {

    @DubboReference
    HelloService helloService;

    @GetMapping("hello")
    public String sayHello(@RequestParam("name") String name) {
        String s = helloService.sayHello(name);
        System.out.println(s);
        return s;
    }
}
  1. 启动类ServiceConsumerApplication
package com.cys.service;

import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


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

启动消费者。

然后访问我们的接口即可完成调用。

Dubbo启动时qos-server can not bind localhost22222错误解决:

配置qosEnable: false

dubbo:
application:
 name: dubbo-springboot-demo-consumer
 qosEnable: false

或者修改服务端和消费端端口不同也可以。

基于Spring XML 开发

除了使用注解开发使用dubbo的微服务,还可以使用xml的方式。

默认已经安装Zookeeper。

  1. 初始化项目

创建项目dubbo-demo-xml。

需要在 src/main/java 目录下创建 cys.com.service.apicys.com.service.clientcys.com.service.provider 三个 package。

后续我们将在 api 下创建对应的接口,在 client 下创建对应客户端订阅服务的功能,在 provider 下创建对应服务端的实现以及发布服务的功能。

上述三个 package 分别对应了应用共同依赖的 api、消费端应用的模块、服务端应用的模块。在实际部署中需要拆成三个工程,消费端和服务的共同依赖 api 模块。从简单出发,本教程将在同一个工程中进行开发,区分多个启动类。

  1. 添加 Maven 依赖
<dependencies>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo</artifactId>
        <version>3.1.6</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.25</version>
    </dependency>

    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-x-discovery</artifactId>
        <version>5.2.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.8.0</version>
    </dependency>
</dependencies>

定义了 dubbo 和 zookeeper(以及对应的连接器 curator)的依赖。

添加了上述的配置以后,可以通过 IDEA 的 Maven - Reload All Maven Projects 刷新依赖。

  1. 定义服务接口

服务接口 Dubbo 中沟通消费端和服务端的桥梁。

在 cys.com.service.api 下建立 HelloService 接口,定义如下:

package com.cys.service.api;


public interface HelloService {

    /**
     * 接口方法
     *
     * @param name
     * @return
     */
    String sayHello(String name);
}

定义了 sayHello 这个方法。后续服务端发布的服务,消费端订阅的服务都是围绕着 HelloService 接口展开的。

  1. 定义服务端的实现

定义了服务接口之后,可以在服务端这一侧定义对应的实现,这部分的实现相对于消费端来说是远端的实现,本地没有相关的信息。

package com.cys.service.provider;

import com.cys.service.api.HelloService;

public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String name) {
        return "hello:"+name;
    }
}
  1. 配置服务端 XML 配置文件

resources 资源文件夹下建立 dubbo-demo-provider.xml 文件,定义如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
       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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder/>

    <!-- 定义应用名 -->
    <dubbo:application name="demo-provider"/>

    <!-- 定义注册中心地址 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <!-- 定义实现类对应的 bean -->
    <bean id="helloService" class="com.cys.service.provider.HelloServiceImpl"/>
    <!-- 定义服务信息,引用上面的 bean -->
    <dubbo:service interface="com.cys.service.api.HelloService" ref="helloService"/>

</beans>

在这个配置文件中,定义了 Dubbo 的应用名、Dubbo 使用的注册中心地址、发布服务的 spring bean 以及通过 Dubbo 去发布这个 bean。

  1. 配置消费端 XML 配置文件

resources 资源文件夹下建立 dubbo-demo-consumer.xml 文件,定义如下:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
       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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder/>

    <!-- 定义应用名 -->
    <dubbo:application name="demo-provider"/>

    <!-- 定义注册中心地址 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <!-- 定义订阅信息,Dubbo 会在 Spring Context 中创建对应的 bean -->
    <dubbo:reference id="helloService" interface="com.cys.service.api.HelloService"/>

</beans>

在这个配置文件中,定义了 Dubbo 的应用名、Dubbo 使用的注册中心地址、订阅的服务信息。

  1. 基于 Spring 配置服务端启动类

除了配置 XML 配置文件之外,我们还需要创建基于 Spring Context 的启动类。

首先,我们先创建服务端的启动类。

package com.cys.service.provider;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.concurrent.CountDownLatch;


public class Application {

    public static void main(String[] args) throws InterruptedException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-demo-provider.xml");
        context.start();

        // 挂起主线程,防止退出
        new CountDownLatch(1).await();
    }
}

在这个启动类中,配置了一个 ClassPathXmlApplicationContext 去读取我们前面第 6 步中定义的 dubbo-demo-provider.xml 配置文件。

  1. 基于 Spring 配置消费端启动类
package com.cys.service.client;

import com.cys.service.api.HelloService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;


public class Application {
    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-demo-consumer.xml");
        context.start();
        HelloService helloService = (HelloService) context.getBean("helloService");

        String message = helloService.sayHello("dubbo");
        System.out.println("Receive result ======> " + message);
        System.in.read();
        System.exit(0);
    }
}

在这个启动类中,主要执行了三个功能:

  1. 配置了一个 ClassPathXmlApplicationContext 去读取我们前面第 7 步中定义的 dubbo-demo-consumer.xml 配置文件

  2. 从 Spring Context 中获取名字为 greetingsService 的由 Dubbo 创建的 bean

  3. 通过这个 bean 对远端发起调用

  4. 启动应用

首先是启动 com.cys.service.provider.Application ,等待一会出现如下所示的日志(Dubbo Application[1.1](demo-provider) is ready)即代表服务提供者启动完毕,标志着该服务提供者可以对外提供服务了。

[DUBBO] Dubbo Application[1.1](demo-provider) is ready., dubbo version: 3.1.6, current host: 30.221.128.96

然后是启动com.cys.service.client.Application ,等待一会出现如下图所示的日志(hi, dubbo )即代表服务消费端启动完毕并调用到服务端成功获取结果。

Receive result ======> hi, dubbo

Dubbo配置

说明:官网上有完整的配置选项和配置原理,具体开发遇到问题请查阅官网:配置项手册

实现原理

为了更好地管理各种配置,Dubbo 抽象了一套结构化的配置组件,各组件总体以用途划分,分别控制不同作用域的行为。

在这里插入图片描述

从实现原理层面,最终 Dubbo 所有的配置项都会被组装到 URL 中,以 URL 为载体在后续的启动、RPC 调用过程中传递,进而控制框架行为。

service 与 reference

servicereference 是 Dubbo 最基础的两个配置项,它们用来将某个指定的接口或实现类注册为 Dubbo 服务,并通过配置项控制服务的行为。

  • service 用于服务提供者端,通过 service 配置的接口和实现类将被定义为标准的 Dubbo 服务,从而实现对外提供 RPC 请求服务。
  • reference 用于服务消费者端,通过 reference 配置的接口将被定义为标准的 Dubbo 服务,生成的 proxy 可发起对远端的 RPC 请求。

一个应用中可以配置任意多个 servicereference

consumer 与 provider

  • 当应用内有多个 reference 配置时,consumer 指定了这些 reference 共享的默认值,如共享的超时时间等以简化繁琐的配置,如某个 reference 中单独设置了配置项值则该 reference 中的配置优先级更高。
  • 当应用内有多个 service 配置时,provider 指定了这些 service 共享的默认值,如某个 service 中单独设置了配置项值则该 service 中的配置优先级更高。

consumer 组件还可以对 reference 进行虚拟分组,不通分组下的 reference 可有不同的 consumer 默认值设定;如在 XML 格式配置中,<dubbo:reference /> 标签可通过嵌套在 <dubbo:consumer /> 标签之中实现分组。provider 与 service 之间也可以实现相同的效果。

配置项

针对dubbo不同的组件,可以有很多配置项。大致组件如下:

组件名称描述范围是否必须配置
application指定应用名等应用级别相关信息一个应用内只允许出现一个必选
service声明普通接口或实现类为 Dubbo 服务一个应用内可以有 0 到多个 serviceservice/reference 至少一种
reference声明普通接口为 Dubbo 服务一个应用内可以有 0 到多个 referenceservice/reference 至少一种
protocol要暴露的 RPC 协议及相关配置如端口号等一个应用可配置多个,一个 protocol 可作用于一组 service&reference可选,默认 dubbo
registry注册中心类型、地址及相关配置一个应用内可配置多个,一个 registry 可作用于一组 service&reference必选
config-center配置中心类型、地址及相关配置一个应用内可配置多个,所有服务共享可选
metadata-report元数据中心类型、地址及相关配置一个应用内可配置多个,所有服务共享可选
consumerreference 间共享的默认配置一个应用内可配置多个,一个 consumer 可作用于一组 reference可选
providerservice 间共享的默认配置一个应用内可配置多个,一个 provider 可作用于一组 service可选
monitor监控系统类型及地址一个应用内只允许配置一个可选
metrics数据采集模块相关配置一个应用内只允许配置一个可选
sslssl/tls 安全链接相关的证书等配置一个应用内只允许配置一个可选
method指定方法级的配置service 和 reference 的子配置可选
argument某个方法的参数配置method的子配置可选

下面只讲一些常用的,具体的配置使用到的时候查阅官网。

application

每个应用必须要有且只有一个 application 配置

对应的配置类:org.apache.dubbo.config.ApplicationConfig

属性对应URL参数类型是否必填缺省值作用描述兼容性
nameapplicationstring必填服务治理当前应用名称,用于注册中心计算应用间依赖关系,注意:消费者和提供者应用名不要一样,此参数不是匹配条件,你当前项目叫什么名字就填什么,和提供者消费者角色无关,比如:kylin应用调用了morgan应用的服务,则kylin项目配成kylin,morgan项目配成morgan,可能kylin也提供其它服务给别人使用,但kylin项目永远配成kylin,这样注册中心将显示kylin依赖于morgan2.7.0以上版
ownerownerstring可选服务治理应用负责人,用于服务治理,请填写负责人公司邮箱前缀2.0.5以上版本
organizationorganizationstring可选服务治理组织名称(BU或部门),用于注册中心区分服务来源,此配置项建议不要使用autoconfig,直接写死在配置中,比如china,intl,itu,crm,asc,dw,aliexpress等2.0.0以上版本
architecturearchitecturestring可选服务治理用于服务分层对应的架构。如,intl、china。不同的架构使用不同的分层。2.0.7以上版本
environmentenvironmentstring可选服务治理应用环境,如:develop/test/product,不同环境使用不同的缺省值,以及作为只用于开发测试功能的限制条件2.0.0以上版本
versionapplication.versionstring可选服务治理当前应用的版本2.7.0以上版本
dumpDirectorydump.directorystring可选服务治理当进程出问题如线程池满时,框架自动dump文件的存储路径2.7.0以上版本
qosEnableqos.enableboolean可选服务治理是否启用 qos 运维端口2.7.0以上版本
qosHostqos.hoststring可选服务治理监听的网络接口地址,默认 0.0.0.02.7.3以上版本
qosPortqos.portint可选服务治理监听的网络端口2.7.0以上版本
qosAcceptForeignIpqos.accept.foreign.ipboolean可选服务治理安全配置,是否接收除localhost本机访问之外的外部请求2.7.0以上版本
shutwaitdubbo.service.shutdown.waitstring可选服务治理优雅停机时 shutdown 的等待时间(ms)2.7.0以上版本
hostnamestring可选本机主机名服务治理主机名2.7.5以上版本
registerConsumerregisterConsumerboolean可选true服务治理是否注册实例到注册中心。当时实例为纯消费者时才设置为false2.7.5以上版本

service

服务提供者暴露服务配置。

对应的配置类:org.apache.dubbo.config.ServiceConfig

属性对应URL参数类型是否必填缺省值作用描述兼容性
interfaceclass必填服务发现服务接口名,指定当前需要进行对外暴露的接口是什么1.0.0以上版本
refobject必填服务发现服务对象实现引用,一般我们在生产级别都是使用Spring去进行Bean托管的,所以这里面一般也指的是Spring中的BeanId1.0.0以上版本
versionversionstring可选0.0.0服务发现服务版本,建议使用两位数字版本,如:1.0,通常在接口不兼容时版本号才需要升级。不同的版本号,消费者在消费的时候只会根据固定的版本号进行消费。1.0.0以上版本
groupgroupstring可选服务发现服务分组,当一个接口有多个实现,可以用分组区分1.0.7以上版本
timeouttimeoutint可选1000性能调优远程服务调用超时时间(毫秒)2.0.0以上版本
retriesretriesint可选2性能调优远程服务调用重试次数,不包括第一次调用,不需要重试请设为02.0.0以上版本
connectionsconnectionsint可选100性能调优对每个提供者的最大连接数,rmi、http、hessian等短连接协议表示限制连接数,dubbo等长连接协表示建立的长连接个数2.0.0以上版本
loadbalanceloadbalancestring可选random性能调优负载均衡策略,可选值: * random - 随机; * roundrobin - 轮询; * leastactive - 最少活跃调用; * consistenthash - 哈希一致 (2.1.0以上版本); * shortestresponse - 最短响应 (2.7.7以上版本);2.0.0以上版本
asyncasyncboolean可选false性能调优是否缺省异步执行,不可靠异步,只是忽略返回值,不阻塞执行线程2.0.0以上版本
registrystring可选缺省向所有registry注册配置关联向指定注册中心注册,在多个注册中心时使用,值为dubbo:registry的id属性,多个注册中心ID用逗号分隔,如果不想将该服务注册到任何registry,可将值设为N/A2.0.0以上版本

reference

服务消费者引用服务配置。

对应的配置类: org.apache.dubbo.config.ReferenceConfig

属性对应URL参数类型是否必填缺省值作用描述兼容性
idstring必填配置关联服务引用的BeanId1.0.0以上版本
interfaceclass必填服务发现服务接口名1.0.0以上版本
versionversionstring可选服务发现服务版本,与服务提供者的版本一致1.0.0以上版本
groupgroupstring可选服务发现服务分组,当一个接口有多个实现,可以用分组区分,必需和服务提供方一致1.0.7以上版本
timeouttimeoutlong可选缺省使用dubbo:consumer的timeout性能调优服务方法调用超时时间(毫秒)1.0.5以上版本
retriesretriesint可选缺省使用dubbo:consumer的retries性能调优远程服务调用重试次数,不包括第一次调用,不需要重试请设为02.0.0以上版本
connectionsconnectionsint可选缺省使用dubbo:consumer的connections性能调优对每个提供者的最大连接数,rmi、http、hessian等短连接协议表示限制连接数,dubbo等长连接协表示建立的长连接个数2.0.0以上版本
loadbalanceloadbalancestring可选缺省使用dubbo:consumer的loadbalance性能调优负载均衡策略,可选值: * random - 随机; * roundrobin - 轮询; * leastactive - 最少活跃调用; * consistenthash - 哈希一致 (2.1.0以上版本); * shortestresponse - 最短响应 (2.7.7以上版本);2.0.0以上版本
asyncasyncboolean可选缺省使用dubbo:consumer的async性能调优是否异步执行,不可靠异步,只是忽略返回值,不阻塞执行线程2.0.0以上版本
registrystring可选缺省将从所有注册中心获服务列表后合并结果配置关联从指定注册中心注册获取服务列表,在多个注册中心时使用,值为dubbo:registry的id属性,多个注册中心ID用逗号分隔2.0.0以上版本

registry

注册中心配置。

对应的配置类: org.apache.dubbo.config.RegistryConfig。同时如果有多个不同的注册中心,可以声明多个 <dubbo:registry> 标签,并在 <dubbo:service><dubbo:reference>registry 属性指定使用的注册中心。

属性对应URL参数类型是否必填缺省值作用描述兼容性
idstring可选配置关联注册中心引用BeanId,可以在<dubbo:service registry=“”>或<dubbo:reference registry=“”>中引用此ID1.0.16以上版本
addresshost:portstring必填服务发现注册中心服务器地址,如果地址没有端口缺省为9090,同一集群内的多个地址用逗号分隔,如:ip:port,ip:port,不同集群的注册中心,请配置多个dubbo:registry标签1.0.16以上版本
protocolstring可选dubbo服务发现注册中心地址协议,支持dubbo, multicast, zookeeper, redis, consul(2.7.1), sofa(2.7.2), etcd(2.7.2), nacos(2.7.2)等协议2.0.0以上版本
portint可选9090服务发现注册中心缺省端口,当address没有带端口时使用此端口做为缺省值2.0.0以上版本
usernamestring可选服务治理登录注册中心用户名,如果注册中心不需要验证可不填2.0.0以上版本
passwordstring可选服务治理登录注册中心密码,如果注册中心不需要验证可不填2.0.0以上版本
transportregistry.transporterstring可选netty性能调优网络传输方式,可选mina,netty2.0.0以上版本
timeoutregistry.timeoutint可选5000性能调优注册中心请求超时时间(毫秒)2.0.0以上版本
sessionregistry.sessionint可选60000性能调优注册中心会话超时时间(毫秒),用于检测提供者非正常断线后的脏数据,比如用心跳检测的实现,此时间就是心跳间隔,不同注册中心实现不一样。2.1.0以上版本

protocol

服务提供者协议配置。

对应的配置类: org.apache.dubbo.config.ProtocolConfig。同时,如果需要支持多协议,可以声明多个 <dubbo:protocol> 标签,并在 <dubbo:service> 中通过 protocol 属性指定使用的协议。

属性对应URL参数类型是否必填缺省值作用描述兼容性
idstring可选dubbo配置关联协议BeanId,可以在<dubbo:service protocol=“”>中引用此ID,如果ID不填,缺省和name属性值一样,重复则在name后加序号。2.0.5以上版本
namestring必填dubbo性能调优协议名称2.0.5以上版本
portint可选dubbo协议缺省端口为20880,rmi协议缺省端口为1099,http和hessian协议缺省端口为80;如果没有配置port,则自动采用默认端口,如果配置为**-1**,则会分配一个没有被占用的端口。Dubbo 2.4.0+,分配的端口在协议缺省端口的基础上增长,确保端口段可控。服务发现服务端口2.0.5以上版本

method

方法级配置。

对应的配置类: org.apache.dubbo.config.MethodConfig。同时该标签为 servicereference 的子标签,用于控制到方法级。

用于在制定的 dubbo:service 或者 dubbo:reference 中的更具体一个层级,指定具体方法级别在进行RPC操作时候的配置,可以理解为对这上面层级中的配置针对具体方法的特殊处理。

比如:

<dubbo:reference interface="com.xxx.XxxService">
   <dubbo:method name="findXxx" timeout="3000" retries="2" />
</dubbo:reference>
属性对应URL参数类型是否必填缺省值作用描述兼容性
namestring必填标识方法名1.0.8以上版本
timeout.timeoutint可选缺省为的timeout性能调优方法调用超时时间(毫秒)1.0.8以上版本
retries.retriesint可选缺省为dubbo:reference的retries性能调优远程服务调用重试次数,不包括第一次调用,不需要重试请设为02.0.0以上版本
loadbalance.loadbalancestring可选缺省为的loadbalance性能调优负载均衡策略,可选值: * random - 随机; * roundrobin - 轮询; * leastactive - 最少活跃调用; * consistenthash - 哈希一致 (2.1.0以上版本); * shortestresponse - 最短响应 (2.7.7以上版本);2.0.0以上版本
async.asyncboolean可选缺省为dubbo:reference的async性能调优是否异步执行,不可靠异步,只是忽略返回值,不阻塞执行线程1.0.9以上版本

配置方式

API 配置

通过 API 编码方式组装配置、启动 Dubbo、发布及订阅服务。此方式可以支持动态创建 ReferenceConfig/ServiceConfig,结合泛化调用可以满足 API Gateway 或测试平台的需要。

注意:为了更好支持 Dubbo3 应用级服务发现,推荐使用新的DubboBootstrap API。

服务提供者

通过 ServiceConfig 暴露服务接口,发布服务接口到注册中心。

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ProviderConfig;
import org.apache.dubbo.config.ServiceConfig;
import com.xxx.DemoService;
import com.xxx.DemoServiceImpl;

public class DemoProvider {
    public static void main(String[] args) {
        // 服务实现
        DemoService demoService = new DemoServiceImpl();

        // 当前应用配置
        ApplicationConfig application = new ApplicationConfig();
        application.setName("demo-provider");

        // 连接注册中心配置
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("zookeeper://10.20.130.230:2181");

        // 服务提供者协议配置
        ProtocolConfig protocol = new ProtocolConfig();
        protocol.setName("dubbo");
        protocol.setPort(12345);
        protocol.setThreads(200);

        // 注意:ServiceConfig为重对象,内部封装了与注册中心的连接,以及开启服务端口
        // 服务提供者暴露服务配置
        ServiceConfig<DemoService> service = new ServiceConfig<DemoService>(); // 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏
        service.setApplication(application);
        service.setRegistry(registry); // 多个注册中心可以用setRegistries()
        service.setProtocol(protocol); // 多个协议可以用setProtocols()
        service.setInterface(DemoService.class);
        service.setRef(demoService);
        service.setVersion("1.0.0");

        // 暴露及注册服务
        service.export();

        // 挂起等待(防止进程退出)
        System.in.read();
    }
}
服务消费者

通过 ReferenceConfig 引用远程服务,从注册中心订阅服务接口。

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ConsumerConfig;
import org.apache.dubbo.config.ReferenceConfig;
import com.xxx.DemoService;

public class DemoConsumer {
    public static void main(String[] args) {
        // 当前应用配置
        ApplicationConfig application = new ApplicationConfig();
        application.setName("demo-consumer");

        // 连接注册中心配置
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("zookeeper://10.20.130.230:2181");

        // 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
        // 引用远程服务
        ReferenceConfig<DemoService> reference = new ReferenceConfig<DemoService>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
        reference.setApplication(application);
        reference.setRegistry(registry); // 多个注册中心可以用setRegistries()
        reference.setInterface(DemoService.class);
        reference.setVersion("1.0.0");

        // 和本地bean一样使用demoService
        // 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用
        DemoService demoService = reference.get();
        demoService.sayHello("Dubbo");
    }
}
Bootstrap API

通过 DubboBootstrap API 可以减少重复配置,更好控制启动过程,支持批量发布/订阅服务接口,还可以更好支持 Dubbo3 的应用级服务发现。

服务端

import org.apache.dubbo.config.bootstrap.DubboBootstrap;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ProviderConfig;
import org.apache.dubbo.config.ServiceConfig;
import com.xxx.DemoService;
import com.xxx.DemoServiceImpl;

public class DemoProvider {
    public static void main(String[] args) {

        ConfigCenterConfig configCenter = new ConfigCenterConfig();
        configCenter.setAddress("zookeeper://127.0.0.1:2181");

        // 服务提供者协议配置
        ProtocolConfig protocol = new ProtocolConfig();
        protocol.setName("dubbo");
        protocol.setPort(12345);
        protocol.setThreads(200);

        // 注意:ServiceConfig为重对象,内部封装了与注册中心的连接,以及开启服务端口
        // 服务提供者暴露服务配置
        ServiceConfig<DemoService> demoServiceConfig = new ServiceConfig<>();
        demoServiceConfig.setInterface(DemoService.class);
        demoServiceConfig.setRef(new DemoServiceImpl());
        demoServiceConfig.setVersion("1.0.0");

        // 第二个服务配置
        ServiceConfig<FooService> fooServiceConfig = new ServiceConfig<>();
        fooServiceConfig.setInterface(FooService.class);
        fooServiceConfig.setRef(new FooServiceImpl());
        fooServiceConfig.setVersion("1.0.0");

        ...

        // 通过DubboBootstrap简化配置组装,控制启动过程
        DubboBootstrap.getInstance()
                .application("demo-provider") // 应用配置
                .registry(new RegistryConfig("zookeeper://127.0.0.1:2181")) // 注册中心配置
                .protocol(protocol) // 全局默认协议配置
                .service(demoServiceConfig) // 添加ServiceConfig
                .service(fooServiceConfig)
                .start()    // 启动Dubbo
                .await();   // 挂起等待(防止进程退出)
    }
}

消费端

import org.apache.dubbo.config.bootstrap.DubboBootstrap;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ProviderConfig;
import org.apache.dubbo.config.ServiceConfig;
import com.xxx.DemoService;
import com.xxx.DemoServiceImpl;

public class DemoConsumer {
    public static void main(String[] args) {

        // 引用远程服务
        ReferenceConfig<DemoService> demoServiceReference = new ReferenceConfig<DemoService>();
        demoServiceReference.setInterface(DemoService.class);
        demoServiceReference.setVersion("1.0.0");

        ReferenceConfig<FooService> fooServiceReference = new ReferenceConfig<FooService>();
        fooServiceReference.setInterface(FooService.class);
        fooServiceReference.setVersion("1.0.0");

        // 通过DubboBootstrap简化配置组装,控制启动过程
        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
        bootstrap.application("demo-consumer") // 应用配置
                .registry(new RegistryConfig("zookeeper://127.0.0.1:2181")) // 注册中心配置
                .reference(demoServiceReference) // 添加ReferenceConfig
                .reference(fooServiceReference)
                .start();    // 启动Dubbo

        ...

        // 和本地bean一样使用demoService
        // 通过Interface获取远程服务接口代理,不需要依赖ReferenceConfig对象
        DemoService demoService = DubboBootstrap.getInstance().getCache().get(DemoService.class);
        demoService.sayHello("Dubbo");

        FooService fooService = DubboBootstrap.getInstance().getCache().get(FooService.class);
        fooService.greeting("Dubbo");
    }

}
其它配置

API 提供了最灵活丰富的配置能力,以下是一些可配置组件示例。

  1. 基本配置

可以在 DubboBootstrap 中设置全局基本配置,包括应用配置、协议配置、注册中心、配置中心、元数据中心、模块、监控、SSL、provider 配置、consumer 配置等。

// 注册中心
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://192.168.10.1:2181");
...

// 服务提供者协议配置
ProtocolConfig protocol = new ProtocolConfig();
protocol.setName("dubbo");
protocol.setPort(12345);
protocol.setThreads(200);
...

// 配置中心
ConfigCenterConfig configCenter = new ConfigCenterConfig();
configCenter.setAddress("zookeeper://192.168.10.2:2181");
...

// 元数据中心
MetadataReportConfig metadataReport = new MetadataReportConfig();
metadataReport.setAddress("zookeeper://192.168.10.3:2181");
...

// Metrics
MetricsConfig metrics = new MetricsConfig();
metrics.setProtocol("dubbo");
...

// SSL
SslConfig ssl = new SslConfig();
ssl.setServerKeyCertChainPath("/path/ssl/server-key-cert-chain");
ssl.setServerPrivateKeyPath("/path/ssl/server-private-key");
...

// Provider配置(ServiceConfig默认配置)
ProviderConfig provider = new ProviderConfig();
provider.setGroup("demo");
provider.setVersion("1.0.0");
...

// Consumer配置(ReferenceConfig默认配置)
ConsumerConfig consumer = new ConsumerConfig();
consumer.setGroup("demo");
consumer.setVersion("1.0.0");
consumer.setTimeout(2000);
...

DubboBootstrap.getInstance()
    .application("demo-app")
    .registry(registry)
    .protocol(protocol)
    .configCenter(configCenter)
    .metadataReport(metadataReport)
    .module(new ModuleConfig("module"))
    .metrics(metrics)
  	.ssl(ssl)
  	.provider(provider)
  	.consumer(consumer)
  	...
  	.start();
  1. 方法级设置
...

// 方法级配置
List<MethodConfig> methods = new ArrayList<MethodConfig>();
MethodConfig method = new MethodConfig();
method.setName("sayHello");
method.setTimeout(10000);
method.setRetries(0);
methods.add(method);

// 引用远程服务
ReferenceConfig<DemoService> reference = new ReferenceConfig<DemoService>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
...
reference.setMethods(methods); // 设置方法级配置

...
  1. 点对点直连
...

// 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
ReferenceConfig<DemoService> reference = new ReferenceConfig<DemoService>();
// 如果点对点直连,可以用reference.setUrl()指定目标地址,设置url后将绕过注册中心,
// 其中,协议对应provider.setProtocol()的值,端口对应provider.setPort()的值,
// 路径对应service.setPath()的值,如果未设置path,缺省path为接口名
reference.setUrl("dubbo://10.20.130.230:20880/com.xxx.DemoService");

...

Annotation 配置

基于注解开发可以参考Dubbo入门开发实战示例-基于Spring Boot Starter开发。

在 Dubbo Spring Boot 开发中,你只需要增加几个注解,并配置 application.propertiesapplication.yml 文件即可完成 Dubbo 服务定义:

  • 注解有 @DubboService@DubboReferenceEnableDubbo。其中 @DubboService@DubboReference 用于标记 Dubbo 服务,EnableDubbo 启动 Dubbo 相关配置并指定 Spring Boot 扫描包路径。
  • 配置文件 application.propertiesapplication.yml
配置文件

可使用application.yml 或 application.properties文件。

除 service、reference 之外的组件都可以在 application.yml 文件中设置,如果要扩展 service 或 reference 的注解配置,则需要增加 dubbo.properties 配置文件或使用其他非注解如 Java Config 方式,具体请看 扩展注解的配置

service、reference 组件也可以通过 id 与 application 中的全局组件做关联,以下面配置为例:

dubbo:
  application:
    name: dubbo-springboot-demo-provider
  protocol:
    name: dubbo
    port: -1
  registry:
    id: zk-registry
    address: zookeeper://127.0.0.1:2181
  config-center:
    address: zookeeper://127.0.0.1:2181
  metadata-report:
    address: zookeeper://127.0.0.1:2181

通过注解将 service 关联到上文定义的特定注册中心

@DubboService(registry="zk-registry")  // 这个registry要与上面配置的registry的id一致
public class HelloServiceImpl implements HelloService {}

通过 Java Config 配置进行关联也是同样道理

@Configuration
public class ProviderConfiguration {
    @Bean
    public ServiceConfig serviceConfig() {
        ServiceConfig service = new ServiceConfig();
        service.setRegistry("zk-registry");  // 这个registry要与上面配置的registry的id一致
        return service;
    }
}
@DubboService 注解

@Service 注解从 3.0 版本开始就已经废弃,改用 @DubboService,以区别于 Spring 的 @Service 注解

定义好 Dubbo 服务接口后,提供服务接口的实现逻辑,并用 @DubboService 注解标记,就可以实现 Dubbo 的服务暴露

@DubboService
public class HelloServiceImpl implements HelloService {}

如果要设置服务参数,@DubboService 也提供了常用参数的设置方式。如果有更复杂的参数设置需求,则可以考虑使用其他设置方式

@DubboService(version = "1.0.0", group = "dev", timeout = 5000)
public class HelloServiceImpl implements HelloService {}
@DubboReference 注解

@Reference 注解从 3.0 版本开始就已经废弃,改用 @DubboReference,以区别于 Spring 的 @Reference 注解

@Component
public class DemoClient {
    @DubboReference
    private HelloService helloService;
}

@DubboReference 注解将自动注入为 Dubbo 服务代理实例,使用 helloService 即可发起远程服务调用。

@EnableDubbo 注解

@EnableDubbo 注解必须配置,否则将无法加载 Dubbo 注解定义的服务,@EnableDubbo 可以定义在主类上

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

Spring Boot 注解默认只会扫描 main 类所在的 package,如果服务定义在其它 package 中,需要增加配置 EnableDubbo(scanBasePackages = {"org.apache.dubbo.springboot.demo.provider"})

扩展注解配置

虽然可以通过 @DubboServiceDubboReference 调整配置参数(如下代码片段所示),但总体来说注解提供的配置项还是非常有限。在这种情况下,如果有更复杂的参数设置需求,可以使用 Java Configdubbo.properties 两种方式。

@DubboService(version = "1.0.0", group = "dev", timeout = 5000)
@DubboReference(version = "1.0.0", group = "dev", timeout = 5000)
使用 Java Config 代替注解

注意,Java Config 是 DubboServiceDubboReference 的替代方式,对于有复杂配置需求的服务建议使用这种方式。

@Configuration
public class ProviderConfiguration {
    @Beas
    public ServiceConfig serviceConfig() {
        ServiceConfig service = new ServiceConfig();
        service.setInterface(DemoService.class);
        service.setRef(new DemoServiceImpl());
        service.setGroup("dev");
        service.setVersion("1.0.0");
        Map<String, String> parameters = new HashMap<>();
        service.setParameters(parameters);
        return service;
    }
}

配置工作原理

配置来源

Dubbo 默认支持 6 种配置来源:

  • JVM System Properties,JVM -D 参数
  • System environment,JVM进程的环境变量
  • Externalized Configuration,外部化配置,从配置中心读取
  • Application Configuration,应用的属性配置,从Spring应用的Environment中提取"dubbo"打头的属性集
  • API / XML /注解等编程接口采集的配置可以被理解成配置来源的一种,是直接面向用户编程的配置采集方式
  • 从classpath读取配置文件 dubbo.properties

关于dubbo.properties属性:

  1. 如果在 classpath 下有超过一个 dubbo.properties 文件,比如,两个 jar 包都各自包含了 dubbo.properties,dubbo 将随机选择一个加载,并且打印错误日志。
  2. Dubbo 可以自动加载 classpath 根目录下的 dubbo.properties,但是你同样可以使用 JVM 参数来指定路径:-Ddubbo.properties.file=xxx.properties

如果通过多种配置来源指定了相同的配置项,则会出现配置项的互相覆盖,具体覆盖关系和优先级请参考下一小节。

配置加载流程

处理流程

Dubbo 配置加载大概分为两个阶段:

在这里插入图片描述

主要与如下两个阶段:

  • 第一阶段为DubboBootstrap初始化之前,在Spring context启动时解析处理XML配置/注解配置/Java-config 或者是执行API配置代码,创建config bean并且加入到ConfigManager中。
  • 第二阶段为DubboBootstrap初始化过程,从配置中心读取外部配置,依次处理实例级属性配置和应用级属性配置,最后刷新所有配置实例的属性,也就是属性覆盖。
属性覆盖

发生属性覆盖可能有两种情况,并且二者可能是会同时发生的:

  1. 不同配置源配置了相同的配置项

如下,注册中心的地址在4个地方都有配置

在这里插入图片描述

  1. 相同配置源,但在不同层次指定了相同的配置项

属性覆盖是指用配置的属性值覆盖config bean实例的属性,类似Spring PropertyOverrideConfigurer 的作用。

但与PropertyOverrideConfigurer的不同之处是,Dubbo的属性覆盖有多个匹配格式,优先级从高到低依次是:

#1. 指定id的实例级配置
dubbo.{config-type}s.{config-id}.{config-item}={config-item-value}

#2. 指定name的实例级配置
dubbo.{config-type}s.{config-name}.{config-item}={config-item-value}

#3. 应用级配置(单数配置)
dubbo.{config-type}.{config-item}={config-item-value}

属性覆盖处理流程:

按照优先级从高到低依次查找,如果找到此前缀开头的属性,则选定使用这个前缀提取属性,忽略后面的配置。

在这里插入图片描述

外部化配置

外部化配置目的之一是实现配置的集中式管理,这部分业界已经有很多成熟的专业配置系统如 Apollo, Nacos 等,Dubbo 所做的主要是保证能配合这些系统正常工作。

外部化配置和其他本地配置在内容和格式上并无区别,可以简单理解为 dubbo.properties 的外部化存储,配置中心更适合将一些公共配置如注册中心、元数据中心配置等抽取以便做集中管理。

# 将注册中心地址、元数据中心地址等配置集中管理,可以做到统一环境、减少开发侧感知。
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.registry.simplified=true

dubbo.metadata-report.address=zookeeper://127.0.0.1:2181

dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

dubbo.application.qos.port=33333
  • 优先级 外部化配置默认较本地配置有更高的优先级,因此这里配置的内容会覆盖本地配置值,关于各配置形式间的覆盖关系有单独一章说明。
  • 作用域 外部化配置有全局和应用两个级别,全局配置是所有应用共享的,应用级配置是由每个应用自己维护且只对自身可见的。当前已支持的扩展实现有 Zookeeper、Apollo、Nacos。

外部化配置使用方式如下:

  1. 增加 config-center 配置
<dubbo:config-center address="zookeeper://127.0.0.1:2181"/>
  1. 在相应的配置中心(zookeeper、Nacos 等)增加全局配置项,如下以 Nacos 为例:

在这里插入图片描述

开启外部化配置后,registry、metadata-report、protocol、qos 等全局范围的配置理论上都不再需要在应用中配置,应用开发侧专注业务服务配置,一些全局共享的全局配置转而由运维人员统一配置在远端配置中心。

这样能做到的效果就是,应用只需要关心:

  • 服务暴露、订阅配置
  • 配置中心地址 当部署到不同的环境时,其他配置就能自动的被从对应的配置中心读取到。

举例来说,每个应用中 Dubbo 相关的配置只有以下内容可能就足够了,其余的都托管给相应环境下的配置中心:

dubbo
  application
    name: demo
  config-center
    address: nacos://127.0.0.1:8848

自行加载外部化配置

所谓 Dubbo 对配置中心的支持,本质上就是把 .properties 从远程拉取到本地,然后和本地的配置做一次融合。理论上只要 Dubbo 框架能拿到需要的配置就可以正常的启动,它并不关心这些配置是自己加载到的还是应用直接塞给它的,所以Dubbo还提供了以下API,让用户将自己组织好的配置塞给 Dubbo 框架(配置加载的过程是用户要完成的),这样 Dubbo 框架就不再直接和 Apollo 或 Zookeeper 做读取配置交互。

// 应用自行加载配置
Map<String, String> dubboConfigurations = new HashMap<>();
dubboConfigurations.put("dubbo.registry.address", "zookeeper://127.0.0.1:2181");
dubboConfigurations.put("dubbo.registry.simplified", "true");

//将组织好的配置塞给Dubbo框架
ConfigCenterConfig configCenter = new ConfigCenterConfig();
configCenter.setExternalConfig(dubboConfigurations);

Dubbo SPI 机制

SPI介绍

SPI简介

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 目前有不少框架用它来做服务的扩展发现,简单来说,它就是一种动态替换发现的机制。

我们希望在现有的架构或设计基础上,当未来某些方面发生变化的时候,我们能够以最小的改动来适应这种变化。

SPI的优点主要表现模块之间解耦,它符合开闭原则,对扩展开放,对修改关闭。当系统增加新功能时,不需要对现有系统的结构和代码进行修改,仅仅新增一个扩展即可。

JDK中的SPI

在这里插入图片描述

Java中如果想要使用SPI功能,先提供标准服务接口,然后再提供相关接口实现和调用者。这样就可以通过SPI机制中约定好的信息进行查询相应的接口实现。

SPI遵循如下约定:

  1. 当服务提供者提供了接口的一种具体实现后,在META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
  2. 接口实现类所在的jar包放在主程序的classpath中;
  3. 主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
  4. SPI的实现类必须携带一个无参构造方法;

Dubbo中的SPI

一般来说,系统会采用 Factory、IoC、OSGI 等方式管理扩展(插件)生命周期。考虑到 Dubbo 的适用面,不想强依赖 Spring 等 IoC 容器。 而自己造一个小的 IoC 容器,也觉得有点过度设计,所以选择最简单的 Factory 方式管理扩展(插件)。在 Dubbo 中,所有内部实现和第三方实现都是平等的。

  • 平等对待第三方的实现。在 Dubbo 中,所有内部实现和第三方实现都是平等的,用户可以基于自身业务需求,替换 Dubbo 提供的原生实现。
  • 每个扩展点只封装一个变化因子,最大化复用。每个扩展点的实现者,往往都只是关心一件事。如果用户有需求需要进行扩展,那么只需要对其关注的扩展点进行扩展就好,极大的减少用户的工作量。

Dubbo 中的扩展能力是从 JDK 标准的 SPI 扩展点发现机制加强而来,它改进了 JDK 标准的 SPI 以下问题:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。

用户能够基于 Dubbo 提供的扩展能力,很方便基于自身需求扩展其他协议、过滤器、路由等。下面介绍下 Dubbo 扩展能力的特性。

  • 按需加载。Dubbo 的扩展能力不会一次性实例化所有实现,而是用扩展类实例化,减少资源浪费。
  • 增加扩展类的 IOC 能力。Dubbo 的扩展能力并不仅仅只是发现扩展服务实现类,而是在此基础上更进一步,如果该扩展类的属性依赖其他对象,则 Dubbo 会自动的完成该依赖对象的注入功能。
  • 增加扩展类的 AOP 能力。Dubbo 扩展能力会自动的发现扩展类的包装类,完成包装类的构造,增强扩展类的功能。
  • 具备动态选择扩展实现的能力。Dubbo 扩展会基于参数,在运行时动态选择对应的扩展类,提高了 Dubbo 的扩展能力。
  • 可以对扩展实现进行排序。能够基于用户需求,指定扩展实现的执行顺序。
  • 提供扩展点的 Adaptive 能力。该能力可以使的一些扩展类在 consumer 端生效,一些扩展类在 provider 端生效。

从 Dubbo 扩展的设计目标可以看出,Dubbo 实现的一些例如动态选择扩展实现、IOC、AOP 等特性,能够为用户提供非常灵活的扩展能力。

Dubbo 扩展加载流程

在这里插入图片描述

主要步骤为 4 个:

  • 读取并解析配置文件
  • 缓存所有扩展实现
  • 基于用户执行的扩展名,实例化对应的扩展实现
  • 进行扩展实例属性的 IOC 注入以及实例化扩展的包装类,实现 AOP 特性

使用 Dubbo SPI

下面以扩展协议为例进行说明如何利用 Dubbo 提供的扩展能力扩展 Triple 协议。

(1) 在协议的实现 jar 包内放置文本文件:META-INF/dubbo/org.apache.dubbo.remoting.api.WireProtocol

tri=org.apache.dubbo.rpc.protocol.tri.TripleHttp2Protocol

(2) 实现类内容

@Activate
public class TripleHttp2Protocol extends Http2WireProtocol {
    // ...
}

说明下:Http2WireProtocol 实现了 WireProtocol 接口

(3) Dubbo 配置模块中,扩展点均有对应配置属性或标签,通过配置指定使用哪个扩展实现。比如:

<dubbo:protocol name="tri" />

从上面的扩展步骤可以看出,用户基本在黑盒下就完成了扩展。

Dubbo 扩展的应用

Dubbo 的扩展能力非常灵活,在自身功能的实现上无处不在。

在这里插入图片描述

Dubbo 扩展能力使得 Dubbo 项目很方便的切分成一个一个的子模块,实现热插拔特性。用户完全可以基于自身需求,替换 Dubbo 原生实现,来满足自身业务需求。

Dubbo 扩展的使用场景

  • 如果你需要自定义负载均衡策略,你可以使用 Dubbo 扩展能力。
  • 如果你需要实现自定义的注册中心,你可以使用 Dubbo 扩展能力。
  • 如果你需要实现自定义的过滤器,你可以使用 Dubbo 扩展能力。

Dubbo 扩展平等的对待内部实现和第三方实现。更多使用场景,参考官网: SPI 扩展实现

示例:扩展拦截器

创建自定义的Filter:

package com.cys.service.filters;

import org.apache.dubbo.rpc.*;

public class TimeFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        System.out.println("调用前...");
        long t1 = System.currentTimeMillis();
        Result result = invoker.invoke(invocation);
        long t2 = System.currentTimeMillis();
        System.out.println("用时:" + (t2- t1));
        System.out.println("调用后...");
        return result;
    }
}

在resources下创建META-INF/dubbo/org.apache.dubbo.rpc.Filter,内容如下:

timer=com.cys.service.filters.TimeFilter

其中com.cys.service.filters.TimeFilter是实现类的全限定名。

最后在配置文件里使用:

server:
  port: 9000
dubbo:
  application:
    name: dubbo-springboot-demo-provider
    qosEnable: false
  protocol:
    name: dubbo
    port:  20891
  registry:
    address: zookeeper://${zookeeper.address:127.0.0.1}:2181
  provider:
    filter:
      - timer

高级主题

负载均衡策略

内置负载均衡

负载均衡(Load Balance), 其实就是将请求分摊到多个操作单元上进行执行,从而共同完成工作任务。负载均衡策略主要用于客户端存在多个提供者时进行选择某个提供者。

目前 Dubbo 内置了如下负载均衡算法,用户可直接配置使用:

算法特性备注配置值
Weighted Random LoadBalance加权随机默认算法,默认权重相同random (默认)
RoundRobin LoadBalance加权轮询借鉴于 Nginx 的平滑加权轮询算法,默认权重相同。roundrobin
LeastActive LoadBalance最少活跃优先 + 加权随机背后是能者多劳的思想leastactive
Shortest-Response LoadBalance最短响应优先 + 加权随机更加关注响应速度shortestresponse
ConsistentHash LoadBalance一致性哈希确定的入参,确定的提供者,适用于有状态请求consistenthash
P2C LoadBalancePower of Two Choice随机选择两个节点后,继续选择“连接数”较小的那个节点。p2c
Adaptive LoadBalance自适应负载均衡在 P2C 算法基础上,选择二者中 load 最小的那个节点adaptive
Weighted Random
  • 加权随机,按权重设置随机概率。
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
  • 缺点:存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
RoundRobin
  • 加权轮询,按公约后的权重设置轮询比率,循环调用节点
  • 缺点:同样存在慢的提供者累积请求的问题。

加权轮询过程中,如果某节点权重过大,会存在某段时间内调用过于集中的问题。 例如 ABC 三节点有如下权重:{A: 3, B: 2, C: 1} 那么按照最原始的轮询算法,调用过程将变成:A A A B B C

对此,Dubbo 借鉴 Nginx 的平滑加权轮询算法,对此做了优化,调用过程可抽象成下表:

轮前加和权重本轮胜者合计权重轮后权重(胜者减去合计权重)
起始轮\\A(0), B(0), C(0)
A(3), B(2), C(1)A6A(-3), B(2), C(1)
A(0), B(4), C(2)B6A(0), B(-2), C(2)
A(3), B(0), C(3)A6A(-3), B(0), C(3)
A(0), B(2), C(4)C6A(0), B(2), C(-2)
A(3), B(4), C(-1)B6A(3), B(-2), C(-1)
A(6), B(0), C(0)A6A(0), B(0), C(0)

我们发现经过合计权重(3+2+1)轮次后,循环又回到了起点,整个过程中节点流量是平滑的,且哪怕在很短的时间周期内,概率都是按期望分布的。

如果用户有加权轮询的需求,可放心使用该算法。

LeastActive
  • 加权最少活跃调用优先,活跃数越低,越优先调用,相同活跃数的进行加权随机。活跃数指调用前后计数差(针对特定提供者:请求发送数 - 响应返回数),表示特定提供者的任务堆积量,活跃数越低,代表该提供者处理能力越强。
  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大;相对的,处理能力越强的节点,处理更多的请求。
ShortestResponse
  • 加权最短响应优先,在最近一个滑动窗口中,响应时间越短,越优先调用。相同响应时间的进行加权随机。
  • 使得响应时间越快的提供者,处理更多的请求。
  • 缺点:可能会造成流量过于集中于高性能节点的问题。

这里的响应时间 = 某个提供者在窗口时间内的平均响应时间,窗口时间默认是 30s。

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

Power of Two Choice 算法简单但是经典,主要思路如下:

  1. 对于每次调用,从可用的provider列表中做两次随机选择,选出两个节点providerA和providerB。
  2. 比较providerA和providerB两个节点,选择其“当前正在处理的连接数”较小的那个节点。
Adaptive Load Balance

Adaptive 即自适应负载均衡,是一种能根据后端实例负载自动调整流量分布的算法实现,它总是尝试将请求转发到负载最小的节点。

使用场景

  • 高可用性:部署服务的多个实例以确保即使一个或多个实例失败服务保持可用,负载均衡功能可用于在这些实例之间分配传入的请求确保以负载均衡方式使用每个实例的方式,还能最大限度地降低服务停机的风险。
  • 流量管理:限制指向特定服务实例的流量,以防止过载或确保公平的资源分配,负载均衡特性提供了 Round Robin、Weighted Round Robin、Random、Least Active Load Balancing 等多种负载均衡策略,可以用来实现流量控制。
  • 服务划分:将一个服务划分成多个逻辑组件,每个逻辑组件可以部署在不同的实例上,使用负载平衡以确保每个分区平衡的方式在这些实例之间分配请求,同时在实例发生故障的情况下提供故障转移功能。
  • 性能优化:负载平衡可用于优化服务的性能,通过跨多个实例分发请求可以利用可用的计算资源来缩短响应时间并减少延迟。

使用方式

只需要调整 loadbalance 相应取值即可。

服务端服务级别

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

自定义负载均衡

可使用SPI实现负载均衡扩展。

配置

<dubbo:protocol loadbalance="xxx" />
<!-- 缺省值设置,当<dubbo:protocol>没有配置loadbalance时,使用此配置 -->
<dubbo:provider loadbalance="xxx" />

自定义负载均衡器 XxxLoadBalance.java:

package com.xxx;
 
import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.RpcException; 
 
public class XxxLoadBalance implements LoadBalance {
    public <T> Invoker<T> select(List<Invoker<T>> invokers, Invocation invocation) throws RpcException {
        // ...
    }
}

添加文件 META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance:

xxx=com.xxx.XxxLoadBalance

异步调用

概述

Dubbo不只提供了堵塞式的的同步调用,同时提供了异步调用的方式。这种方式主要应用于提供者接口响应耗时明显,消费者端可以利用调用接口的时间去做一些其他的接口调用,利用 Future 模式来异步等待和获取结果即可。这种方式可以大大的提升消费者端的利用率。

从 2.7.0 开始,Dubbo 的所有异步编程接口开始以 CompletableFuture为基础

基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。

在这里插入图片描述

Provider 端异步执行和 Consumer 端异步调用是相互独立的,任意正交组合两端配置

  • Consumer同步 - Provider同步
  • Consumer异步 - Provider同步
  • Consumer同步 - Provider异步
  • Consumer异步 - Provider异步

异步调用实现

需要服务提供者事先定义 CompletableFuture 签名的服务,接口定义指南如下:

Provider端异步执行将阻塞的业务从Dubbo内部线程池切换到业务自定义线程,避免Dubbo线程池的过度占用,有助于避免不同服务间的互相影响。异步执行无异于节省资源或提升RPC响应性能,因为如果业务执行需要阻塞,则始终还是要有线程来负责执行。

服务接口定义

public interface AsyncService {
    CompletableFuture<String> sayHello(String name);
}

服务实现

public class AsyncServiceImpl implements AsyncService {
    @Override
    public CompletableFuture<String> sayHello(String name) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println(name);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "async response from provider.";
        });
    }
}

通过 return CompletableFuture.supplyAsync() ,业务执行已从 Dubbo 线程切换到业务线程,避免了对 Dubbo 线程池的阻塞。

注意接口的返回类型是 CompletableFuture<String>

XML 引用服务

<dubbo:reference id="asyncService" timeout="10000" interface="com.alibaba.dubbo.samples.async.api.AsyncService"/>

调用远程服务

// 调用直接返回CompletableFuture
CompletableFuture<String> future = asyncService.sayHello("async call request");
// 增加回调
future.whenComplete((v, t) -> {
    if (t != null) {
        t.printStackTrace();
    } else {
        System.out.println("Response: " + v);
    }
});
// 早于结果输出
System.out.println("Executed before response return.");

线程池

线程池隔离

使用线程池隔离来确保 Dubbo 用于调用远程方法的线程与微服务用于执行其任务的线程是分开的。可以通过防止线程阻塞或相互竞争来帮助提高系统的性能和稳定性。

目前可以以 API、XML、Annotation 的方式进行配置

配置参数

  • ApplicationConfig新增String executor-management-mode参数,配置值为defaultisolation,默认为isolation。
    • executor-management-mode = default 使用原有 以协议端口为粒度、服务间共享 的线程池管理方式
    • executor-management-mode = isolation 使用新增的 以服务三元组为粒度、服务间隔离 的线程池管理方式
  • ServiceConfig 新增 Executor executor 参数,用以服务间隔离的线程池,可以由用户配置化、提供自己想要的线程池,若没有指定,则会根据协议配置(ProtocolConfig)信息构建默认的线程池用以服务隔离。

ServiceConfig 新增 Executor executor 配置参数只有指定executor-management-mode = isolation 才生效。

API方式
    public void test() {
        // provider app
        DubboBootstrap providerBootstrap = DubboBootstrap.newInstance();

        ServiceConfig serviceConfig1 = new ServiceConfig();
        serviceConfig1.setInterface(DemoService.class);
        serviceConfig1.setRef(new DemoServiceImpl());
        serviceConfig1.setVersion(version1);
        // set executor1 for serviceConfig1, max threads is 10
        NamedThreadFactory threadFactory1 = new NamedThreadFactory("DemoService-executor");
        ExecutorService executor1 = Executors.newFixedThreadPool(10, threadFactory1);
        serviceConfig1.setExecutor(executor1);

        ServiceConfig serviceConfig2 = new ServiceConfig();
        serviceConfig2.setInterface(HelloService.class);
        serviceConfig2.setRef(new HelloServiceImpl());
        serviceConfig2.setVersion(version2);
        // set executor2 for serviceConfig2, max threads is 100
        NamedThreadFactory threadFactory2 = new NamedThreadFactory("HelloService-executor");
        ExecutorService executor2 = Executors.newFixedThreadPool(100, threadFactory2);
        serviceConfig2.setExecutor(executor2);

        ServiceConfig serviceConfig3 = new ServiceConfig();
        serviceConfig3.setInterface(HelloService.class);
        serviceConfig3.setRef(new HelloServiceImpl());
        serviceConfig3.setVersion(version3);
        // Because executor is not set for serviceConfig3, the default executor of serviceConfig3 is built using
        // the threadpool parameter of the protocolConfig ( FixedThreadpool , max threads is 200)
        serviceConfig3.setExecutor(null);

        // It takes effect only if [executor-management-mode=isolation] is configured
        ApplicationConfig applicationConfig = new ApplicationConfig("provider-app");
        applicationConfig.setExecutorManagementMode("isolation");

        providerBootstrap
        .application(applicationConfig)
        .registry(registryConfig)
        // export with tri and dubbo protocol
        .protocol(new ProtocolConfig("tri", 20001))
        .protocol(new ProtocolConfig("dubbo", 20002))
        .service(serviceConfig1)
        .service(serviceConfig2)
        .service(serviceConfig3);

        providerBootstrap.start();
    }
XML方式
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

  <!-- NOTE: we need config executor-management-mode="isolation" -->
  <dubbo:application name="demo-provider" executor-management-mode="isolation">
  </dubbo:application>

  <dubbo:config-center address="zookeeper://127.0.0.1:2181"/>
  <dubbo:metadata-report address="zookeeper://127.0.0.1:2181"/>
  <dubbo:registry id="registry1" address="zookeeper://127.0.0.1:2181?registry-type=service"/>

  <dubbo:protocol name="dubbo" port="-1"/>
  <dubbo:protocol name="tri" port="-1"/>

  <!-- expose three service with dubbo and tri protocol-->
  <bean id="demoServiceV1" class="org.apache.dubbo.config.spring.impl.DemoServiceImpl"/>
  <bean id="helloServiceV2" class="org.apache.dubbo.config.spring.impl.HelloServiceImpl"/>
  <bean id="helloServiceV3" class="org.apache.dubbo.config.spring.impl.HelloServiceImpl"/>

  <!-- customized thread pool -->
  <bean id="executor-demo-service"
        class="org.apache.dubbo.config.spring.isolation.spring.support.DemoServiceExecutor"/>
  <bean id="executor-hello-service"
        class="org.apache.dubbo.config.spring.isolation.spring.support.HelloServiceExecutor"/>

  <!-- this service use [executor="executor-demo-service"] as isolated thread pool-->
  <dubbo:service executor="executor-demo-service"
                 interface="org.apache.dubbo.config.spring.api.DemoService" version="1.0.0" group="Group1"
                 timeout="3000" ref="demoServiceV1" registry="registry1" protocol="dubbo,tri"/>

  <!-- this service use [executor="executor-hello-service"] as isolated thread pool-->
  <dubbo:service executor="executor-hello-service"
                 interface="org.apache.dubbo.config.spring.api.HelloService" version="2.0.0" group="Group2"
                 timeout="5000" ref="helloServiceV2" registry="registry1" protocol="dubbo,tri"/>

  <!-- not set executor for this service, the default executor built using threadpool parameter of the protocolConfig -->
  <dubbo:service interface="org.apache.dubbo.config.spring.api.HelloService" version="3.0.0" group="Group3"
                 timeout="5000" ref="helloServiceV3" registry="registry1" protocol="dubbo,tri"/>

</beans>
Annotation方式
@Configuration
@EnableDubbo(scanBasePackages = "org.apache.dubbo.config.spring.isolation.spring.annotation.provider")
public class ProviderConfiguration {
    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("zookeeper://127.0.0.1:2181");
        return registryConfig;
    }

    // NOTE: we need config executor-management-mode="isolation"
    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig("provider-app");

        applicationConfig.setExecutorManagementMode("isolation");
        return applicationConfig;
    }

    // expose services with dubbo protocol
    @Bean
    public ProtocolConfig dubbo() {
        ProtocolConfig protocolConfig = new ProtocolConfig("dubbo");
        return protocolConfig;
    }

    // expose services with tri protocol
    @Bean
    public ProtocolConfig tri() {
        ProtocolConfig protocolConfig = new ProtocolConfig("tri");
        return protocolConfig;
    }

    // customized thread pool
    @Bean("executor-demo-service")
    public Executor demoServiceExecutor() {
        return new DemoServiceExecutor();
    }

    // customized thread pool
    @Bean("executor-hello-service")
    public Executor helloServiceExecutor() {
        return new HelloServiceExecutor();
    }
}
// customized thread pool
public class DemoServiceExecutor extends ThreadPoolExecutor {
    public DemoServiceExecutor() {
        super(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
            new NamedThreadFactory("DemoServiceExecutor"));
    }
}
// customized thread pool
public class HelloServiceExecutor extends ThreadPoolExecutor {
    public HelloServiceExecutor() {
        super(100, 100, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
            new NamedThreadFactory("HelloServiceExecutor"));
    }
}
// "executor-hello-service" is beanName
@DubboService(executor = "executor-demo-service", version = "1.0.0", group = "Group1")
public class DemoServiceImplV1 implements DemoService {

  @Override
  public String sayName(String name) {
    return "server name";
  }

  @Override
  public Box getBox() {
    return null;
  }
}
// not set executor for this service, the default executor built using threadpool parameter of the protocolConfig
@DubboService(version = "3.0.0", group = "Group3")
public class HelloServiceImplV2 implements HelloService {
    private static final Logger logger = LoggerFactory.getLogger(HelloServiceImplV2.class);

    @Override
    public String sayHello(String name) {
        return "server hello";
    }
}
@DubboService(executor = "executor-hello-service", version = "2.0.0", group = "Group2")
public class HelloServiceImplV3 implements HelloService {
    private static final Logger logger = LoggerFactory.getLogger(HelloServiceImplV3.class);

    @Override
    public String sayHello(String name) {
        return "server hello";
    }
}

自定义线程池

可使用SPI扩展自定义线程池。

服务提供方线程池实现策略,当服务器收到一个请求时,需要在线程池中创建一个线程去执行服务提供方业务逻辑。

扩展接口:

org.apache.dubbo.common.threadpool.ThreadPool

配置:

<dubbo:protocol threadpool="xxx" />
<!-- 缺省值设置,当<dubbo:protocol>没有配置threadpool时,使用此配置 -->
<dubbo:provider threadpool="xxx" />

已知扩展:

  • org.apache.dubbo.common.threadpool.FixedThreadPool
  • org.apache.dubbo.common.threadpool.CachedThreadPool

示例:

Maven 项目结构:

src
 |-main
    |-java
        |-com
            |-xxx
                |-XxxThreadPool.java (实现ThreadPool接口)
    |-resources
        |-META-INF
            |-dubbo
                |-org.apache.dubbo.common.threadpool.ThreadPool (纯文本文件,内容为:xxx=com.xxx.XxxThreadPool)

XxxThreadPool.java:

package com.xxx;
 
import org.apache.dubbo.common.threadpool.ThreadPool;
import java.util.concurrent.Executor;
 
public class XxxThreadPool implements ThreadPool {
    public Executor getExecutor() {
        // ...
    }
}

META-INF/dubbo/org.apache.dubbo.common.threadpool.ThreadPool:

xxx=com.xxx.XxxThreadPool
文章来源:https://blog.csdn.net/qq_43745578/article/details/135363645
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。