ApacheDubbo


一、什么是ApacheDubbo

Apache Dubbo (incubating) |?d?b??| 是一款高性能、轻量级的开源 Java RPC 分布式服务框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。她最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度来看,Dubbo 采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务,所以基于这一点可以抽象出服务提供方(Provider)和服务消费方(Consumer)两个角色。

二、架构

img

节点角色说明

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

调用关系说明

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

三、Dubbo 功能特点

  • 面向接口代理的高性能 RPC 调用: 提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节。
  • 智能负载均衡: 内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量。
  • 服务自动注册与发现: 支持多种注册中心服务,服务实例上下线实时感知。
  • 高度可扩展能力: 遵循微内核 + 插件的设计原则,所有核心能力如 Protocol、Transport、Serialization 被设计为扩展点,平等对待内置实现和第三方实现。
  • 运行期流量调度: 内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布,同机房优先等功能。
  • 可视化的服务治理与运维: 提供丰富服务治理、运维工具:随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数。

四、创建工程

4.1 父工程

父工程ApacheDubbo的pom.xml

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

    4.0.0

    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.6.RELEASE
        
    

    com.qiang
    ApacheDubbo
    1.0.0-SNAPSHOT
    pom

    
        apache-dubbo-dependencies
        apache-dubbo-provider
        apache-dubbo-consumer
    

    
        1.8
        ${java.version}
        ${java.version}
        UTF-8
        UTF-8
    

    
        
            Apache 2.0
            https://www.apache.org/licenses/LICENSE-2.0.txt
        
    


    
        
            
                com.qiang
                apache-dubbo-dependencies
                ${project.version}
                pom
                import
            
        
    

    
        
            default
            
                true
            
            
                0.0.12
            
            
                
                    
                        io.spring.javaformat
                        spring-javaformat-maven-plugin
                        ${spring-javaformat.version}
                    
                    
                        org.apache.maven.plugins
                        maven-surefire-plugin
                        
                            
                                **/*Tests.java
                            
                            
                                **/Abstract*.java
                            
                            
                                file:/dev/./urandom
                                true
                            
                        
                    
                    
                        org.apache.maven.plugins
                        maven-enforcer-plugin
                        
                            
                                enforce-rules
                                
                                    enforce
                                
                                
                                    
                                        
                                            
                                                commons-logging:*:*
                                            
                                            true
                                        
                                    
                                    true
                                
                            
                        
                    
                    
                        org.apache.maven.plugins
                        maven-install-plugin
                        
                            true
                        
                    
                    
                        org.apache.maven.plugins
                        maven-javadoc-plugin
                        
                            true
                        
                        true
                    
                
            
        
    

    
        
            spring-milestone
            Spring Milestone
            https://repo.spring.io/milestone
            
                false
            
        
        
            spring-snapshot
            Spring Snapshot
            https://repo.spring.io/snapshot
            
                true
            
        
    

    
        
            spring-milestone
            Spring Milestone
            https://repo.spring.io/milestone
            
                false
            
        
        
            spring-snapshot
            Spring Snapshot
            https://repo.spring.io/snapshot
            
                true
            
        
    

4.2 统一依赖管理

统一依赖管理apache-dubbo-dependencies

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

    4.0.0

    com.qiang
    apache-dubbo-dependencies
    1.0.0-SNAPSHOT
    pom

    
        2.7.3
        2.7.1
        Greenwich.SR2
        2.1.0.RELEASE
        1.0.2
        15.0
    

    
        
            Apache 2.0
            https://www.apache.org/licenses/LICENSE-2.0.txt
        
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
            
                com.alibaba.cloud
                spring-cloud-alibaba-dependencies
                ${spring-cloud-alibaba.verion}
                pom
                import
            

            
                org.apache.dubbo
                dubbo
                ${dubbo.version}
                
                    
                        org.springframework
                        spring
                    
                    
                        javax.servlet
                        servlet-api
                    
                    
                        log4j
                        log4j
                    
                
            
            
                org.apache.dubbo
                dubbo-spring-boot-actuator
                ${dubbo-actuator.version}
            
            
                com.alibaba.spring
                spring-context-support
                ${alibaba-spring-context-support.version}
            
            
                com.google.guava
                guava
                ${guava.version}
            
        
    

    
        
            spring-milestone
            Spring Milestone
            https://repo.spring.io/milestone
            
                false
            
        
        
            spring-snapshot
            Spring Snapshot
            https://repo.spring.io/snapshot
            
                true
            
        
    

    
        
            spring-milestone
            Spring Milestone
            https://repo.spring.io/milestone
            
                false
            
        
        
            spring-snapshot
            Spring Snapshot
            https://repo.spring.io/snapshot
            
                true
            
        
    


4.3 服务提供者

由于我们已经有了 Nacos 注册中心,Sentinel 熔断限流控制中心,所以我们不再使用 Zookeeper 和 Dubbo Admin 来管理我们的 Dubbo 应用程序,Dubbo 仅当作我们微服务中的 RPC 通信框架,真正实现对内 RPC,对外 REST。

服务提供者apache-dubbo-provider

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

    4.0.0

    
        com.qiang
        ApacheDubbo
        1.0.0-SNAPSHOT
    

    apache-dubbo-provider
    pom

    
        
            Apache 2.0
            https://www.apache.org/licenses/LICENSE-2.0.txt
        
    


    
        
        apache-dubbo-provider-api
        
        apache-dubbo-provider-service
    

创建服务提供者接口模块apache-dubbo-provider-api

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

    4.0.0

    
        com.qiang
        apache-dubbo-provider
        1.0.0-SNAPSHOT
    

    apache-dubbo-provider-api
    jar

    
        
            Apache 2.0
            https://www.apache.org/licenses/LICENSE-2.0.txt
        
    


定义接口

package com.qiang.apache.dubbo.provider.api;

/**
 * @author: 小强崽
 * @create: 2020-08-29 07:07
 * @description: 定义接口
 */
public interface EchoService {
    /**
     * 输出字符串
     * @param string
     * @return
     */
    String echo(String string);
}

创建服务提供者接口实现apache-dubbo-provider-service

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

    4.0.0

    
        com.qiang
        apache-dubbo-provider
        1.0.0-SNAPSHOT
    

    apache-dubbo-provider-service
    jar

    
        
            Apache 2.0
            https://www.apache.org/licenses/LICENSE-2.0.txt
        
    


    

        
            org.springframework.boot
            spring-boot-starter
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.apache.dubbo
            dubbo-spring-boot-starter
        

        
            org.apache.dubbo
            dubbo
        
        
            org.apache.dubbo
            dubbo-registry-nacos
        
        
            com.alibaba.nacos
            nacos-client
        
        
            com.alibaba.spring
            spring-context-support
        

        
            com.qiang
            apache-dubbo-provider-api
            ${project.parent.version}
        

        
            com.alibaba.csp
            sentinel-apache-dubbo-adapter
        
        
            com.alibaba.csp
            sentinel-transport-simple-http
        
        
            com.alibaba.csp
            sentinel-annotation-aspectj
        
        
        
            com.alibaba.csp
            sentinel-datasource-nacos
        
        
            com.google.guava
            guava
            compile
        
        
        
            org.springframework.cloud
            spring-cloud-starter-alibaba-sentinel
            RELEASE
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    com.qiang.apache.dubbo.provider.ProviderApplication
                
            
        
    

application.yml

主要增加了 Dubbo 包扫描路径和 Nacos Server 配置。

spring:
  application:
    name: dubbo-provider
  main:
    allow-bean-definition-overriding: true


dubbo:
  scan:
    base-packages: com.qiang.apache.dubbo.provider.service
  protocol:
    name: dubbo
    port: 20880
  registry:
    address: nacos://172.25.0.12:8848
  provider:
    loadbalance: roundrobin

Application

package com.qiang.apache.dubbo.provider;

import com.qiang.apache.dubbo.provider.configuration.DubboSentinelConfiguration;
import com.qiang.apache.dubbo.provider.service.fallback.EchoServiceFallback;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author: 小强崽
 * @create: 2020-08-29 07:16
 * @description:
 */

@SpringBootApplication(scanBasePackageClasses = {EchoServiceFallback.class, DubboSentinelConfiguration.class})
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

Service

package com.qiang.apache.dubbo.provider.service;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.qiang.apache.dubbo.provider.api.EchoService;
import com.qiang.apache.dubbo.provider.service.fallback.EchoServiceFallback;
import org.apache.commons.io.FileExistsException;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Value;

/**
 * @author: 小强崽
 * @create: 2020-08-29 07:18
 * @description:
 */
@Service(version = "1.0.0")
public class EchoServiceImpl implements EchoService {

    @Value("${dubbo.protocol.port}")
    private String port;

    @Override
    @SentinelResource(value = "getByUsername", fallback = "getEchoServiceFallback", fallbackClass = EchoServiceFallback.class)
    public String echo(String string) {
        if ("hello".equals(string)){
            throw new IllegalArgumentException("invalid arg");
        }
        return "Echo Hello Dubbo " + string + " i am from port: " + port;
    }
}

验证是否成功

访问http://172.25.0.12:8848/nacos

image-20200831035406723

4.4 服务消费者

apache-dubbo-consumer

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

    4.0.0

    
        com.qiang
        ApacheDubbo
        1.0.0-SNAPSHOT
    

    apache-dubbo-consumer
    jar

    
        
            Apache 2.0
            https://www.apache.org/licenses/LICENSE-2.0.txt
        
    


    

        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-actuator
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.apache.dubbo
            dubbo-spring-boot-starter
        
        
            org.apache.dubbo
            dubbo-spring-boot-actuator
        

        
            org.apache.dubbo
            dubbo
        
        
            com.alibaba
            dubbo-registry-nacos
            2.7.7
        
        
            com.alibaba.nacos
            nacos-client
        
        
            com.alibaba.spring
            spring-context-support
        

        
            com.qiang
            apache-dubbo-provider-api
            ${project.parent.version}
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    com.qiang.apache.dubbo.consumer.ConsumerApplication
                
            
        
    

application.yml

主要增加了 Dubbo 包扫描路径、健康检查以及 Nacos Server 配置。


spring:
  application:
    name: dubbo-consumer
  main:
    allow-bean-definition-overriding: true

dubbo:
  scan:
    base-packages: com.qiang.apache.dubbo.consumer.controller
  protocol:
    name: dubbo
    port: -1
  registry:
    address: nacos://172.25.0.12:8848

server:
  port: 8080

endpoints:
  dubbo:
    enabled: true
management:
  health:
    dubbo:
      status:
        defaults: memory
        extras: threadpool
  endpoints:
    web:
      exposure:
        include: "*"

Application

package com.qiang.apache.dubbo.consumer;

/**
 * @author: 小强崽
 * @create: 2020-08-30 01:21
 * @description:
 */
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

Controller

通过 org.apache.dubbo 包下的 @Reference 注解像调用本地服务一样调用远程服务,轻松实现透明的远程过程调用。

package com.qiang.apache.dubbo.consumer.controller;

import com.qiang.apache.dubbo.provider.api.EchoService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: 小强崽
 * @create: 2020-08-30 01:23
 * @description:
 */
@RestController
public class EchoController {

    @Reference(version = "1.0.0")
    private EchoService echoService;

    @GetMapping(value = "/echo/{string}")
    public String echo(@PathVariable String string) {
        return echoService.echo(string);
    }
}

验证是否成功

访问http://172.25.0.12:8848/nacos

image-20200831035801190

http://localhost:8080/echo/hi

Echo Hello Dubbo hi i am from port: 20880

服务端点检查

http://localhost:8080/actuator/health

{
    "status": "UP"
}

五、负载均衡

修改 dubbo-provider 项目的负载均衡策略,默认的负载均衡策略是 随机,我们修改为 轮循,可配置的值分别是:randomroundrobinleastactiveconsistenthash

dubbo:
  provider:
    loadbalance: roundrobin

修改 dubbo-provider 的协议端口为 20880 和 20881,并启动多个实例,IDEA 中依次点击 Run -> Edit Configurations 并勾选 Allow parallel run 以允许 IDEA 多实例运行项目。

image-20200831040120723

Nacos Server 控制台可以看到 dubbo-provider 有 2 个实例。

image-20200831040327172

修改 dubbo-provider 项目的 EchoServiceImpl 中的测试方法。

package com.qiang.apache.dubbo.provider.service;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.qiang.apache.dubbo.provider.api.EchoService;
import com.qiang.apache.dubbo.provider.service.fallback.EchoServiceFallback;
import org.apache.commons.io.FileExistsException;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Value;

/**
 * @author: 小强崽
 * @create: 2020-08-29 07:18
 * @description:
 */
@Service(version = "1.0.0")
public class EchoServiceImpl implements EchoService {

    @Value("${dubbo.protocol.port}")
    private String port;

    @Override
    @SentinelResource(value = "getByUsername", fallback = "getEchoServiceFallback", fallbackClass = EchoServiceFallback.class)
    public String echo(String string) {
        if ("hello".equals(string)){
            throw new IllegalArgumentException("invalid arg");
        }
        return "Echo Hello Dubbo " + string + " i am from port: " + port;
    }
}

重启服务,通过浏览器访问 http://localhost:8080/echo/hi ,反复刷新浏览器,浏览器交替显示。

image-20200831040432919

image-20200831040446032

六、外部化配置

我们以 dubbo-consumer 项目为例,修改 pom.xml ,引入 Nacos Config Starter。


    com.alibaba.cloud
    spring-cloud-starter-alibaba-nacos-config

Controller

完成上述两步后,应用会从 Nacos Config 中获取相应的配置,并添加在 Spring Environment 的 PropertySources 中。这里我们使用 @Value 注解来将对应的配置注入到 EchoControllerusername字段,并添加 @RefreshScope 打开动态刷新功能。

package com.qiang.apache.dubbo.consumer.controller;

import com.qiang.apache.dubbo.provider.api.EchoService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: 小强崽
 * @create: 2020-08-30 01:23
 * @description:
 */
@RefreshScope
@RestController
public class EchoController {

    @Reference(version = "1.0.0")
    private EchoService echoService;

    @GetMapping(value = "/echo/{string}")
    public String echo(@PathVariable String string) {
        return echoService.echo(string);
    }
}

使用控制台发布配置。

spring:
  application:
    name: dubbo-consumer
  main:
    allow-bean-definition-overriding: true

dubbo:
  scan:
    base-packages: com.qiang.apache.dubbo.consumer.controller
  protocol:
    name: dubbo
    port: -1
    # 高速序列化
    serialization: kryo
  registry:
    address: nacos://172.25.0.12:8848
server:
  port: 8080

endpoints:
  dubbo:
    enabled: true
management:
  health:
    dubbo:
      status:
        defaults: memory
        extras: threadpool
  endpoints:
    web:
      exposure:
        include: "*"

user:
  name: "傻狗"

修改客户端配置

创建名为 bootstrap.properties 的配置文件并删除之前创建的 application.yml 配置文件。

spring.application.name=dubbo-consumer-config
spring.cloud.nacos.config.server-addr=172.25.0.12:8848
spring.cloud.nacos.config.file-extension=yaml

通过浏览器访问 http://localhost:8080/echo/hi ,浏览器输出如下。

Echo Hello Dubbo hi i am from port: -1 傻狗

动态刷新配置

在 Nacos Server 控制台修改配置文件,将 user.name 属性修改为 大傻狗,此时观察控制台日志,你会发现我们已经成功刷新了配置。

验证是否成功
通过浏览器访问 http://localhost:8080/echo/hi ,浏览器输出如下。

Echo Hello Dubbo hi i am from port: -1 大傻狗

可以使用 spring.cloud.nacos.config.refresh.enabled=false 来关闭动态刷新。

七、服务限流Sentinel

7.1 部署

docker-compose.yaml

version: '3.0'
services:
  sentinel:
    image: 172.25.0.15/xiaoqiangzai/bladex/sentinel-dashboard:latest
    container_name: sentinel
    network_mode: "host"
    restart: always
    privileged: true
    ports:
      - '8858:8858'
    environment:
      - auth.username=sentinel
      - auth.password=sentinel

jar包

https://github.com/alibaba/Sentinel/releases

启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

其中 -Dserver.port=8080 用于指定 Sentinel 控制台端口为 8080,从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的 登录 功能,默认用户名和密码都是 sentinel

  • -Dsentinel.dashboard.auth.username=sentinel 用于指定控制台的登录用户名为 sentinel。
  • -Dsentinel.dashboard.auth.password=123456 用于指定控制台的登录密码为 123456;如果省略这两个参数,默认用户和密码均为 sentinel。
  • -Dserver.servlet.session.timeout=7200 用于指定 Spring Boot 服务端 session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟。

7.2 Dubbo 集成 Sentinel

使用时需要引入 3 个依赖。


    com.alibaba.csp
    sentinel-apache-dubbo-adapter


    com.alibaba.csp
    sentinel-transport-simple-http


    com.alibaba.csp
    sentinel-annotation-aspectj

依赖说明

  • sentinel-apache-dubbo-adapter:Sentinel 提供的 Apache Dubbo 适配模块 (注意:sentinel-dubbo-adapter 是未毕业版本的 Dubbo 适配模块)。
  • sentinel-transport-simple-http: 用于暴露一个特定的端口,Sentinel Dashboard 通过 HTTP 的形式进行数据推送,客户端接收后将规则保存在本地内存中。
  • sentinel-annotation-aspectj:Sentinel 提供了 @SentinelResource 注解用于定义资源,并提供了 AspectJ 的扩展用于自动定义资源、处理 BlockException 等。

7.3 配置启动参数

在启动时,需要在 JVM 中添加以下启动参数。

-Djava.net.preferIPv4Stack=true
-Dcsp.sentinel.api.port=8720
-Dproject.name=dubbo-provider
-Dcsp.sentinel.dashboard.server=172.25.0.12:8858

image-20200831041920478

参数说明

-Dcsp.sentinel.api.port=客户端端口,用于上报信息,默认 8720 即可,Sentinel 发现端口冲突会自动递增
-Dproject.name=显示在控制台上的应用名称
-Dcsp.sentinel.dashboard.server=控制台地址

7.4 Dubbo 配置 Sentinel

我们需要使用 Spring AOP 的方式显示的注册 SentinelResourceAspect 为一个 Bean。

package com.qiang.apache.dubbo.provider.configuration;

import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author: 小强崽
 * @create: 2020-08-30 02:29
 * @description: 仅 Dubbo 服务需要该配置,Spring Cloud Alibaba 无需加载该配置
 */
@Configuration
public class DubboSentinelConfiguration {
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}

加载配置

配置单独封装至 configuration-sentinel 项目中,方便其它 Dubbo 服务依赖,所有 Dubbo 服务提供者都需要调用该模块并指定加载配置类,修改 Application 代码如下。

package com.qiang.apache.dubbo.provider;

import com.qiang.apache.dubbo.provider.configuration.DubboSentinelConfiguration;
import com.qiang.apache.dubbo.provider.service.fallback.EchoServiceFallback;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author: 小强崽
 * @create: 2020-08-29 07:16
 * @description:
 */

@SpringBootApplication(scanBasePackageClasses = {EchoServiceFallback.class, DubboSentinelConfiguration.class})
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

7.5 配置熔断类

可以为需要熔断降级和限流的方法创建一个专门的熔断类,在需要使用熔断和限流的方法上使用 @SentinelResource 注解指定该类中的方法即可实现熔断降级功能。

注意: 熔断方法必须是 static 函数。

package com.qiang.apache.dubbo.provider.service.fallback;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author: 小强崽
 * @create: 2020-08-30 02:32
 * @description: 服务熔断后处理的方法
 */
public class EchoServiceFallback {
    private static final Logger logger = LoggerFactory.getLogger(EchoServiceFallback.class);
    public static String getEchoServiceFallback(String username, Throwable ex) {
        logger.warn("Invoke getEchoServiceFallback: " + ex.getClass().getTypeName());
        ex.printStackTrace();
        return "服务熔断";
    }

}

7.6 配置熔断方法

package com.qiang.apache.dubbo.provider.service;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.qiang.apache.dubbo.provider.api.EchoService;
import com.qiang.apache.dubbo.provider.service.fallback.EchoServiceFallback;
import org.apache.commons.io.FileExistsException;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Value;

/**
 * @author: 小强崽
 * @create: 2020-08-29 07:18
 * @description: 熔断器的使用
 * {@link SentinelResource#value()} 对应的是 Sentinel 控制台中的资源,可用作控制台设置【流控】和【降级】操作。
 * {@link SentinelResource#fallback()} 对应的是 {@link EchoServiceFallback#getEchoServiceFallback(String, Throwable)},并且必须为 `static`。
 * 如果不设置 {@link SentinelResource#fallbackClass()},则需要在当前类中创建一个 `Fallback` 函数,函数签名与原函数一致或加一个 {@link Throwable} 类型的参数。
 */
@Service(version = "1.0.0")
public class EchoServiceImpl implements EchoService {

    @Value("${dubbo.protocol.port}")
    private String port;

    @Override
    @SentinelResource(value = "getByUsername", fallback = "getEchoServiceFallback", fallbackClass = EchoServiceFallback.class)
    public String echo(String string) {
        if ("hello".equals(string)){
            throw new IllegalArgumentException("invalid arg");
        }
        return "Echo Hello Dubbo " + string + " i am from port: " + port;
    }
}

7.7 测试熔断限流

7.7.1 配置流控规则

在 Sentinel 控制台配置流控规则,需要至少调用一次服务,才能在控制台看到监控效果。

image-20200831042603440

流控规则说明

  • 资源名: 对应 @SentinelResource 注解中的 value 属性。
  • QPS:平均每秒的请求响应数。
  • 单机阈值: 为方便测试这里设置为 1,就是每秒钟中只允许一个响应请求,如果设置为 5,则代表请求每 200 ms 才能通过一个,多出的请求将排队等待通过。超时时间代表最大排队时间,超出最大排队时间的请求将会直接被拒绝。

排队等待模式下 QPS 不要超过 1000(请求间隔 1 ms)。

7.8 测试熔断效果

在出现异常时会自动启动熔断,增加测试代码如下。

package com.qiang.apache.dubbo.provider.service;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.qiang.apache.dubbo.provider.api.EchoService;
import com.qiang.apache.dubbo.provider.service.fallback.EchoServiceFallback;
import org.apache.commons.io.FileExistsException;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Value;

/**
 * @author: 小强崽
 * @create: 2020-08-29 07:18
 * @description:
 */
@Service(version = "1.0.0")
public class EchoServiceImpl implements EchoService {

    @Value("${dubbo.protocol.port}")
    private String port;

    @Override
    @SentinelResource(value = "getByUsername", fallback = "getEchoServiceFallback", fallbackClass = EchoServiceFallback.class)
    public String echo(String string) {
        if ("hello".equals(string)){
            throw new IllegalArgumentException("invalid arg");
        }
        return "Echo Hello Dubbo " + string + " i am from port: " + port;
    }
}

触发异常时,控制台 (dubbo-provider) 打印日志如下。

java.lang.IllegalArgumentException: invalid arg
	at com.qiang.apache.dubbo.provider.service.EchoServiceImpl.echo(EchoServiceImpl.java:25)
	at com.qiang.apache.dubbo.provider.service.EchoServiceImpl$$FastClassBySpringCGLIB$$9193b731.invoke()
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
	at com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect.invokeResourceWithSentinel(SentinelResourceAspect.java:57)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
	at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
	at com.qiang.apache.dubbo.provider.service.EchoServiceImpl$$EnhancerBySpringCGLIB$$555ba878.echo()
	at org.apache.dubbo.common.bytecode.Wrapper0.invokeMethod(Wrapper0.java)
	at org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:47)
	at org.apache.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:84)
	at org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:56)
	at org.apache.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56)
	at org.apache.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:55)
	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
	at com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboProviderFilter.invoke(SentinelDubboProviderFilter.java:71)
	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
	at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:92)
	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
	at org.apache.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:48)
	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
	at org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:81)
	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
	at org.apache.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:96)
	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
	at org.apache.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:148)
	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
	at org.apache.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38)
	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
	at org.apache.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:41)
	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$CallbackRegistrationInvoker.invoke(ProtocolFilterWrapper.java:157)
	at org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:152)
	at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:102)
	at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:193)
	at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)
	at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

image-20200831043032420

限流效果

com.alibaba.csp.sentinel.slots.block.flow.FlowException
2020-08-31 04:30:01.853  WARN 10932 --- [:20881-thread-7] c.q.a.d.p.s.f.EchoServiceFallback        : Invoke getEchoServiceFallback: com.alibaba.csp.sentinel.slots.block.flow.FlowException
2020-08-31 04:30:46.078  WARN 10932 --- [:20881-thread-8] c.q.a.d.p.s.f.EchoServiceFallback        : Invoke getEchoServiceFallback: java.lang.IllegalArgumentException

image-20200831043220431

八、扩展

8.1 什么是 RPC

分布式是促使 RPC 诞生的领域,RPC 是一种编程模型,并没有规定你具体要怎样实现,无论使用 HTTP 或是 RMI 都是可以的。

假设你有一个计算器接口,Calculator,以及它的实现类 CalculatorImpl,那么在系统还是 单体应用 时,你要调用 Calculator 的 add 方法来执行一个加运算,直接 new 一个 CalculatorImpl,然后调用 add 方法就行了,这其实就是非常普通的 本地函数调用,因为在 同一个地址空间,或者说在同一块内存,所以通过方法栈和参数栈就可以实现。

img

现在,基于高性能和高可靠等因素的考虑,你决定将系统改造为分布式应用,将很多可以共享的功能都单独拎出来,比如上面说到的计算器,你单独把它放到一个服务里头,让别的服务去调用它。

img

这下问题来了,服务 A 里头并没有 CalculatorImpl 这个类,那它要怎样调用服务 B 的 CalculatorImpl 的 add 方法呢?

RPC 要解决的两个问题

  • 解决分布式系统中,服务之间的调用问题。
  • 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。

8.2 如何实现一个 RPC

实际情况下,RPC 很少用到 HTTP 协议来进行数据传输,毕竟我只是想传输一下数据而已,何必动用到一个文本传输的应用层协议呢,我为什么不直接使用二进制传输?比如直接用 Java 的 Socket 协议进行传输?

不管你用何种协议进行数据传输,一个完整的 RPC 过程,都可以用下面这张图来描述。

img

以左边的 Client 端为例,Application 就是 RPC 的调用方,Client Stub 就是我们上面说到的代理对象,也就是那个看起来像是 Calculator 的实现类,其实内部是通过 RPC 方式来进行远程调用的代理对象,至于 Client Run-time Library,则是实现远程调用的工具包,比如 JDK 的 Socket,最后通过底层网络实现实现数据的传输。

这个过程中最重要的就是 序列化反序列化 了,因为数据传输的数据包必须是二进制的,你直接丢一个 Java 对象过去,人家可不认识,你必须把 Java 对象序列化为二进制格式,传给 Server 端,Server 端接收到之后,再反序列化为 Java 对象。

8.3 RPC vs Restful

RPC 是面向过程Restful 是面向资源,并且使用了 HTTP 动词。从这个维度上看,Restful 风格的 URL 在表述的精简性、可读性上都要更好。

8.4 阿里为何放弃 Zookeeper

CAP

有个思考,从 CAP 角度考虑,服务注册中心是 CP 系统还是 AP 系统呢?

  • 服务注册中心是为了服务间调用服务的,那么绝对不允许因为服务注册中心出现了问题而导致服务间的调用出问题。
  • 假如有 node1,node2,node3 集群节点。保存着可用服务列表 ip1,ip2,ip3,试想如果此时不一致,比如 node1 只保存了ip1,ip2,此时服务读取 node1 的节点,那么会造成什么影响?

调用 node1 的服务,顶多就是负载均衡时不会有流量打到 ip3,然后等 node1 同步回 ip3 后,又一致了,这对服务其实没什么太大影响。所以,推测出服务注册中心应该是个 AP 系统。

Zookeeper 是个 CP 系统,强一致性

  • 场景1,当 master 挂了,此时 Zookeeper 集群需要重新选举,而此时服务需要来读取可用服务,是不可用的。影响到了服务的可用性当然你可以说服务本地有缓存可用列表。然而下面这种方式就更无法处理了。
  • 场景2,分区可用。试想,有 3 个机房,如果其中机房 3 和机房 1,2 网络断了,那么机房 3 的注册中心就不能注册新的机器了,这显然也不合理从健康检查角度来看。

img

Zookeeper 是通过 TCP 的心跳判断服务是否可用,但 TCP 的活性并不代表服务是可用的,如:连接池已满,DB 挂了等。

理想的注册中心

  • 服务自动注册发现。最好有新的服务注册上去时还能推送到调用端。
  • 能对注册上来的机器方便的进行管理,能手动删除(发送信号让服务优雅下线)、恢复机器。
  • 服务的健康检查,能真正的检测到服务是否可用。
  • 可以看到是否有其他调用服务正在订阅注册上来的服务。
  • 能够带上些除了 IP 外的其它信息。

九、常见错误

问题:

java.lang.NoClassDefFoundError: com/alibaba/nacos/client/naming/utils/StringUtils
	at org.apache.dubbo.registry.nacos.NacosRegistryFactory.putPropertyIfAbsent(NacosRegistryFactory.java:104) ~[dubbo-2.7.2.jar:2.7.2]
	at org.apache.dubbo.registry.nacos.NacosRegistryFactory.setProperties(NacosRegistryFactory.java:94) ~[dubbo-2.7.2.jar:2.7.2]
	at org.apache.dubbo.registry.nacos.NacosRegistryFactory.buildNacosProperties(NacosRegistryFactory.java:73) ~[dubbo-2.7.2.jar:2.7.2]

解决:

这里版本号nacos跟dubbo冲突,修改dubbo的版本即可
2.7.3

作者(Author):小强崽
来源(Source):https://www.wuduoqiang.com/archives/ApacheDubbo
协议(License):署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
版权(Copyright):商业转载请联系作者获得授权,非商业转载请注明出处。 For commercial use, please contact the author for authorization. For non-commercial use, please indicate the source.