艿艿周末又肝了一篇万字 Dubbo 文章,Spring Boot + Nacos + Sentinel + 参数校验,美滋滋~...

springcloud+dubbo+sentinel+nacos搭建(sentinel规则管理及推送、持久化) 前面两篇讲了不用springcloud做粘合,直接springboot+dubbo+nacos+sentinel的搭建 这篇讲用springcloud做粘合是怎么搞的 一、springcloud+sentinel 在前面的项目的基础上加入 <!--sentinel 核心环境 依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> 阅读详情

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 8:55 更新文章,每天掉亿点点头发...

源码精品专栏

 
  • 1. 概述

  • 2. XML 配置

  • 3. 注解配置

  • 4. 参数验证

  • 5. 自定义实现拓展点

  • 6. 整合 Nacos

  • 6. 整合 Sentinel

  • 666. 彩蛋


本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labs 的 lab-30 目录。

原创不易,给点个 Star 嘿,一起冲鸭!

1. 概述

在 2019.05.21 号,在经历了 1 年多的孵化,Dubbo 终于迎来了 Apache 毕业。在这期间,Dubbo 做了比较多的功能迭代,提供了 NodeJS、Python、Go 等语言的支持,也举办了多次社区活动,在网上的“骂声”也少了。

艿艿:事实上,大多数成熟的开源项目,都是 KPI 驱动,又或者背后有商业化支撑。

作为一个长期使用,并且坚持使用 Dubbo 的开发者,还是比较愉快的。可能,又经历了一次技术正确的选择。当然,更愉快的是,Spring Cloud Alibaba 貌似,也已经完成孵化,双剑合并,biubiubiu 。

可能胖友有些胖友对 Dubbo 不是很了解,这里艿艿先简单介绍下:

FROM Dubbo 官网

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

Dubbo 整体架构

图中,一共涉及到 5 个角色:

  • Registry 注册中心,用于服务的注册与发现。

  • Provider 服务提供者,通过向 Registry 注册服务。

  • Consumer 服务消费者,通过从 Registry 发现服务。后续直接调用 Provider ,无需经过 Registry 。

  • Monitor 监控中心,统计服务的调用次数和调用时间。

  • Container 服务运行容器。

FROM 《Dubbo 文档 —— 架构》

调用关系说明(注意,和上图的数字,和下面的步骤是一一对应的):

  1. 服务容器负责启动,加载,运行服务提供者。

    1. 服务提供者在启动时,向注册中心注册自己提供的服务。

    1. 服务消费者在启动时,向注册中心订阅自己所需的服务。

    1. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

    1. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

    1. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

本文的重心,在于一起入门 Provider 和 Consumer 的代码编写,这也是实际项目开发中,我们涉及到的角色。

Dubbo 提供了比较多的配置方式,日常开发中主要使用的是 XML 配置 和 注解配置 。我们分别会在 「2. XML 配合」 和 「3. 注解配置」 小节来入门。

考虑到现在 Dubbo 已经提供了 dubbo-spring-boot-project 项目,集成到 Spring Boot 体系中,而大家都基本采用 Spring Boot 框架,所以我们就不像 Dubbo 官方文档 一样,提供的是 Spring 环境下的示例,而是 Spring Boot 环境下。

2. XML 配置

示例代码对应仓库:lab-30-dubbo-xml-demo 。

本小节的示例,需要创建三个 Maven 项目,如下图所示:

  • user-rpc-service-api 项目:服务接口,定义 Dubbo Service API 接口,提供给消费者使用。详细代码,我们在 「2.1 API」 讲解。

  • user-rpc-service-provider 项目:服务提供者,实现 user-rpc-service-api 项目定义的 Dubbo Service API 接口,提供相应的服务。详细代码,我们在 「2.2 Provider」 中讲解。

  • user-rpc-service-consumer 项目:服务消费者,会调用 user-rpc-service-provider 项目提供的 Dubbo Service 服务。详细代码,我们在 「2.3 Consumer」 中讲解。

2.1 API

对应 user-rpc-service-api 项目,服务接口,定义 Dubbo Service API 接口,提供给消费者使用。

2.1.1 UserDTO

在 cn.iocoder.springboot.lab30.rpc.dto 包下,创建用于 Dubbo Service 传输类。这里,我们创建 UserDTO 类,用户信息 DTO 。代码如下:

// UserDTO.java

public class UserDTO implements Serializable {

    /**
     * 用户编号
     */
    private Integer id;
    /**
     * 昵称
     */
    private String name;
    /**
     * 性别
     */
    private Integer gender;
    
    // ... 省略 set/get 方法
}

注意,要实现 java.io.Serializable 接口。因为,Dubbo RPC 会涉及远程通信,需要序列化和反序列化。

2.1.2 UserRpcService

在 cn.iocoder.springboot.lab30.rpc.api 包下,创建 Dubbo Service API 接口。这里,我们创建 UserRpcService 接口,用户服务 RPC Service 接口。代码如下:

// UserRpcService.java

public interface UserRpcService {

    /**
     * 根据指定用户编号,获得用户信息
     *
     * @param id 用户编号
     * @return 用户信息
     */
    UserDTO get(Integer id);

}

2.2 Provider

对应 user-rpc-service-provider 项目,服务提供者,实现 user-rpc-service-api 项目定义的 Dubbo Service API 接口,提供相应的服务。

2.2.1 引入依赖

在 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>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-rpc-service</artifactId>

    <dependencies>
        <!-- 引入定义的 Dubbo API 接口 -->
        <dependency>
            <groupId>cn.iocoder.springboot.labs</groupId>
            <artifactId>user-rpc-service-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- 引入 Spring Boot 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- 实现对 Dubbo 的自动化配置 -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.4.1</version>
        </dependency>

        <!-- 使用 Zookeeper 作为注册中心 -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.13.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.13.0</version>
        </dependency>

    </dependencies>

</project>
  • 因为我们希望实现对 Dubbo 的自动化配置,所以引入 dubbo-spring-boot-starter 依赖。

  • 因为我们希望使用 Zookeeper 作为注册中心,所以引入 curator-framework 和 curator-recipes 依赖。可能胖友不太了解 Apache Curator 框架,这里我们看一段简介:

    FROM https://www.oschina.net/p/curator

    Zookeeper 的客户端调用过于复杂,Apache Curator 就是为了简化Zookeeper 客户端调用而生,利用它,可以更好的使用 Zookeeper。

    • 虽然说,目前阿里正在大力推广 Nacos 作为 Dubbo 的注册中心,但是大多数团队,采用的还是 Zookeeper 为主。

    • 对了,如果胖友不知道怎么安装 Zookeeper ,可以看看 《芋道 Zookeeper 极简入门》 文章。

2.2.2 应用配置文件

在 resources 目录下, 创建 application.yml 配置文件,添加 Dubbo 相关的配置,如下:

# dubbo 配置项,对应 DubboConfigurationProperties 配置类
dubbo:
  # Dubbo 应用配置
  application:
    name: user-service-provider # 应用名
  # Dubbo 注册中心配
  registry:
    address: zookeeper://127.0.0.1:2181 # 注册中心地址。个鞥多注册中心,可见 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文档。
  # Dubbo 服务提供者协议配置
  protocol:
    port: -1 # 协议端口。使用 -1 表示随机端口。
    name: dubbo # 使用 `dubbo://` 协议。更多协议,可见 http://dubbo.apache.org/zh-cn/docs/user/references/protocol/introduction.html 文档
  # Dubbo 服务提供者配置
  provider:
    timeout: 1000 # 【重要】远程服务调用超时时间,单位:毫秒。默认为 1000 毫秒,胖友可以根据自己业务修改
    UserRpcService:
      version: 1.0.0
  • dubbo-spring-boot-starter 依赖,会根据 dubbo 配置项,实现对 Dubbo 的自动化配置。下面呢,我们来逐个配置项看看。

    艿艿:本小节,我们的 「XML 配置」 ,指的是使用 XML 来配置 Dubbo Service 服务。如果胖友想看纯粹的全量 XML 配置,可以看看 《Dubbo 官方文档 —— XML 配置》 。

  • dubbo.application 配置项,Dubbo 应用信息配置。更多属性,可见 ApplicationConfig 类。每个属性的说明,可见 《Dubbo 文档 —— dubbo:application》 。

  • dubbo.registry 配置项,Dubbo 注册中心配置。更多属性,可见 RegistryConfig 类。每个属性的说明,可见 《Dubbo 文档 —— dubbo:registry》 。

  • dubbo.protocol 配置项,Dubbo 服务提供者协议配置。更多属性,可见 ProtocolConfig 类。每个属性的说明,可见 《Dubbo 文档 —— dubbo:protocol》 。

  • dubbo.provider 配置项,Dubbo 服务提供者配置。更多属性,可见 ProviderConfig 类。每个属性的说明,可见 《Dubbo 文档 —— dubbo:provider》 。

  • dubbo.provider.UserRpcService 配置项,是我们自定义的,设置每个 Service 服务的配置。更多属性,可见 ServiceConfig 类。每个属性的说明,可见 《Dubbo 文档 —— dubbo:service》 。

2.2.3 UserRpcServiceImpl

在 cn.iocoder.springboot.lab30.rpc.service 包下,创建 Dubbo Service 实现类。这里,我们创建 UserRpcServiceImpl 类,用户服务 RPC Service 实现类。代码如下:

// UserRpcServiceImpl.java

@Service
public class UserRpcServiceImpl implements UserRpcService {

    @Override
    public UserDTO get(Integer id) {
        return new UserDTO().setId(id)
                .setName("没有昵称:" + id)
                .setGender(id % 2 + 1); // 1 - 男;2 - 女
    }

}
  • 实现 UserRpcService 接口,提供 UserRpcService Dubbo 服务。

  • 注意,在类上添加了 Spring @Service 注解,暴露出 UserRpcServiceImpl Bean 对象。???? 后续,我们会将该 Bean 暴露成 UserRpcService Dubbo 服务,注册其到注册中心中,并提供相应的 Dubbo 服务。

2.2.4 Dubbo XML 配置文件

在 resources 目录下, 创建 dubbo.xml 配置文件,添加 Dubbo 的 Service 服务提供者,如下:

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

    <!-- 服务提供者暴露服务配置 -->
    <dubbo:service ref="userRpcServiceImpl" interface="cn.iocoder.springboot.lab30.rpc.api.UserRpcService"
        version="${dubbo.provider.UserRpcService.version}" />

</beans>
  • 使用 Dubbo 自定义的 Spring <dubbo:service> 标签,配置我们 「2.2.3 UserRpcServiceImpl」 成 UserRpcService 的 Dubbo 服务提供者。

更多 <dubbo:service> 标签的属性的说明,可见 《Dubbo 文档 —— dubbo:service》 。

2.2.5 ProviderApplication

创建 ProviderApplication 类,用于启动该项目,提供 Dubbo 服务。代码如下:

// ProviderApplication.java

@SpringBootApplication
@ImportResource("classpath:dubbo.xml")
public class ProviderApplication {

    public static void main(String[] args) {
        // 启动 Spring Boot 应用
        SpringApplication.run(ProviderApplication.class, args);
    }

}
  • 在类上,添加 @ImportResource 注解,引入 dubbo.xml 配置文件。

运行 #main(String[] args) 方法,启动项目。控制台打印日志如下:

// ... 省略其它日志

2019-12-01 22:40:34.721  INFO 64176 --- [pool-1-thread-1] .b.c.e.AwaitingNonWebApplicationListener :  [Dubbo] Current Spring Boot Application is await...
  • 看到该日志内容,意味着启动成功。

我们来使用 Zookeeper 客户端,查看 UserRpcService 服务是否注册成功。操作流程如下:

# 使用 Zookeeper 自带的客户端,连接到 Zookeeper 服务器
$ bin/zkCli.sh

# 查看 /dubbo 目录下的所有服务。
# 此时,我们查看到了 UserRpcService 服务
$ ls /dubbo
[cn.iocoder.springboot.lab30.rpc.api.UserRpcService]

# 查看 /dubbo/cn.iocoder.springboot.lab30.rpc.api.UserRpcService 目录下的存储情况。
# 此时,我们看到了 consumers 消费者信息,providers 提供者信息,routers 路由信息,configurators 配置信息。
$ ls /dubbo/cn.iocoder.springboot.lab30.rpc.api.UserRpcService
[consumers, configurators, routers, providers]

# 查看 UserRpcService 服务的节点列表
# 此时,可以看到有一个节点,就是我们刚启动的服务提供者。
$ ls /dubbo/cn.iocoder.springboot.lab30.rpc.api.UserRpcService/providers
[dubbo%3A%2F%2F10.171.1.115%3A20880%2Fcn.iocoder.springboot.lab30.rpc.api.UserRpcService%3Fanyhost%3Dtrue%26application%3Duser-service-provider%26bean.name%3Dcn.iocoder.springboot.lab30.rpc.api.UserRpcService%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcn.iocoder.springboot.lab30.rpc.api.UserRpcService%26methods%3Dget%26pid%3D64176%26release%3D2.7.4.1%26revision%3D1.0.0%26side%3Dprovider%26timeout%3D1000%26timestamp%3D1575211234365%26version%3D1.0.0]

想要了解更多 Dubbo 是如何使用 Zookeeper 存储数据的,可以看看 《Dubbo 文档 —— Zookeeper 注册中心》 文档。

2.3 Consumer

对应 user-rpc-service-consumer 项目,服务消费者,会调用 user-rpc-service-provider 项目提供的 Dubbo Service 服务。

2.3.1 引入依赖

在 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>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-rpc-service-consumer</artifactId>

    <dependencies>
        <!-- 引入定义的 Dubbo API 接口 -->
        <dependency>
            <groupId>cn.iocoder.springboot.labs</groupId>
            <artifactId>user-rpc-service-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- 引入 Spring Boot 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- 实现对 Dubbo 的自动化配置 -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.4.1</version>
        </dependency>

        <!-- 使用 Zookeeper 作为注册中心 -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.13.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.13.0</version>
        </dependency>

    </dependencies>

</project>
  • 和 「2.2.1 引入依赖」 一模一样,除了 <artifactId /> 改成了 "user-rpc-service-consumer" 值。

2.3.2 应用配置文件

在 resources 目录下, 创建 application.yml 配置文件,添加 Dubbo 相关的配置,如下:

# dubbo 配置项,对应 DubboConfigurationProperties 配置类
dubbo:
  # Dubbo 应用配置
  application:
    name: user-service-consumer # 应用名
  # Dubbo 注册中心配置
  registry:
    address: zookeeper://127.0.0.1:2181 # 注册中心地址。个鞥多注册中心,可见 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文档。
  # Dubbo 消费者配置
  consumer:
    timeout: 1000 # 【重要】远程服务调用超时时间,单位:毫秒。默认为 1000 毫秒,胖友可以根据自己业务修改
    UserRpcService:
      version: 1.0.0
  • 和 「2.2.2 应用配置文件」 看起来有点类似,我们仅仅说说差异性。

  • 去掉 dubbo.protocol 配置项,因为我们是作为 Dubbo 服务的消费者,所以无需添加 Dubbo 服务提供者协议配置。

  • dubbo.consumer 配置项,Dubbo 服务消费者配置。更多属性,可见 ConsumerConfig 类。每个属性的说明,可见 《Dubbo 文档 —— dubbo:consumer》 。

  • dubbo.consumer.UserRpcService 配置项,是我们自定义的,设置每个 Service 服务的配置。更多属性,可见 ReferenceConfig 类。每个属性的说明,可见 《Dubbo 文档 —— dubbo:reference》 。

2.3.3 Dubbo XML 配置文件

在 resources 目录下,创建 dubbo.xml 配置文件,添加 Dubbo 的 Service 服务引用者,如下:

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

    <!-- 服务消费者引用服务配置 -->
    <dubbo:reference id="userService" interface="cn.iocoder.springboot.lab30.rpc.api.UserRpcService"
                     version="${dubbo.consumer.UserRpcService.version}"/>

</beans>
  • 使用 Dubbo 自定义的 Spring <dubbo:reference> 标签,引用 UserRpcService 接口对应的 Dubbo Service 服务,并创建一个 Bean 编号为 "userService" 的 Bean 对象。这样,我们在 Spring 中,就可以直接注入 UserRpcService Bean ,后续就可以像一个“本地”的 UserRpcService 进行调用使用。

更多 <dubbo:reference> 标签的属性的说明,可见 《Dubbo 文档 —— dubbo:reference》 。

2.3.4 ConsumerApplication

创建 ConsumerApplication 类,用于启动该项目,调用 Dubbo 服务。代码如下:

// ConsumerApplication.java

@SpringBootApplication
@ImportResource("classpath:dubbo.xml")
public class ConsumerApplication {

    public static void main(String[] args) {
        // 启动 Spring Boot 应用
        ConfigurableApplicationContext context = SpringApplication.run(ConsumerApplication.class, args);
    }

    @Component
    public class UserRpcServiceTest implements CommandLineRunner {

        private final Logger logger = LoggerFactory.getLogger(getClass());

        @Resource
        private UserRpcService userRpcService;

        @Override
        public void run(String... args) throws Exception {
            UserDTO user = userRpcService.get(1);
            logger.info("[run][发起一次 Dubbo RPC 请求,获得用户为({})", user);
        }

    }

}
  • 在类上,添加 @ImportResource 注解,引入 dubbo.xml 配置文件。

  • 在 UserRpcServiceTest 中,我们使用 @Resource 注解,引用通过 <dubbo:reference /> 配置的引用的 UserRpcService 服务对应的 UserRpcService Bean 。

运行 #main(String[] args) 方法,启动项目。控制台打印日志如下:

2019-12-01 23:15:47.380  INFO 65726 --- [           main] r.ConsumerApplication$UserRpcServiceTest : [run][发起一次 Dubbo RPC 请求,获得用户为(cn.iocoder.springboot.lab30.rpc.dto.UserDTO@a0a9fa5)
  • 我们在应用启动完成后,成功的发起了一次 UserRpcService 的 Dubbo RPC 的调用。

我们来使用 Zookeeper 客户端,查看 UserRpcService 服务是否多了一个消费者。操作流程如下:

# 使用 Zookeeper 自带的客户端,连接到 Zookeeper 服务器
$ bin/zkCli.sh

# 查看 UserRpcService 服务的消费者列表
# 此时,可以看到有一个节点,就是我们刚启动的服务消费者。
$ ls /dubbo/cn.iocoder.springboot.lab30.rpc.api.UserRpcService/consumers
[consumer%3A%2F%2F10.171.1.115%2Fcn.iocoder.springboot.lab30.rpc.api.UserRpcService%3Fapplication%3Duser-service-consumer%26category%3Dconsumers%26check%3Dfalse%26dubbo%3D2.0.2%26interface%3Dcn.iocoder.springboot.lab30.rpc.api.UserRpcService%26lazy%3Dfalse%26methods%3Dget%26pid%3D65726%26qos.enable%3Dfalse%26release%3D2.7.4.1%26revision%3D1.0.0%26side%3Dconsumer%26sticky%3Dfalse%26timeout%3D1000%26timestamp%3D1575213346748%26version%3D1.0.0]

至此,我们已经完成了使用 XML 配置的方式,在 Spring Boot 中使用 Dubbo 的入门。???? 虽然篇幅长了一点点,但是还是比较简单的。个人建议的话,此时此刻仅仅是看到这里,但是并没有手敲代码的胖友,可以赶紧打开 IDEA 自己敲(“抄”)一波,嘿嘿。

3. 注解配置

示例代码对应仓库:lab-30-dubbo-annotations-demo 。

本小节的示例,需要创建三个 Maven 项目,如下图所示:

  • user-rpc-service-api-02 项目:服务接口,定义 Dubbo Service API 接口,提供给消费者使用。详细代码,我们在 「3.1 API」 讲解。

  • user-rpc-service-provider-02 项目:服务提供者,实现 user-rpc-service-api-02 项目定义的 Dubbo Service API 接口,提供相应的服务。详细代码,我们在 「3.2 Provider」 中讲解。

  • user-rpc-service-consumer-02 项目:服务消费者,会调用 user-rpc-service-provider-02 项目提供的 Dubbo Service 服务。详细代码,我们在 「3.3 Consumer」 中讲解。

???? 本小节的内容上,和 「2.1 XML 配置」 会比较接近,所以会讲的相对简略,重点说差异。

艿艿:为了保证阅读体验,即使一致的内容,艿艿还是贴一遍比较好。

3.1 API

对应 user-rpc-service-api-02 项目,服务接口,定义 Dubbo Service API 接口,提供给消费者使用。

3.1.1 UserDTO

和 「2.1.1 UserDTO」 一致。

在 cn.iocoder.springboot.lab30.rpc.dto 包下,创建用于 Dubbo Service 传输类。这里,我们创建 UserDTO 类,用户信息。代码如下:

// UserDTO.java

public class UserDTO implements Serializable {

    /**
     * 用户编号
     */
    private Integer id;
    /**
     * 昵称
     */
    private String name;
    /**
     * 性别
     */
    private Integer gender;
    
    // ... 省略 set/get 方法
}

注意,要实现 java.io.Serializable 接口。因为,Dubbo RPC 会涉及远程通信,需要序列化和反序列化。

3.1.2 UserRpcService

和 3.1.2 UserRpcService」 一致。

在 cn.iocoder.springboot.lab30.rpc.api 包下,创建 Dubbo Service API 接口。这里,我们创建 UserRpcService 接口,用户服务 RPC Service 接口。代码如下:

// UserRpcService.java

public interface UserRpcService {

    /**
     * 根据指定用户编号,获得用户信息
     *
     * @param id 用户编号
     * @return 用户信息
     */
    UserDTO get(Integer id);

}

3.2 Provider

对应 user-rpc-service-provider-02 项目,服务提供者,实现 user-rpc-service-api 项目定义的 Dubbo Service API 接口,提供相应的服务。

3.2.1 引入依赖

和 「2.2.1 引入依赖」 一致。

在 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>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-rpc-service-provider-02</artifactId>

    <dependencies>
        <!-- 引入定义的 Dubbo API 接口 -->
        <dependency>
            <groupId>cn.iocoder.springboot.labs</groupId>
            <artifactId>user-rpc-service-api-02</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- 引入 Spring Boot 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- 实现对 Dubbo 的自动化配置 -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.4.1</version>
        </dependency>

        <!-- 使用 Zookeeper 作为注册中心 -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.13.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.13.0</version>
        </dependency>

    </dependencies>

</project>

3.2.2 应用配置文件

在 resources 目录下, 创建 application.yml 配置文件,添加 Dubbo 相关的配置,如下:

# dubbo 配置项,对应 DubboConfigurationProperties 配置类
dubbo:
  # Dubbo 应用配置
  application:
    name: user-service-provider # 应用名
  # Dubbo 注册中心配
  registry:
    address: zookeeper://127.0.0.1:2181 # 注册中心地址。个鞥多注册中心,可见 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文档。
  # Dubbo 服务提供者协议配置
  protocol:
    port: -1 # 协议端口。使用 -1 表示随机端口。
    name: dubbo # 使用 `dubbo://` 协议。更多协议,可见 http://dubbo.apache.org/zh-cn/docs/user/references/protocol/introduction.html 文档
  # Dubbo 服务提供者配置
  provider:
    timeout: 1000 # 【重要】远程服务调用超时时间,单位:毫秒。默认为 1000 毫秒,胖友可以根据自己业务修改
    UserRpcService:
      version: 1.0.
  # 配置扫描 Dubbo 自定义的 @Service 注解,暴露成 Dubbo 服务提供者
  scan:
    base-packages: cn.iocoder.springboot.lab30.rpc.service

和 「2.2.2 应用配置」 基本一致,差异在于多出了 dubbo.scan.base-packages 配置项,配置扫描的基础路径,后续会根据该路径,扫描使用了 Dubbo 自定义的 @Service 注解的 Service 类们,将它们暴露成 Dubbo 服务提供者。

如此,我们就不需要使用 「2.2.4 Dubbo XML 配置文件」 ,配置暴露的 Service 服务,而是通过 Dubbo 定义的 @Service 注解。

3.2.3 UserRpcServiceImpl

在 cn.iocoder.springboot.lab30.rpc.service 包下,创建 Dubbo Service 实现类。这里,我们创建 UserRpcServiceImpl 类,用户服务 RPC Service 实现类。代码如下:

// UserRpcServiceImpl.java

@Service(version = "${dubbo.provider.UserRpcService.version}")
public class UserRpcServiceImpl implements UserRpcService {

    @Override
    public UserDTO get(Integer id) {
        return new UserDTO().setId(id)
                .setName("没有昵称:" + id)
                .setGender(id % 2 + 1); // 1 - 男;2 - 女
    }

}
  • 在类上,我们添加的是 Dubbo 定义的 @Service 注解。并且,在该注解里,我们可以添加该 Service 服务的配置。当然,每个属性和 <dubbo:service /> 标签是基本一致的。也因此,每个属性的说明,还可见 《Dubbo 文档 —— dubbo:service》 。

3.2.4 ProviderApplication

创建 ProviderApplication 类,用于启动该项目,提供 Dubbo 服务。代码如下:

// ProviderApplication.java

@SpringBootApplication
public class ProviderApplication {

    public static void main(String[] args) {
        // 启动 Spring Boot 应用
        SpringApplication.run(ProviderApplication.class, args);
    }

}
  • 在类上,无需添加 @ImportResource 注解,引入 dubbo.xml 配置文件。

艿艿:后续的操作,和 「2.2.5 ProviderApplication」 是一致的,这里就不重复赘述了。

3.3 Consumer

对应 user-rpc-service-consumer-02 项目,服务消费者,会调用 user-rpc-service-provider-02 项目提供的 Dubbo Service 服务。

3.3.1 引入依赖

和 「2.3.1 引入依赖」 一致。

在 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>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-rpc-service-consumer-02</artifactId>

    <dependencies>
        <!-- 引入定义的 Dubbo API 接口 -->
        <dependency>
            <groupId>cn.iocoder.springboot.labs</groupId>
            <artifactId>user-rpc-service-api-02</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- 引入 Spring Boot 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- 实现对 Dubbo 的自动化配置 -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.4.1</version>
        </dependency>

        <!-- 使用 Zookeeper 作为注册中心 -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.13.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.13.0</version>
        </dependency>

    </dependencies>

</project>
  • 和 「3.2.1 引入依赖」 一模一样,除了 <artifactId /> 改成了 "user-rpc-service-consumer-02" 值。

3.3.2 应用配置文件

和 「2.3.2 应用配置文件」 一致。

# dubbo 配置项,对应 DubboConfigurationProperties 配置类
dubbo:
  # Dubbo 应用配置
  application:
    name: user-service-consumer # 应用名
  # Dubbo 注册中心配置
  registry:
    address: zookeeper://127.0.0.1:2181 # 注册中心地址。个鞥多注册中心,可见 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文档。
  # Dubbo 消费者配置
  consumer:
    timeout: 1000 # 【重要】远程服务调用超时时间,单位:毫秒。默认为 1000 毫秒,胖友可以根据自己业务修改
    UserRpcService:
      version: 1.0.0

3.3.3 ConsumerApplication

创建 ConsumerApplication 类,用于启动该项目,调用 Dubbo 服务。代码如下:

// ConsumerApplication.java

@SpringBootApplication
public class ConsumerApplication {

    public static void main(String[] args) {
        // 启动 Spring Boot 应用
        ConfigurableApplicationContext context = SpringApplication.run(ConsumerApplication.class, args);
    }

    @Component
    public class UserRpcServiceTest implements CommandLineRunner {

        private final Logger logger = LoggerFactory.getLogger(getClass());

        @Reference(version = "${dubbo.consumer.UserRpcService.version}")
        private UserRpcService userRpcService;

        @Override
        public void run(String... args) throws Exception {
            UserDTO user = userRpcService.get(1);
            logger.info("[run][发起一次 Dubbo RPC 请求,获得用户为({})", user);
        }

    }

}
  • 在类上,无需添加 @ImportResource 注解,引入 dubbo.xml 配置文件。

  • 在 UserRpcServiceTest 中,我们使用 Dubbo 定义的 @Reference 注解,**“直接”**引用的 UserRpcService 服务对应的 UserRpcService Bean 。并且,在该注解里,我们可以添加该 Service 服务的配置。当然,每个属性和 <dubbo:reference /> 标签是基本一致的。也因此,每个属性的说明,还可见 《Dubbo 文档 —— dubbo:reference》 。

艿艿:后续的操作,和 「2.3.4 ConsumerApplication」 是一致的,这里就不重复赘述了。

3.4 选择注解还是 XML 配置?

艿艿个人倾向的话,偏向使用 XML 配置。

主要原因是,@Reference 注解,每次引用服务的时候,都需要在注解上添加好多配置的属性。这样,服务的引用的配置后就散落到各个类里了。

虽然说,我们可以把 @Reference 注解的配置的属性值,放到 application.yaml 等等配置文件里,但是如果我们要给相同 Service 的多个 @Reference 增加新的配置属性时,就要每个注解都修改一遍。

对于这种情况,XML 配置的方式,只要修改一下该 Service 的 XML 配置,就可以全部生效了。

4. 参数验证

参数校验,对于提供 API 调用的服务来说,必然是必不可少的。在 《芋道 Spring Boot 参数校验 Validation 入门》 中,我们已经看了如何在 SpringMVC 和本地的 Service 使用参数校验的示例。

本小节,我们来学习下,如何在 Dubbo RPC Service 中,使用参数校验。在 《Dubbo 文档 —— 参数验证》 中,对该功能的描述如下:

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

  • 我们在 《芋道 Spring Boot 参数校验 Validation 入门》 中学习的,也是基于 JSR303 规范实现的,所以在使用上,是基本一致的。???? 有统一的规范,真好。

下面,我们开始在 「2. XML 配置」 小节的 lab-30-dubbo-xml-demo 示例项目,进行修改,添加参数校验的功能。

4.1 API

本小节,我们来看看对 user-rpc-service-api 项目的改造。

4.1.1 引入依赖

修改 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>lab-30-dubbo-xml-demo</artifactId>
        <groupId>cn.iocoder.springboot.labs</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-rpc-service-api</artifactId>

    <dependencies>
        <!-- 参数校验相关依赖 -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId> <!-- JSR 参数校验规范 API -->
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId> <!-- JSR 参数校验规范实现,我们使用 hibernate-validator -->
            <version>6.0.18.Final</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.el</artifactId> <!-- 可能涉及到 EL 表达,所以引入,否则 hibernate-validator 在初始化会报错 -->
            <version>3.0.1-b11</version>
        </dependency>
    </dependencies>

</project>

4.1.2 UserAddDTO

在 cn.iocoder.springboot.lab30.rpc.dto 包下,创建 UserAddDTO 类,用户添加 DTO。代码如下:

// UserAddDTO.java

public class UserAddDTO implements Serializable {

    /**
     * 昵称
     */
    @NotEmpty(message = "昵称不能为空")
    @Length(min = 5, max = 16, message = "账号长度为 5-16 位")
    private String name;
    /**
     * 性别
     */
    @NotNull(message = "性别不能为空")
    private Integer gender;
    
    // ... 省略 set/get 方法
}
  • 在 name 和 gender 属性上,我们添加了参数校验的注解。

4.1.3 UserRpcService

修改  UserRpcService 接口,代码如下:

// UserRpcService.java

public interface UserRpcService {

    /**
     * 根据指定用户编号,获得用户信息
     *
     * @param id 用户编号
     * @return 用户信息
     */
    UserDTO get(@NotNull(message = "用户编号不能为空") Integer id)
            throws ConstraintViolationException;

    /**
     * 添加新用户,返回新添加的用户编号
     *
     * @param addDTO 添加的用户信息
     * @return 用户编号
     */
    Integer add(UserAddDTO addDTO)
            throws ConstraintViolationException;

}
  • 在已有的 #get(Integer id) 方法上,添加 @NotNull 注解,校验用户编号不允许传空。

  • 新增 #add(UserAddDTO addDTO) 方法,添加新用户,返回新添加的用户编号。我们已经在 UserAddDTO 类,添加了相应的参数校验的注解。

  • 注意,因为参数校验不通过时,会抛出 ConstraintViolationException 异常,所以需要在接口的方法,显示使用 throws 注明。具体的原因,可以看看 《浅谈 Dubbo 的 ExceptionFilter 异常处理》 文章,了解下 Dubbo 的异常处理机制。

4.2 Provider

本小节,我们来看看对 user-rpc-service-provider 项目的改造。

4.2.1 UserRpcServiceImpl

修改 UserRpcServiceImpl 类,简单实现下 #add(UserAddDTO addDTO) 方法。代码如下:

// UserRpcServiceImpl.java

@Override
public Integer add(UserAddDTO addDTO) {
    return (int) (System.currentTimeMillis() / 1000); // 嘿嘿,随便返回一个 id
} 

4.2.2 Dubbo XML 配置文件

修改 dubbo.xml 配置文件,开启 UserRpcService 的参数校验功能。配置如下:

<dubbo:service ref="userRpcServiceImpl" interface="cn.iocoder.springboot.lab30.rpc.api.UserRpcService"
    version="${dubbo.provider.UserRpcService.version}" validation="true" />
  • 这里,我们将 validation 设置为 "true" ,开启 Dubbo 服务提供者的 UserRpcService 服务的参数校验的功能。

???? 如果胖友想把 Dubbo 服务提供者的所有 Service 服务的参数校验都开启,可以修改 application.yaml 配置文件,增加 dubbo.provider.validation = true 配置。

4.3 Consumer

本小节,我们来看看对 user-rpc-service-consumer 项目的改造。

4.3.1 Dubbo XML 配置文件

修改 dubbo.xml 配置文件,开启 UserRpcService 的参数校验功能。配置如下:

<dubbo:reference id="userService" interface="cn.iocoder.springboot.lab30.rpc.api.UserRpcService"
                 version="${dubbo.consumer.UserRpcService.version}" validation="true" />
  • 这里,我们将 validation 设置为 "true" ,开启 Dubbo 服务消费者的 UserRpcService 服务的参数校验的功能。

???? 如果胖友想把 Dubbo 服务消费者的所有 Service 服务的参数校验都开启,可以修改 application.yaml 配置文件,增加 dubbo.consumer.validation = true 配置。

可能胖友会有疑惑,服务提供者和服务消费者的 validation = true ,都是开启参数校验规则,会有什么区别呢?Dubbo 内置 ValidationFilter 过滤器,实现参数校验的功能,可作用于服务提供者和服务消费者。效果如下:

  • 如果服务消费者开启参数校验,请求参数校验不通过时,结束请求,抛出 ConstraintViolationException 异常。即,不会向服务提供者发起请求。

  • 如果服务提供者开启参数校验,请求参数校验不通过时,结束请求,抛出 ConstraintViolationException 异常。即,不会执行后续的业务逻辑。

实际项目在使用时,至少要开启服务提供者的参数校验功能。

4.3.2 ConsumerApplication

修改 ConsumerApplication 类,增加调用 UserRpcService 服务时,参数校验不通过的示例。代码如下:

// ConsumerApplication.java

@Component
public class UserRpcServiceTest02 implements CommandLineRunner {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private UserRpcService userRpcService;

    @Override
    public void run(String... args) throws Exception {
        // 获得用户
        try {
            // 发起调用
            UserDTO user = userRpcService.get(null); // 故意传入空的编号,为了校验编号不通过
            logger.info("[run][发起一次 Dubbo RPC 请求,获得用户为({})]", user);
        } catch (Exception e) {
            logger.error("[run][获得用户发生异常,信息为:[{}]", e.getMessage());
        }

        // 添加用户
        try {
            // 创建 UserAddDTO
            UserAddDTO addDTO = new UserAddDTO();
            addDTO.setName("yudaoyuanmayudaoyuanma"); // 故意把名字打的特别长,为了校验名字不通过
            addDTO.setGender(null); // 不传递性别,为了校验性别不通过
            // 发起调用
            userRpcService.add(addDTO);
            logger.info("[run][发起一次 Dubbo RPC 请求,添加用户为({})]", addDTO);
        } catch (Exception e) {
            logger.error("[run][添加用户发生异常,信息为:[{}]", e.getMessage());
        }
    }

}
  • 添加了两段代码,分别调用 UserRpcService 服务的 #get(Integer id) 和 #add(UserAddDTO addDTO) 方法,并且是参数不符合校验条件的示例。

运行 #main(String[] args) 方法,启动项目。控制台打印日志如下:

// 调用 UserRpcService 服务的 `#get(Integer id)` 方法,参数不通过
2019-12-01 13:19:08.836 ERROR 7055 --- [           main] ConsumerApplication$UserRpcServiceTest02 : [run][获得用户发生异常,信息为:[Failed to validate service: cn.iocoder.springboot.lab30.rpc.api.UserRpcService, method: get, cause: [ConstraintViolationImpl{interpolatedMessage='用户编号不能为空', propertyPath=getArgument0, rootBeanClass=class cn.iocoder.springboot.lab30.rpc.api.UserRpcService_GetParameter_java.lang.Integer, messageTemplate='用户编号不能为空'}]]

// 调用 UserRpcService 服务的 `#add(UserAddDTO addDTO)` 方法,参数不通过
2019-12-01 13:19:08.840 ERROR 7055 --- [           main] ConsumerApplication$UserRpcServiceTest02 : [run][添加用户发生异常,信息为:[Failed to validate service: cn.iocoder.springboot.lab30.rpc.api.UserRpcService, method: add, cause: [ConstraintViolationImpl{interpolatedMessage='性别不能为空', propertyPath=gender, rootBeanClass=class cn.iocoder.springboot.lab30.rpc.dto.UserAddDTO, messageTemplate='性别不能为空'}, ConstraintViolationImpl{interpolatedMessage='账号长度为 5-16 位', propertyPath=name, rootBeanClass=class cn.iocoder.springboot.lab30.rpc.dto.UserAddDTO, messageTemplate='账号长度为 5-16 位'}]]
  • 上述贼长的两段日志,我们可以看到两次 UserRpcService 服务的调用,都抛出了 ConstraintViolationException 异常。

4.4 存在的问题

如果我们关闭掉服务消费者的参数校验功能,而只使用服务提供者的参数校验功能的情况下,当参数校验不通过时,因为 Hibernate ConstraintDescriptorImpl 没有默认空构造方法,所以 Hessian 反序列化时,会抛出 HessianProtocolException 异常。详细如下:

Caused by: com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl' could not be instantiated
 at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:316)
 at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:201)
 at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2818)
 at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2145)
 at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074)
 at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2118)
 at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074)
 at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:406)

目前有两种解决方案:

  • 方案一,不要关闭掉服务消费者的参数校验功能。

  • 方案二,参考 《Dubbo 使用 JSR303 框架 hibernate-validator 遇到 ConstraintDescriptorImpl could not be instantiated》 文章的方法三。

  • 方案三,Service 接口上,不要抛出 ConstraintViolationException 异常。这样,该异常就可以被 Dubbo 内置的 ExceptionFilter 封装成 RuntimeException 异常,就不会存在反序列化的问题。

不过目前方案二,提交在 https://github.com/apache/incubator-dubbo/pull/1708 的 PR 代码,已经被 Dubbo 开发团队否决了。所以,目前建议还是采用方案一来解决。

5. 自定义实现拓展点

在「4. 参数校验」 小节中,我们入门了 Dubbo 提供的参数校验的功能,它是由 ValidationFilter 过滤器,通过拦截请求,根据我们添加 JSR303 定义的注解,校验参数是否正确。在 Dubbo 框架中,还提供了 AccessLogFilter、ExceptionFilter 等等过滤器,他们都属于 Dubbo Filter 接口的实现类。

而实际上,Filter 是 Dubbo 定义的 调用拦截 拓展点。除了 Filter 拓展点,Dubbo 还定义了 协议、路由、注册中心 等等拓展点。如下图所示:

而这些 Dubbo 拓展点,通过 Dubbo SPI 机制,进行加载。可能胖友对 Dubbo SPI 机制有点懵逼。嘿嘿,一定没有好好读过 Dubbo 的官方文档:

FROM 《Dubbo 扩展点加载》

Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。

Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。

Dubbo 改进了 JDK 标准的 SPI 的以下问题:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。

  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

下面,我们实现一个对 ExceptionFilter 增强的过滤器,实现即使 Service API 接口上,未定义 ServiceException、ConstraintViolationException 等异常,也不会自动封装成 RuntimeException 。???? 毕竟,要求每个开发同学记得在 Service API 接口上,添加 ServiceException、ConstraintViolationException 等异常,是挺困难的事情,总是可能不经意遗忘。

下面,我们继续在 「2. XML 配置」 小节的 lab-30-dubbo-xml-demo 示例项目,进行修改,添加自定义 ExceptionFilter 增强的过滤器的功能。

艿艿:关于本小节的内容,艿艿希望胖友有看过 《芋道 Spring Boot SpringMVC 入门》 的 「4. 全局统一返回」 和 「5. 全局异常处理」 小节的内容,因为涉及到的思路是一致的。

5.1 API

本小节,我们来看看对 user-rpc-service-api 项目的改造。

5.1.1 ServiceExceptionEnum

在 cn.iocoder.springboot.lab30.rpc.core 包路径,创建 ServiceExceptionEnum 枚举类,枚举项目中的错误码。代码如下:

// ServiceExceptionEnum.java

public enum ServiceExceptionEnum {

    // ========== 系统级别 ==========
    SUCCESS(0, "成功"),
    SYS_ERROR(2001001000, "服务端发生异常"),
    MISSING_REQUEST_PARAM_ERROR(2001001001, "参数缺失"),

    // ========== 用户模块 ==========
    USER_NOT_FOUND(1001002000, "用户不存在"),

    // ========== 订单模块 ==========

    // ========== 商品模块 ==========
    ;

    /**
     * 错误码
     */
    private int code;
    /**
     * 错误提示
     */
    private String message;

    ServiceExceptionEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    // ... 省略 getting 方法

}
  • 因为错误码是全局的,最好按照模块来拆分。如下是艿艿在 onemall 项目的实践:

    /**
     * 服务异常
     *
     * 参考 https://www.kancloud.cn/onebase/ob/484204 文章
     *
     * 一共 10 位,分成四段
     *
     * 第一段,1 位,类型
     *      1 - 业务级别异常
     *      2 - 系统级别异常
     * 第二段,3 位,系统类型
     *      001 - 用户系统
     *      002 - 商品系统
     *      003 - 订单系统
     *      004 - 支付系统
     *      005 - 优惠劵系统
     *      ... - ...
     * 第三段,3 位,模块
     *      不限制规则。
     *      一般建议,每个系统里面,可能有多个模块,可以再去做分段。以用户系统为例子:
     *          001 - OAuth2 模块
     *          002 - User 模块
     *          003 - MobileCode 模块
     * 第四段,3 位,错误码
     *       不限制规则。
     *       一般建议,每个模块自增。
     */
    

5.1.2 ServiceException

在 cn.iocoder.springboot.lab30.rpc.core 包路径,创建 ServiceException 异常类,继承 RuntimeException 异常类,用于定义业务异常。代码如下:

public final class ServiceException extends RuntimeException {

    /**
     * 错误码
     */
    private Integer code;

    public ServiceException() { // 创建默认构造方法,用于反序列化的场景。
    }

    public ServiceException(ServiceExceptionEnum serviceExceptionEnum) {
        // 使用父类的 message 字段
        super(serviceExceptionEnum.getMessage());
        // 设置错误码
        this.code = serviceExceptionEnum.getCode();
    }

    public ServiceException(ServiceExceptionEnum serviceExceptionEnum, String message) {
        // 使用父类的 message 字段
        super(message);
        // 设置错误码
        this.code = serviceExceptionEnum.getCode();
    }

    public Integer getCode() {
        return code;
    }

}

5.2 Provider

本小节,我们来看看对 user-rpc-service-provider 项目的改造。

5.2.1 DubboExceptionFilter

在 cn.iocoder.springboot.lab30.rpc.filter 包路径,创建 DubboExceptionFilter ,继承 ListenableFilter 抽象类,实现对 ExceptionFilter 增强的过滤器。代码如下:

// DubboExceptionFilter.java

@Activate(group = CommonConstants.PROVIDER)
public class DubboExceptionFilter extends ListenableFilter {

    public DubboExceptionFilter() {
        super.listener = new ExceptionListenerX();
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        return invoker.invoke(invocation);
    }

    static class ExceptionListenerX extends ExceptionListener {

        @Override
        public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
            // 发生异常,并且非泛化调用
            if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
                Throwable exception = appResponse.getException();
                // <1> 如果是 ServiceException 异常,直接返回
                if (exception instanceof ServiceException) {
                    return;
                }
                // <2> 如果是参数校验的 ConstraintViolationException 异常,则封装返回
                if (exception instanceof ConstraintViolationException) {
                    appResponse.setException(this.handleConstraintViolationException((ConstraintViolationException) exception));
                    return;
                }
            }
            // <3> 其它情况,继续使用父类处理
            super.onResponse(appResponse, invoker, invocation);
        }

        private ServiceException handleConstraintViolationException(ConstraintViolationException ex) {
            // 拼接错误
            StringBuilder detailMessage = new StringBuilder();
            for (ConstraintViolation<?> constraintViolation : ex.getConstraintViolations()) {
                // 使用 ; 分隔多个错误
                if (detailMessage.length() > 0) {
                    detailMessage.append(";");
                }
                // 拼接内容到其中
                detailMessage.append(constraintViolation.getMessage());
            }
            // 返回异常
            return new ServiceException(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR,
                    detailMessage.toString());
        }

    }

    static class ExceptionListener implements Listener {

        private Logger logger = LoggerFactory.getLogger(ExceptionListener.class);

        @Override
        public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
            if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
                try {
                    Throwable exception = appResponse.getException();

                    // directly throw if it's checked exception
                    if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
                        return;
                    }
                    // directly throw if the exception appears in the signature
                    try {
                        Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                        Class<?>[] exceptionClassses = method.getExceptionTypes();
                        for (Class<?> exceptionClass : exceptionClassses) {
                            if (exception.getClass().equals(exceptionClass)) {
                                return;
                            }
                        }
                    } catch (NoSuchMethodException e) {
                        return;
                    }

                    // for the exception not found in method's signature, print ERROR message in server's log.
                    logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);

                    // directly throw if exception class and interface class are in the same jar file.
                    String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                    String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                    if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
                        return;
                    }
                    // directly throw if it's JDK exception
                    String className = exception.getClass().getName();
                    if (className.startsWith("java.") || className.startsWith("javax.")) {
                        return;
                    }
                    // directly throw if it's dubbo exception
                    if (exception instanceof RpcException) {
                        return;
                    }

                    // otherwise, wrap with RuntimeException and throw back to the client
                    appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
                    return;
                } catch (Throwable e) {
                    logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
                    return;
                }
            }
        }

        @Override
        public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
            logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
        }

        // For test purpose
        public void setLogger(Logger logger) {
            this.logger = logger;
        }
    }

}
  • 在类上,添加 @Activate 注解,并设置 "group = CommonConstants.PROVIDER" 属性,将 DubboExceptionFilter 过滤器仅在服务提供者生效。

  • 因为目前 Dubbo 源码改版,建议在对于 Filter 拓展点的实现,继承 ListenableFilter 抽象类,更简易的实现对调用结果的处理。

  • 在构造方法中,我们创建了 ExceptionListenerX 类,作为 listener 监听器。而 ExceptionListenerX 继承自的 ExceptionListener 类,是我们直接从 Dubbo ExceptionFilter.ExceptionListener 复制过来的逻辑,为了保持 ExceptionFilter 原有逻辑的不变。下面,让我们来看看 ExceptionListenerX 的实现代码:

    • <1> 处,如果是 ServiceException 异常,直接返回。

    • <2> 处,如果是参数校验的 ConstraintViolationException 异常,则调用 #handleConstraintViolationException(ConstraintViolationException ex) 方法,封装成 ServiceException 异常,之后返回。

    • <3> 处,其它情况,继续使用父类 ExceptionListener 来处理。

这里,可能有胖友对 ExceptionFilter 异常处理不是很了解,建议看看 《浅谈 Dubbo 的 ExceptionFilter 异常处理》 文章。

另外,DubboExceptionFilter 是 「4.4 存在问题」 的方案二的一种变种解决方案。

5.2.2 Dubbo SPI 配置文件

在 resources 目录下,创建 META-INF/dubbo/ 目录,然后创建 org.apache.dubbo.rpc.Filter 配置文件,配置如下:

dubboExceptionFilter=cn.iocoder.springboot.lab30.rpc.filter.DubboExceptionFilter
  • org.apache.dubbo.rpc.Filter 配置文件名,不要乱创建,就是 DubboExceptionFilter 对应的 Dubbo SPI 拓展点 Filter 。

  • 该配置文件里的每一行,格式为 ${拓展名}=${拓展类全名}。这里,我们配置了一个拓展名为 dubboExceptionFilter。

5.2.3 UserRpcServiceImpl

修改 UserRpcServiceImpl 类,修改下 #add(UserAddDTO addDTO) 方法,抛出 ServiceException 异常。代码如下:

// UserRpcServiceImpl.java

@Override
public Integer add(UserAddDTO addDTO) {
    // 【额外添加】这里,模拟用户已经存在的情况
    if ("yudaoyuanma".equals(addDTO.getName())) {
        throw new ServiceException(ServiceExceptionEnum.USER_EXISTS);
    }
    return (int) (System.currentTimeMillis() / 1000); // 嘿嘿,随便返回一个 id
}

5.2.4 应用配置文件

修改 application.yml 配置文件,添加 dubbo.provider.filter=-exception 配置项,去掉服务提供者的 ExceptionFilter 过滤器。

如果胖友仅仅想去掉 UserRpcService 服务的 ExceptionFilter 过滤器,可以修改 dubbo.xml 配置文件,配置如下:

<dubbo:service ref="userRpcServiceImpl" interface="cn.iocoder.springboot.lab30.rpc.api.UserRpcService"
    version="${dubbo.provider.UserRpcService.version}" validation="true" filter="-exception" />
  • 这里,我们将 filter 设置为 "-exception" ,去掉服务提供者的 UserRpcService 的 ExceptionFilter 过滤器。

当然,一般情况下啊,我们采用全局配置,即通过 dubbo.provider.filter=-exception 配置项。

5.3 Consumer

本小节,我们来看看对 user-rpc-service-consumer 项目的改造。

5.3.1 ConsumerApplication

修改 ConsumerApplication 类,增加调用 UserRpcService 服务时,抛出 ServiceException 异常的示例。代码如下:

// ConsumerApplication.java

@Component
public class UserRpcServiceTest03 implements CommandLineRunner {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private UserRpcService userRpcService;

    @Override
    public void run(String... args) {
        // 添加用户
        try {
            // 创建 UserAddDTO
            UserAddDTO addDTO = new UserAddDTO();
            addDTO.setName("yudaoyuanma"); // 设置为 yudaoyuanma ,触发 ServiceException 异常
            addDTO.setGender(1);
            // 发起调用
            userRpcService.add(addDTO);
            logger.info("[run][发起一次 Dubbo RPC 请求,添加用户为({})]", addDTO);
        } catch (Exception e) {
            logger.error("[run][添加用户发生异常({}),信息为:[{}]", e.getClass().getSimpleName(), e.getMessage());
        }
    }

}
  • 添加了一段代码,调用 UserRpcService 服务的#add(UserAddDTO addDTO) 方法,并且是抛出 ServiceException 异常的示例。

运行 #main(String[] args) 方法,启动项目。控制台打印日志如下:

2019-12-01 16:17:39.919 ERROR 14738 --- [           main] ConsumerApplication$UserRpcServiceTest03 : [run][添加用户发生异常(ServiceException),信息为:[用户已存在]
  • 我们可以看到,成功抛出 ServiceException 异常,即使我们在 UserRpcService API 接口的 #add(UserAddDTO addDTO) 方法上,并未显示 throws 抛出 UserRpcService 异常。

5.4 小结

实际上,因为我们把 ServiceException 放在了 Service API 所在的 Maven 项目里,所以即使使用 Dubbo 内置的 ExceptionFilter 过滤器,并且 UserRpcService API 接口的 #add(UserAddDTO addDTO) 方法并未显示 throws 抛出 UserRpcService 异常,ExceptionFilter 也不会把 UserRpcService 封装成 RuntimeException 异常。咳咳咳 ???? 如果不了解的胖友,胖友在回看下 《浅谈 Dubbo 的 ExceptionFilter 异常处理》 文章,结尾的“4. 把异常放到 provider-api 的 jar 包中”。

实际项目的 ExceptionFilter 增强封装,可以看看艿艿在开源项目 onemall 中,会把 ServiceException 和 DubboExceptionFilter 放在 common-framework 框架项目中,而不是各个业务项目中。

6. 整合 Nacos

示例代码对应仓库:lab-30-dubbo-annotations-nacos 。

本小节我们来进行 Dubbo 和 Nacos 的整合,使用 Nacos 作为 Dubbo 的注册中心。Dubbo 提供了 dubbo-registry-nacos 子项目,已经对 Nacos 进行适配,所以我们只要引入它,基本就完成了 Dubbo 和 Nacos 的整合,贼方便。

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。

还是老样子,我们从「3. 注解配置」小节,复制出对应的三个 Maven 项目来进行改造,进行 Nacos 的整合。最终项目如下图所示:

友情提示:本小节需要搭建一个 Nacos 服务,可以参考《Nacos 极简入门》文章。

6.1 API

将「3. 注解配置」小节的 user-rpc-service-api-02,复制出 user-rpc-service-api-03,无需做任何改动。

6.2 Provider

将「3. 注解配置」小节的 user-rpc-service-provider-02,复制出 user-rpc-service-provider-03,接入 Nacos 作为注册中心。改动点如下图:

6.2.1 引入依赖

修改 pom.xml 文件,额外引入 Sentinel 相关的依赖如下:

<!-- 使用 Nacos 作为注册中心 -->
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.2.1</version>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-registry-nacos</artifactId>
    <version>2.7.4.1</version>
</dependency>

6.2.2 配置文件

修改 application.yaml 配置文件,修改 dubbo.registry.address 配置项,设置 Nacos 作为注册中心。完整配置如下:

# dubbo 配置项,对应 DubboConfigurationProperties 配置类
dubbo:
  # Dubbo 应用配置
  application:
    name: user-service-provider # 应用名
  # Dubbo 注册中心配
  registry:
    address: nacos://127.0.0.1:8848 # 注册中心地址。个鞥多注册中心,可见 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文档。
  # Dubbo 服务提供者协议配置
  protocol:
    port: -1 # 协议端口。使用 -1 表示随机端口。
    name: dubbo # 使用 `dubbo://` 协议。更多协议,可见 http://dubbo.apache.org/zh-cn/docs/user/references/protocol/introduction.html 文档
  # Dubbo 服务提供者配置
  provider:
    timeout: 1000 # 【重要】远程服务调用超时时间,单位:毫秒。默认为 1000 毫秒,胖友可以根据自己业务修改
    UserRpcService:
      version: 1.0.0
  # 配置扫描 Dubbo 自定义的 @Service 注解,暴露成 Dubbo 服务提供者
  scan:
    base-packages: cn.iocoder.springboot.lab30.rpc.service

友情提示:艿艿本机搭建的 Nacos 服务启动在默认的 8848 端口。

6.3 Consumer

将「3. 注解配置」小节的 user-rpc-service-consumer-02,复制出 user-rpc-service-consumer-03,接入 Nacos 作为注册中心。改动点如下图:

友情提示:整合的过程,和「6.2 Provider」一模一样。

6.3.1 引入依赖

修改 pom.xml 文件,额外引入 Sentinel 相关的依赖如下:

<!-- 使用 Nacos 作为注册中心 -->
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.2.1</version>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-registry-nacos</artifactId>
    <version>2.7.4.1</version>
</dependency>

6.3.2 配置文件

修改 application.yaml 配置文件,修改 dubbo.registry.address 配置项,设置 Nacos 作为注册中心。完整配置如下:

# dubbo 配置项,对应 DubboConfigurationProperties 配置类
dubbo:
  # Dubbo 应用配置
  application:
    name: user-service-consumer # 应用名
  # Dubbo 注册中心配置
  registry:
    address: nacos://127.0.0.1:8848 # 注册中心地址。个鞥多注册中心,可见 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文档。
  # Dubbo 消费者配置
  consumer:
    timeout: 1000 # 【重要】远程服务调用超时时间,单位:毫秒。默认为 1000 毫秒,胖友可以根据自己业务修改
    UserRpcService:
      version: 1.0.0

友情提示:艿艿本机搭建的 Nacos 服务启动在默认的 8848 端口。

6.4 简单测试

① 使用 ProviderApplication 启动服务提供者。在 Nacos 控台中,我们可以看到以 providers 为开头的服务提供者,如下图所示:

  • 服务列表
  • 服务提供者

② 使用 ConsumerApplication 启动服务消费者。在 Nacos 控台中,我们可以看到以 consumers 为开头的服务消费者,如下图所示:

  • 服务列表
  • 服务

更多关于 Dubbo 集成 Nacos 作为注册中心的内容,可以看看《Dubbo 文档 —— Nacos 注册中心》。

7. 整合 Sentinel

示例代码对应仓库:lab-30-dubbo-annotations-sentinel 。

本小节我们来进行 Dubbo 和 Sentinel 的整合,使用 Sentinel 进行 Dubbo 的流量保护。Sentinel 提供了 sentinel-apache-dubbo-adapter 子项目,已经对 Dubbo 进行适配,所以我们只要引入它,基本就完成了 Dubbo 和 Sentinel 的整合,贼方便。

Sentinel 是阿里中间件团队开源的,面向分布式服务架构的轻量级流量控制产品,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。

还是老样子,我们从「3. 注解配置」小节,复制出对应的三个 Maven 项目来进行改造,进行 Sentinel 的整合。最终项目如下图所示:

友情提示:本小节需要搭建一个 Sentinel 服务,可以参考《Sentinel 极简入门》文章。

7.1 API

将「3. 注解配置」小节的 user-rpc-service-api-02,复制出 user-rpc-service-api-04,无需做任何改动。

7.2 Provider

将「3. 注解配置」小节的 user-rpc-service-provider-02,复制出 user-rpc-service-provider-03,接入 Sentinel 实现服务消费者的流量控制。改动点如下图:

7.2.1 引入依赖

修改 pom.xml 文件,额外引入 Sentinel 相关的依赖如下:

<!-- Sentinel 核心库 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.7.1</version>
</dependency>
<!-- Sentinel 接入控制台 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>1.7.1</version>
</dependency>
<!-- Sentinel 对 Dubbo 的支持 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-apache-dubbo-adapter</artifactId>
    <version>1.7.1</version>
</dependency>

7.2.2 Sentinel 配置文件

在 resources 目录下,创建 Sentinel 自定义的sentinel.properties 配置文件。内容如下:

csp.sentinel.dashboard.server=127.0.0.1:7070
  • csp.sentinel.dashboard.server 配置项,设置 Sentinel 控制台地址。

  • 更多其它配置项,可见《Sentinel 官方文档 —— 启动配置项》。

7.3 Consumer

将「2. 快速入门」小节的 user-rpc-service-consumer-02,复制出 user-rpc-service-consumer-04,接入 Sentinel 实现服务消费者的流量控制。改动点如下图:

友情提示:整合的过程,和「7.2 Provider」一模一样。

7.3.1 引入依赖

修改 pom.xml 文件,额外引入 Sentinel 相关的依赖如下:

<!-- Sentinel 核心库 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.7.1</version>
</dependency>
<!-- Sentinel 接入控制台 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>1.7.1</version>
</dependency>
<!-- Sentinel 对 Dubbo 的支持 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-apache-dubbo-adapter</artifactId>
    <version>1.7.1</version>
</dependency>

7.3.2 Sentinel 配置文件

在 resources 目录下,创建 Sentinel 自定义的sentinel.properties 配置文件。内容如下:

csp.sentinel.dashboard.server=127.0.0.1:7070

7.3.3 UserController

创建 UserController 类,增加调用 UserRpcService 服务的 HTTP API 接口。代码如下:

@RestController
@RequestMapping("/user")
public class UserController {

    @Reference(version = "${dubbo.consumer.UserRpcService.version}")
    private UserRpcService userRpcService;

    @GetMapping("/get")
    public UserDTO get(@RequestParam("id") Integer id) {
        return userRpcService.get(id);
    }

}

友情提示:注意,需要额外引入 spring-boot-starter-web 依赖。因为它不是主角,所以并没有主动写出来哈~

7.4 简单测试

① 使用 ProviderApplication 启动服务提供者。使用 ConsumerApplication 启动服务消费者。

② 访问服务消费者的 http://127.0.0.1:8080/user/get?id=1 接口,保证相关资源的初始化。

下面,我们来演示使用 Sentinel 对服务消费者的流量控制。

而 Sentinel 对服务提供者的流量控制是一样的,胖友可以自己去尝试。

③ 使用浏览器,访问下 http://127.0.0.1:7070/ 地址,进入 Sentinel 控制台。

然后,点击 Sentinel 控制台的「簇点链路」菜单,可以看到看到 Dubbo 服务消费者产生的 cn.iocoder.springboot.lab30.rpc.api.UserRpcService.UserRpcService:get(java.lang.Integer) 资源。如下图所示:

点击 n.iocoder.springboot.lab30.rpc.api.UserRpcService.UserRpcService:get(java.lang.Integer) 资源所在列的「流控」按钮,弹出「新增流控规则」。填写流控规则,如下图所示:

  • 这里,我们创建的是比较简单的规则,仅允许该资源被每秒调用一次。

④ 使用浏览器,快速访问 http://127.0.0.1:8080/user/get?id=1 接口两次,会调用 UserService#get(Integer id) 方法两次,会有一次被 Sentinel 流量控制而拒绝,返回结果如下图所示:

因为默认的错误提示不是很友好,所以胖友可以自定义 SpringMVC 全局错误处理器,对 Sentinel 的异常进行处理。感兴趣的胖友,可以阅读《芋道 Spring Boot SpringMVC 入门》文章的「5. 全局异常处理」小节。

重要的友情提示:更多 Sentinel 的使用方式,胖友可以阅读《芋道 Spring Boot 服务容错 Sentinel 入门》文章。

7.5 DubboFallback

sentinel-apache-dubbo-adapter 支持配置全局的 fallback 函数,可以在 Dubbo 服务被 Sentinel 限流/降级/负载保护的时候,进行相应的 fallback 处理。

  • 我们只需要实现自定义的 DubboFallback 接口,并通过 DubboFallbackRegistry 进行注册即可。

  • 默认情况下,使用 DubboFallback 的 DefaultDubboFallback 实现类,它会将 BlockException 包装成 SentinelRpcException 异常后抛出。

另外,我们还可以配合 Dubbo 的 fallback 机制,来为降级的服务提供替代的实现。

666. 彩蛋

现在,Dubbo 可以说从原本的 Java RPC 框架,演化成 Dubbo 生态体系,其周边也越来越丰富。所以,让我们一起来期望 《Dubbo 3.0 预览版详细解读,关注异步化和响应式编程》 。

???? 无意中,发现 Dubbo 官方已经整理了 Dubbo 的整个生态体系,具体可以看看 Build production-ready microservices 页面。咳咳咳,真特喵的齐全,完全学不动了。

另外,有一点需要提醒下,很多初学 Dubbo 的胖友,可能会犯跟艿艿一样的错误,直接把原本的 Service 层,直接接入 Dubbo 框架,提供 Dubbo Service RPC 调用。其实这是不对的!具体的代码结构和项目的示例,可以看看 onemall/demo 项目。

因为本文仅仅是在 Spring Boot 下使用 Dubbo RPC 框架的入门文章,这里在推荐一些不错的内容:

  • 《Dubbo 官方文档》 :还有比官方文档更香的东西么?在国内的开源项目中,Dubbo 的文档质量,起码 TOP10 吧。

  • dubbo-samples 仓库:提供了大量的示例,美滋滋。

  • 《设计 RPC 接口时,你有考虑过这些吗?》 :让你更优雅的设计 RPC 接口。

  • 《你的项目应该如何正确分层?》 :可以借鉴的项目分层。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

兄弟,艿一口,点个赞!????

菜鸟学Dubbo和Nacos随记(更新中) Dubbo和Nacos是两个在微服务架构中常用的工具,它们各自扮演着不同的角色。Dubbo主要关注于RPC框架的实现,提供了高性能的远程方法调用和服务治理功能;而Nacos则更侧重于服务发现、配置管理和服务管理平台的建设。Dubbo是阿里巴巴公司开源的一个高性能、轻量级的Java RPC框架。它允许应用通过高性能的RPC实现服务的输出和输入功能,并可以与Spring框架无缝集成。 阅读详情

相关推荐

SpringBoot整合dubbo以及nacos

注意,我们这边整合的时候分为消费者,生产者和中间服务接口层。

fly狮子座的博客 4495

dubbo整合nacos

文章目录前言一、搭建工程框架新建maven空工程作为父工程新建服务提供者provider模块新建消费者 consumer模块新建公共接口interface模块二、各模块代码2.1服务提供者 boot-user-service-provider代码构架pom文件依赖配置文件application.yamlmapperservice启动类2.2服务消费者 boot-user-service-consumer代码构架pom文件依赖配置文件beancontroler其他文件,包括启动类等,无变化,不需要调整。2.3

weixin_37558140的博客 2万+

Spring Cloud Alibaba Nacos与Sentinel源码深度解析:微服务治理核心原理与实战

在微服务架构中,服务注册发现与流量治理是保障系统稳定性的基石。其核心原理在于通过分布式协调与实时数据统计,实现服务实例的动态管理与异常流量的精准控制。从技术价值看,深入理解这些机制不仅能提升系统的高可用性与可扩展性,更是排查线上复杂问题的关键。具体到应用场景,例如在应对服务注册延迟或配置推送失败时,掌握客户端长轮询与心跳机制的工作原理至关重要;而在处理限流不准确或熔断失效问题时,则需要明晰滑动时间窗口算法与熔断状态机的底层实现。本文聚焦于Spring Cloud Alibaba生态下的Nacos与Senti

weixin_34258838的博客 383

手把手带你实现Dubbo集成Nacos作为注册中心(超详细+全代码)

SpringCloud项目,Dubbo集成Nacos作为注册中心

一名Java语言狂热分子。 1万+

微服务架构实战:从Nacos、Sentinel到Seata的电商项目深度解析

微服务架构通过将单体应用拆分为多个独立部署、松耦合的服务,解决了传统单体应用迭代僵化、扩展性差等痛点。其核心原理在于服务间的协同治理,包括服务注册发现、配置管理、流量控制和数据一致性保障。这一架构的技术价值在于提升了系统的可维护性、可扩展性和技术选型的灵活性,使其能够更好地应对高并发、快速迭代的业务场景。在电商、金融、社交等互联网应用领域,微服务已成为主流架构模式。本文聚焦于Spring Cloud Alibaba生态,通过一个模拟电商核心链路的实战项目,深入剖析Nacos集群在服务注册与配置管理中的高可用

weixin_34302798的博客 409

将Python爬取的数据导入钉钉多维表

摘要:本文介绍将Python爬取的数据导入钉钉多维表的完整解决方案。首先通过requests库爬取数据并解析为结构化格式,然后使用钉钉开放平台API对接多维表。关键步骤包括获取app_key、app_secret等凭证,通过OAuth2获取access_token,以及批量写入记录。文章详细说明了参数获取位置、字段类型匹配、权限配置等注意事项,并提供了错误处理建议和性能优化方法。适用于需要将网络数据同步到钉钉多维表的开发场景。

IT深耕十余载,大道之简 2131

SpringBoot与SpringCloudAlibaba版本对应关系、SpringCloudAlibaba集成的组件版本(Sentinel、Nacos、RocketMQ、Dubbo、Seata)

SpringBoot与SpringCloudAlibaba版本对应关系、SpringCloudAlibaba集成的组件版本(Sentinel、Nacos、RocketMQ、Dubbo、Seata)

聂可以的博客 1840

【自由谈】城域网IPv6过渡技术——MAP技术(1)

在IPv4向IPv6网络演进的浪潮中,存在着两对主要矛盾的较量,一对是IPv4地址短缺和IPv4业务蓬勃发展之间的矛盾,另一对是IPv6海量的地址空间和IPv6应用的匮乏之间的矛盾。同时两对矛盾又互相制约,IPv4地址共享机制似乎又减缓了IPv6产业链的发展,IPv6产业链的不断发展似乎又在考验着IPv4地址共享机制的部署规模。MAP成为目前兼顾IPv4和IPv6的4voer6场景中最为关注的技术。

韩立金自由谈 3022

Sentinel - 在 Dubbo 微服务中使用:Alibaba 生态无缝集成

Sentinel 在 Dubbo 微服务中的应用 摘要 本文介绍了阿里巴巴开源的流量治理组件Sentinel如何与Dubbo微服务框架集成,实现服务的高可用保障。主要内容包括: Sentinel核心功能:流量控制、熔断降级、系统负载保护和实时监控 Dubbo架构概述:服务提供者、消费者、注册中心和监控中心的基本组成 集成方案: 通过sentinel-dubbo-adapter实现调用链路注入 添加必要的Maven依赖和配置 服务提供者使用@SentinelResource注解实现熔断降级 实践价值:帮助开发

千淘万漉虽辛苦,吹尽狂沙始到金 2万+

自动驾驶仿真:python和carsim联合仿真案例

carsim内部有许多相关联合仿真的demo,simulink、labview等等都有涉及,这里简单介绍下python和carsim联合仿真的方法,虽然carsim官方有个Steer_Control.py相关的案例,但是感觉比较冗余,这里抽出重点部分和大家交流探讨下。提示:以下是本篇文章正文内容,下面案例可供参考1、这里关于solvers的细节其实都没说,因为里面确实也没什么内容好讲的,本质就是调用carsim.dll,如果你需要更多的函数解析其实可以看vs_api.h,路径在,具体内容如下图所示。

zataji的博客 9062

单目视觉定位:深入解析PNP算法在机器人导航中的实战应用

本文深入解析了PNP算法在机器人单目视觉定位中的核心原理与实战应用。通过拆解坐标变换四步,详细阐述了如何从二维图像点反求相机三维位姿。文章结合OpenCV的solvePnP函数,提供了算法选择、参数调优及结果后处理的实用指南,并针对机器人导航中的特征匹配、尺度不确定性等挑战,给出了鲁棒性优化与实时性平衡的解决方案。

weixin_29083649的博客 419

Spring Boot Dubbo 入门

1. 概述 在 2019.05.21 号,在经历了 1 年多的孵化,Dubbo 终于迎来了 Apache 毕业。在这期间,Dubbo 做了比较多的功能迭代,提供了 NodeJS、Python、Go 等语言的支持,也举办了多次社区活动,在网上的“骂声”也少了。 艿艿:事实上,大多数成熟的开源项目,都是 KPI 驱动,又或者背后有商业化支撑。 作为一个长期使用,并且坚持使用 Dubbo 的开发者,还是比较愉快的。可能,又经历了一次技术正确的选择。当然,更愉快的是,Spring Cloud Alibab

weixin_42073629的博客 1058

SAP EWM 内向交货流程(一)

本文介绍了SAP EWM系统中内向交货单的基本配置与操作流程。

huqinghai的专栏 2398

MateCloud服务熔断:从Hystrix到Sentinel的实战演进指南

在微服务架构中,服务熔断是保障系统稳定性的关键技术。MateCloud作为一款基于Spring Cloud Alibaba的微服务架构,在服务熔断方面经历了从传统Hystrix到现代Sentinel的演进过程。本文将深入探讨这两种熔断方案的对比,并展示如何在MateCloud项目中实战应用Sentinel服务熔断。 ## 🔥 MateCloud微服务架构概览 MateCloud是一款**AI

gitblog_00626的博客 711

MATLAB 开发项目实例 1000 例专栏之第37例:基于Simulink的无人机飞行控制系统设计

通过上述项目实例,我们成功地使用Simulink设计和仿真了一个无人机的飞行控制系统。这个项目展示了从无人机建模、路径规划、控制器设计到仿真验证的完整流程。Simulink的可视化和模块化特性使得系统的建模和仿真更加直观和高效。

xiaoheshang_123的博客 163

C#实战大华智能安防系统:从SDK封装到视频流与智能报警的全链路落地

本文将介绍C#集成大华SDK开发安防系统的实战经验,重点解决多路视频预览卡顿和报警延迟问题。文章通过三层架构设计(SDK封装层、业务服务层、应用层)实现解耦,详细分析线程安全封装、高效解码显示和智能事件联动等核心技术。针对16路摄像头同时预览时CPU占用率过高的问题,提出使用子码流拉流与异步解码的优化方案,并总结8个常见开发陷阱的解决方案。文中还提供了大华智能安防系统的完整架构图,展示从设备层到应用层的交互流程,特别强调业务服务层中视频流服务和智能报警服务的核心功能设计。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 432

Spring Boot、Spring Cloud、Dubbo、Spring Cloud Alibaba 到底什么关系?一文讲透

Spring Boot 是什么定义:Spring Boot 是用于快速构建、配置、运行 Spring 应用的框架(更准确说是“开发与运行的脚手架 + 约定配置”)。解决的问题:减少繁琐配置与依赖管理,让一个 Web/服务应用能快速启动、快速开发、易部署。通俗理解:以前搭 Spring 像手动装电脑:配依赖、配容器、配一堆配置;Boot 像买整机:常用配置给你默认配好,你直接写业务。一句话理解:把“写一个能跑的 Spring 应用”变得很简单。Spring Cloud 是什么定义。

我是小bā吖的博客 759

第二十一:Python接口自动化-处理Json数据

【代码】第二十一:Python接口自动化-处理Json数据。

欢迎来到本博客! 111

springCloud 整合 Sentinel Nacos RocketMQ Dubbo Seata 如何选择对应的版本

前段时间搭建了一个业务网关,其中限流用到的是阿里的sentinel。 因为选择版本的问题,踩了很大的坑。说实话sentinel的官网真的写的不详细。根本没有提到版本的问题,就跟你引入对应的最新版本的就可以使用。 springboot和sentinel就有很大的兼容性问题,我在创建项目的时候,发现springboot必须选择 2.30以下,并且大于2.2.0,才能选择引入sentinel。版本不匹配报错还好发现,但是不报错,组件不生效,是真的很难受的。 举一反三,我们在使用任何技术的时候,都不能忽略..

Angus 2139

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Spring Boot 服务容错 Sentinel 从入门到精通

摘要:本文详细介绍了SpringBoot集成Sentinel微服务流量控制组件的完整方案。主要内容包括:1)Sentinel核心概念与特性;2)SpringBoot集成配置及基础功能实现;3)流量控制、熔断降级等核心功能详解;4)与Feign、RestTemplate的高级集成;5)规则持久化方案;6)监控与Dashboard配置;7)生产环境最佳实践与性能优化建议。文章通过丰富代码示例,全面展示了Sentinel在微服务架构中的流量防护能力,为构建高可用系统提供实用指导。

油墨香^-^的博客 838

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

【Spring Cloud Alibaba】Nacos(一)

在Spring Cloud Alibaba套件中服务治理方面的核心组件是Nacos(Dynamic Naming and Configuration Service),在Spring Cloud Alibaba套件为基础的微服务中,Nacos担当的角色有服务发现和健康检测、服务注册中心(动态注册)、配置中心(动态配置)、服务元数据管理等功能。做为注册中心:Nacos管理所有的微服务,解决微服务调用之间的错综复杂、难以维护的问题。

Blue_Pepsi_Cola的博客 1696

Spring Cloud Alibaba 最佳实践:基于 Spring Boot 4.0 的完整微服务示例项目

使用 BOM 统一管理版本,避免冲突:</</</</</</</</</</</</</</它不是简单的"Hello World",而是真正考虑了生产环境的复杂性。从链路追踪到限流熔断,从响应式编程到消息驱动,每一个模块都经过精心设计,每一行配置都有实际意义。🎯 学习 Spring Cloud Alibaba🚀 准备升级 Spring Boot 4.0🏗️ 设计微服务架构🔍 寻找生产级参考示例那么这个项目绝对值得你 Star ⭐ 和深入研究!立即体验。

武汉红喜 347

Spring Cloud Alibaba、Spring Boot、Spring Cloud 版本对应关系全解析

在微服务架构中,依赖版本管理是确保系统稳定运行的基础。Spring Cloud Alibaba、Spring Boot 和 Spring Cloud 三者版本对应关系复杂,涉及不同的命名规则和演进逻辑。理解这些版本线的兼容性原理,能有效避免因版本错配导致的运行时错误,提升工程实践的效率。典型应用场景包括新项目搭建、版本升级迁移以及多模块项目的统一管理。通过 BOM 管理版本、遵循选型决策树,可以显著降低维护成本。本文深入拆解版本对应关系,提供实用指南。

weixin_33762130的博客 722

SpringCloud-Sentinel实现服务限流、熔断、降级,整合Nacos实现持久化

文章目录Sentinel简介一、安装Sentinel控制台二、创建springboot-sentinel模块三、限流功能四、创建RateLimitController类1.根据资源名称限流2.根据URL限流3.自定义限流处理逻辑五、熔断功能六、与Feign结合使用七、使用Nacos存储规则1. 原理示意图2. 功能演示3. Sentinel Dashboard集成Nacos实现规则同步4. Sentinel Dashboard源码修改 Sentinel简介 随着微服务的流行,服务与服务之间的稳定性变得越来越

BlackMoon 1183

Dubbo微服务入门指南:Spring Boot Starter如何让你告别XML配置地狱

Apache Dubbo 是 Java 生态中最主流的 **RPC 与微服务框架**,而 `dubbo-spring-boot-starter` 是它接入 Spring Boot 的“黄金搭档”。本指南面向新手,用最短的路径带你完成:引入 Starter → 注解式发布/引用服务 → 外部化配置 → 生产级健康检查,全程 **零 XML、零样板代码**。 ## 为什么 Spring Boot S

gitblog_01050的博客 666
上一篇: SELECT COUNT(*) 底层究竟干了啥么?
下一篇: Hutool,一个贼好用的 Java 工具类库,用过都说好~
公众号-芋道源码
博客等级 码龄10年 1631粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值