Spring Cloud 系列之 Dubbo RPC 通信

Dubbo 介绍

官网:http://dubbo.apache.org/zh-cn/

Github:https://github.com/apache/dubbo

2018 年 2 月 15 日,阿里巴巴的服务治理框架 dubbo 通过投票,顺利成为 Apache 基金会孵化项目。

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

Dubbo 架构

Dubbo 提供三个核心功能:基于接口的远程调用、容错和负载均衡,以及服务的自动注册与发现。Dubbo 框架广泛的在阿里巴巴内部使用,以及当当、去哪儿、网易考拉、滴滴等都在使用。

节点角色说明

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

调用关系说明

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

Dubbo 快速入门

我们先通过一个简单的案例让大家理解一下 Dubbo 的使用,然后基于 Spring Boot 和 Spring Cloud 环境整合 Dubbo。

Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需用 Spring 加载 Dubbo 的配置即可。

依赖

JDK 1.6 以上和 Maven 3.0 以上,采用 Maven 多模块聚合工程构建 api 模块,provider 模块以及 consumer 模块。

聚合工程

项目结构如下图,简单介绍一下:

  • dubbo-api:服务接口
  • dubbo-provider:依赖服务接口,具体的业务实现,服务提供者
  • dubbo-coonsumer:依赖服务接口,远程调用服务,服务消费者

依赖关系

dubbo-parent 的 pom.xml 依赖 apache dubbo。

<dependencies>
    <!-- apache dubbo 依赖 -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo</artifactId>
        <version>2.7.4.1</version>
    </dependency>
</dependencies>

dubbo-provider 和 dubbo-consumer 的 pom.xml 依赖 dubbo-api 服务接口。

<dependencies>
    <!-- dubbo-api 依赖 -->
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>dubbo-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

定义服务接口

dubbo-api 中编写 HelloService.java

package org.example.service;

/**
 * Hello服务
 */
public interface HelloService {

    String sayHello(String name);

}

定义服务提供者

在 provider 模块中实现服务接口

dubbo-provider 中编写 HelloServiceImpl.java

package org.example.service.impl;

import org.example.service.HelloService;

/**
 * 服务实现
 */
public class HelloServiceImpl implements HelloService {

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

}

配置服务提供者

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

    <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="hello-world-app"/>

    <!-- 使用 multicast 广播注册中心暴露服务地址 -->
    <dubbo:registry address="multicast://224.5.6.7:1234"/>

    <!-- 用 dubbo 协议在 20880 端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="org.example.service.HelloService" ref="helloService"/>

    <!-- 和本地 bean 一样实现服务 -->
    <bean id="helloService" class="org.example.service.impl.HelloServiceImpl"/>

</beans>

加载 Spring 配置启动服务

package org.example;

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 发布服务
 */
public class Provider {

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:dubbo-provider.xml");
        context.start();
        System.out.println("服务注册成功!");
        System.in.read(); // 按任意键退出
    }

}

定义服务消费者

通过 Spring 配置引用远程服务

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

    <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
    <dubbo:application name="consumer-of-helloworld-app"  />

    <!-- 使用 multicast 广播注册中心暴露发现服务地址 -->
    <dubbo:registry address="multicast://224.5.6.7:1234" />

    <!-- 生成远程服务代理,可以和本地 bean 一样使用 helloService -->
    <dubbo:reference id="helloService" interface="org.example.service.HelloService" />

</beans>

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

package org.example;

import org.example.service.HelloService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 调用远程服务
 */
public class Consumer {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:dubbo-consumer.xml");
        context.start();
        HelloService helloService = (HelloService) context.getBean("helloService"); // 获取远程服务代理
        String result = helloService.sayHello("world"); // 执行远程方法
        System.out.println(result); // 显示调用结果
    }
}

Dubbo 常用标签

  • dubbo:application:应用程序名称
  • dubbo:registry:连接注册中心信息(配置注册中心)
  • dubbo:protocol:服务提供者注册服务采用的协议
  • dubbo:service:声明需要暴露的服务接口
  • dubbo:reference:配置订阅的服务(生成远程服务代理)

更多配置信息请参考:http://dubbo.apache.org/zh-cn/docs/user/references/xml/introduction.html

注册中心

注册中心我们已经学习了不少,例如:ZooKeeper、Eureka、Consul、Nacos 等等,注册中心可以更高效的管理系统的服务:比如服务接口的发布、自动剔除无效的服务、自动恢复服务等。

Dubbo 支持五种注册中心:Multicast、Nacos(推荐)、ZooKeeper(推荐) 、Redis、Simple。本文重点介绍前两个,更多注册中心的信息请参考:http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html

Multicast 注册中心

Multicast 注册中心不需要启动任何中心节点,只要广播地址一样,就可以互相发现。

  1. 提供方启动时广播自己的地址
  2. 消费方启动时广播订阅请求
  3. 提供方收到订阅请求时,单播自己的地址给订阅者,如果设置了 unicast=false,则广播给订阅者
  4. 消费方收到提供方地址时,连接该地址进行 RPC 调用。

组播受网络结构限制,只适合小规模应用或开发阶段使用。组播地址段: 224.0.0.0 - 239.255.255.255

配置

<dubbo:registry address="multicast://224.5.6.7:1234" />

<dubbo:registry protocol="multicast" address="224.5.6.7:1234" />

为了减少广播量,Dubbo 缺省使用单播发送提供者地址信息给消费者,如果一个机器上同时启了多个消费者进程,消费者需声明 unicast=false,否则只会有一个消费者能收到消息。

当服务者和消费者运行在同一台机器上,消费者同样需要声明unicast=false,否则消费者无法收到消息,导致 No provider available for the service 异常。

<dubbo:registry address="multicast://224.5.6.7:1234?unicast=false" />

<dubbo:registry protocol="multicast" address="224.5.6.7:1234">
    <dubbo:parameter key="unicast" value="false" />
</dubbo:registry>

ZooKeeper 注册中心

Apache ZooKeeper 是一个开放源码的分布式应用程序协调组件,是 Hadoop 和 Hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,推荐使用。

在微服务项目开发中 ZooKeeper 主要的角色是当做服务注册中心存在,我们将编写好的服务注册至 ZooKeeper 即可。

流程说明:

  • 服务提供者启动时: 向 /dubbo/com.foo.BarService/providers 目录下写入自己的 URL 地址。
  • 服务消费者启动时: 订阅 /dubbo/com.foo.BarService/providers 目录下的提供者 URL 地址。并向 /dubbo/com.foo.BarService/consumers 目录下写入自己的 URL 地址。
  • 监控中心启动时: 订阅 /dubbo/com.foo.BarService 目录下的所有提供者和消费者 URL 地址。

支持以下功能:

  • 当提供者出现断电等异常停机时,注册中心能自动删除提供者信息;
  • 当注册中心重启时,能自动恢复注册数据,以及订阅请求;
  • 当会话过期时,能自动恢复注册数据,以及订阅请求;
  • 当设置 <dubbo:registry check="false" /> 时,记录失败注册和订阅请求,后台定时重试;
  • 可通过 <dubbo:registry username="admin" password="1234" /> 设置 zookeeper 登录信息;
  • 可通过 <dubbo:registry group="dubbo" /> 设置 zookeeper 的根节点,不配置将使用默认的根节点;
  • 支持 * 号通配符 <dubbo:reference group="*" version="*" />,可订阅服务的所有分组和所有版本的提供者。

作为 Dubbo 的老牌黄金搭档 ZooKeeper,我们在单独讲解 Dubbo 时已经给大家分享过如何使用了,本文系 Spring Cloud Alibaba 系列文章,重点对象是 Nacos,所以 ZooKeeper 这里就不过多赘述了。

Nacos 注册中心

Nacos 是 Alibaba 公司推出的开源工具,用于实现分布式系统的服务发现与配置管理。Nacos 是 Dubbo 生态系统中重要的注册中心实现,其中 dubbo-registry-nacos 则是 Dubbo 融合 Nacos 注册中心的实现,推荐使用。

Nacos 官网:https://nacos.io/zh-cn/

Github:https://github.com/alibaba/nacos

预备工作

当您将 Nacos 整合到您的 Dubbo 工程之前,请确保后台已经启动 Nacos 服务。关于 Nacos 的安装和其他详细内容可参考我之前的文章 Spring Cloud 系列之 Alibaba Nacos 注册中心(一)Spring Cloud 系列之 Alibaba Nacos 注册中心(二)

快速上手

Dubbo 融合 Nacos 成为注册中心的操作步骤非常简单,大致步骤可分为“增加 Maven 依赖”和“配置注册中心“。

依赖

核心依赖主要是 dubbo-registry-nacosnacos-client

<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-registry-nacos -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-registry-nacos</artifactId>
    <version>2.7.4.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.nacos/nacos-client -->
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.3.0</version>
</dependency>

配置注册中心

服务提供者和服务消费者只需要调整 address 属性配置即可。

单机配置:

<!-- 使用 Nacos 注册中心,单机版 -->
<dubbo:registry address="nacos://127.0.0.1:8848"/>
<!-- 或 -->
<dubbo:registry protocol="nacos" address="127.0.0.1:2181"/>

或:

dubbo.register.address=nacos://192.168.10.101:8848

或:

dubbo.register.protocol=nacos
dubbo.register.address=192.168.10.101:8848

集群配置:

<!-- 使用 Nacos 注册中心,集群版 -->
<dubbo:registry address="nacos://192.168.10.101:2181?backup=192.168.10.102:2181,192.168.10.103:2181"/>
<!-- 或 -->
<dubbo:registry protocol="nacos" address="192.168.10.101:2181,192.168.10.102:2181,192.168.10.103:2181"/>

或:

dubbo.register.address=nacos://192.168.10.101:8848

或:

dubbo.register.protocol=nacos
dubbo.register.address=192.168.10.101:8848,192.168.10.102:8848,192.168.10.103:8848

随后,重启您的 Dubbo 应用,Dubbo 的服务提供和消费信息在 Nacos 控制台中即可显示。

Spring Cloud Alibaba Nacos 整合 Dubbo

之前的文章中,无论我们学习 Eureka、Consul 还是 Nacos,负责服务间通信的功能都是由 Ribbon 来完成的,接下来我们使用 Dubbo 来替换 Ribbon。

聚合工程

dubbo-demo 聚合工程。SpringBoot 2.3.0.RELEASESpring Cloud Hoxton.SR5

项目结构如下图,简单介绍一下:

  • service-api:服务接口
  • product-service:商品服务,服务提供者,提供了 /product/list 接口
  • order-service:订单服务,服务消费者,远程调用商品服务

依赖关系

dubbo-demo 的 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.example</groupId>
    <!-- 项目模块名称 -->
    <artifactId>dubbo-demo</artifactId>
    <packaging>pom</packaging>
    <!-- 项目版本名称 快照版本SNAPSHOT、正式版本RELEASE -->
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>service-api</module>
        <module>product-service</module>
        <module>order-service</module>
    </modules>

    <!-- 继承 spring-boot-starter-parent 依赖 -->
    <!-- 使用继承方式,实现复用,符合继承的都可以被使用 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
    </parent>

    <!--
        集中定义依赖组件版本号,但不引入,
        在子工程中用到声明的依赖时,可以不加依赖的版本号,
        这样可以统一管理工程中用到的依赖版本
     -->
    <properties>
        <!-- Spring Cloud Hoxton.SR5 依赖 -->
        <spring-cloud.version>Hoxton.SR5</spring-cloud.version>
        <!-- spring cloud alibaba 依赖 -->
        <spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
    </properties>

    <!-- 项目依赖管理 父项目只是声明依赖,子项目需要写明需要的依赖(可以省略版本信息) -->
    <dependencyManagement>
        <dependencies>
            <!-- spring cloud 依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- spring cloud alibaba 依赖 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

service-api 的 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">
    <parent>
        <artifactId>dubbo-demo</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>service-api</artifactId>

    <dependencies>
        <!-- lombok 依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>

product-service 需要依赖 Nacos 和 Dubbo 的依赖,还有 service-api 的依赖,完整依赖如下:

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

    <!-- 继承父依赖 -->
    <parent>
        <artifactId>dubbo-demo</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>product-service</artifactId>

    <!-- 项目依赖 -->
    <dependencies>
        <!-- spring cloud alibaba nacos discovery 依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- spring cloud alibaba dubbo 依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dubbo</artifactId>
        </dependency>
        <!-- spring boot web 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- service-api 依赖 -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>service-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- spring boot test 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

</project>

order-service 需要依赖 Nacos 和 Dubbo 的依赖,还有 service-api 的依赖,完整依赖如下:

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

    <!-- 继承父依赖 -->
    <parent>
        <artifactId>dubbo-demo</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>order-service</artifactId>

    <!-- 项目依赖 -->
    <dependencies>
        <!-- spring cloud alibaba nacos discovery 依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- spring cloud alibaba dubbo 依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dubbo</artifactId>
        </dependency>
        <!-- spring boot web 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- service-api 依赖 -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>service-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- spring boot test 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

</project>

定义服务接口

我们在 service-api 模块中定义实体类和服务接口信息。

实体类

Product.java

package com.example.product.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {

    private Integer id;
    private String productName;
    private Integer productNum;
    private Double productPrice;

}

Order.java

package com.example.product.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {

    private Integer id;
    private String orderNo;
    private String orderAddress;
    private Double totalPrice;
    private List<Product> productList;

}

服务接口

package com.example.product.service;

import com.example.product.pojo.Product;

import java.util.List;

/**
 * 商品服务
 */
public interface ProductService {

    /**
     * 查询商品列表
     *
     * @return
     */
    List<Product> selectProductList();

}

定义服务提供者

配置文件

配置文件需要配置 Nacos 注册中心和 Dubbo 相关信息,核心配置如下:

server:
  port: 7070 # 端口

spring:
  application:
    name: product-service # 应用名称
  # 配置 Nacos 注册中心
  cloud:
    nacos:
      discovery:
        enabled: true # 如果不想使用 Nacos 进行服务注册和发现,设置为 false 即可
        server-addr: 127.0.0.1:8848 # Nacos 服务器地址,单机版

# Dubbo
dubbo:
  # 提供方应用信息,用于计算依赖关系
  application:
    name: product-service
  # 使用 nacos 注册中心暴露服务地址
  registry:
    protocol: nacos
    address: spring-cloud://localhost
  # 用 dubbo 协议在 20880 端口暴露服务
  protocol:
    name: dubbo
    port: 20880
  # 扫描需要暴露的服务,可以被 @EnableDubbo 注解替代
  #scan:
  #  base-packages: com.example.service

服务提供者

product-service 的 ProductServiceImpl.java

package com.example.service.impl;

import com.example.product.pojo.Product;
import com.example.product.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Service;

import java.util.Arrays;
import java.util.List;

/**
 * 商品服务
 *      timeout 调用该服务的超时时间
 *      version 为版本号
 *      group 为分组
 * interface、group、version 三者确定一个服务
 */
@Slf4j
@Service(timeout = 5000, version = "1.0", group = "product-service")
public class ProductServiceImpl implements ProductService {

    /**
     * 查询商品列表
     *
     * @return
     */
    @Override
    public List<Product> selectProductList() {
        log.info("商品服务查询商品信息...");
        return Arrays.asList(
                new Product(1, "华为手机", 1, 5800D),
                new Product(2, "联想笔记本", 1, 6888D),
                new Product(3, "小米平板", 5, 2020D)
        );
    }

}

值得注意的是 @Service 注解不是 Spring 的注解而是 Dubbo 的注释:

启动类

启动类通过 @EnableDubbo 注解扫描需要暴露的服务,如果配置文件中配置了该选项,那么这里可以省略。

package com.example;

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

// 扫描需要暴露的服务
@EnableDubbo(scanBasePackages = "com.example.service")
// 开启 @EnableDiscoveryClient 注解,当前版本默认会开启该注解
//@EnableDiscoveryClient
@SpringBootApplication
public class ProductServiceApplication {

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

}

定义服务消费者

配置文件

配置文件需要配置 Nacos 注册中心和 Dubbo 相关信息,核心配置如下:

server:
  port: 9090 # 端口

spring:
  application:
    name: order-service # 应用名称
  # 配置 Nacos 注册中心
  cloud:
    nacos:
      discovery:
        enabled: true # 如果不想使用 Nacos 进行服务注册和发现,设置为 false 即可
        server-addr: 127.0.0.1:8848 # Nacos 服务器地址,单机版

# Dubbo
dubbo:
  # 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样
  application:
    name: order-service
  # 发现 nacos 注册中心暴露的服务
  registry:
    protocol: nacos
    address: spring-cloud://localhost
  cloud:
    subscribed-services: product-service # 订阅服务,远程调用的服务名称

服务消费者

order-service 的 OrderServiceImpl.java

package com.example.service.impl;

import com.example.product.pojo.Order;
import com.example.product.service.ProductService;
import com.example.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    // dubbo 提供了 @Reference 注解,可替换 @Autowired 注解,用于引入远程服务
    // 如果注册服务时设置了版本及分组信息,调用远程服务时也要设置对应的版本及分组信息
    @Reference(timeout = 5000, version = "1.0", group = "product-service")
    private ProductService productService;

    /**
     * 根据主键查询订单
     *
     * @param id
     * @return
     */
    @Override
    public Order selectOrderById(Integer id) {
        log.info("订单服务查询订单信息...");
        return new Order(id, "order-001", "中国", 22788D,
                productService.selectProductList());
    }

}

启动类

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 开启 @EnableDiscoveryClient 注解,当前版本默认会开启该注解
//@EnableDiscoveryClient
@SpringBootApplication
public class OrderServiceApplication {

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

}

测试

先启动 Nacos 服务器,然后启动服务提供者 product-service,访问:http://localhost:8848/nacos/ 控制台显示如下:

然后启动服务消费者,控制台显示如下:

订单服务调用远程商品服务,结果如下:

Dubbo 负载均衡

在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用,也可以自行扩展负载均衡策略。

负载均衡策略

Random LoadBalance

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

RoundRobin LoadBalance

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

LeastActive LoadBalance

  • 最少活跃调用数,ping 值(延迟低)的调用,相同延迟的情况下随机。
  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

ConsistentHash LoadBalance

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

配置

一般在项目中不会在代码层面指定权重,而是通过监控中心(dubbo-admin)对服务动态的指定权重,官方文档:http://dubbo.apache.org/zh-cn/docs/admin/introduction.html

xml

服务端服务级别

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

客户端服务级别

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

服务端方法级别

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

客户端方法级别

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

yaml

dubbo:
  provider:
    loadbalance: roundrobin
    weight: 100
  consumer:
    loadbalance: roundrobin

注解

@Service(loadbalance = "roundrobin", weight = 100)
@Reference(loadbalance = "roundrobin")

至此 Dubbo RPC 通信所有的知识点就讲解结束了。

本文采用 <a href="http://creativecommons.org/licenses/by-nc-nd/4.0/" target="_blank">知识共享「署名-非商业性使用-禁止演绎 4.0 国际」许可协议</a>。

大家可以通过 <a href="https://mrhelloworld.com/categories" target="_blank">分类</a> 查看更多关于 <a href="https://mrhelloworld.com/categories/spring-cloud" target="_blank">Spring Cloud</a> 的文章。


🤗 您的点赞转发是对我最大的支持。

📢 关注公众号 哈喽沃德先生「文档 + 视频」每篇文章都配有专门视频讲解,学习更轻松噢 ~


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345