1、Dubbo基础

烟雨 4年前 (2021-11-30) 阅读数 535 #Dubbo
文章标签 Dubbo

一、Dubbo基础架构

Dubbo⼀开始定位就是RPC,专注于两个服务之间的调⽤。
但随着微服务的盛⾏,除开服务调⽤之外,Dubbo也在逐步的涉猎服务治理、服务监控、服务⽹关等等,所以现在的Dubbo⽬标已经不⽌是RPC框架了,⽽是和Spring Cloud类似想成为了⼀个服务框架。

image.png

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

整个发布-订阅的过程简单描述如下:

  • 启动容器,加载,运行服务提供者。

  • 服务提供者在启动时,在注册中心发布注册自己提供的服务。

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

如果考虑失败或变更的情况,就需要考虑下面的过程:

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

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

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

1.1、开源RPC框架对比(仅为参考)

image.png

二、Dubbo基本应用

2.1、项目准备

image.png

dubbo父工程pom.xml

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

    <groupId>com.zender</groupId>
    <artifactId>dubbo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>provider</module>
        <module>interface</module>
        <module>consumer</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <spring-boot.version>2.3.1.RELEASE</spring-boot.version>
        <dubbo.version>2.7.5</dubbo.version>
    </properties>

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

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

            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo</artifactId>
                <version>${dubbo.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>javax.servlet</groupId>
                        <artifactId>servlet-api</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>log4j</groupId>
                        <artifactId>log4j</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

interface工程pom.xml

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

    <artifactId>interface</artifactId>
</project>

consumer工程pom.xml

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

    <artifactId>consumer</artifactId>

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

        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.5</version>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>

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

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

        <!-- Zookeeper dependencies -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-dependencies-zookeeper</artifactId>
            <version>${dubbo.version}</version>
            <type>pom</type>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-http</artifactId>
            <version>${dubbo.version}</version>
        </dependency>
    </dependencies>
</project>

配置文件application.yml

spring:
  application:
    name: dubbo-consumer-demo

server:
  port: 8082

dubbo:
  registry:
    address: zookeeper://127.0.0.1:2181

provider工程pom.xml

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

    <artifactId>provider</artifactId>

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

        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.5</version>
        </dependency>

        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jaxrs</artifactId>
            <version>3.0.19.Final</version>
        </dependency>

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.1.0.Final</version>
        </dependency>

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

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

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>

        <!-- Zookeeper dependencies -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-dependencies-zookeeper</artifactId>
            <version>${dubbo.version}</version>
            <type>pom</type>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-http</artifactId>
            <version>${dubbo.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-metadata-report-zookeeper</artifactId>
            <version>${dubbo.version}</version>
        </dependency>
    </dependencies>

</project>

配置文件application.properties

# Spring boot application
spring.application.name=dubbo-provider-demo
server.port=8081

# 扫描包路径
dubbo.scan.base-packages=com.zender.provider.service.impl
# 应用名称
dubbo.application.name=${spring.application.name}


## 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

# Dubbo协议端口号
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

2.2、DefaultDemo

interface工程添加接口:DemoService
public interface DemoService {
    String sayHello(String name);
}

provider工程(服务提供者),实现DemoService接口

package com.zender.provider.service.impl;

import com.zender.service.DemoService;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.rpc.RpcContext;

@Service(version = "default")
public class DefaultDemoServiceImpl implements DemoService {
    @Override
    public String sayHello(String name) {
        System.out.println("执行了服务:" + name);
        URL url = RpcContext.getContext().getUrl();
        return String.format("%s:%s, Hello, %s", url.getProtocol(), url.getPort(), name);
    }
}

启动类

//服务提供者
@SpringBootApplication
public class ProviderStart {
    public static void main(String[] args) {
        SpringApplication.run(ProviderStart.class, args);
    }
}

//服务消费者
@SpringBootApplication
public class ConsumerStart implements WebMvcConfigurer {

    @Reference(version = "default")
    private DemoService demoService;

    public static void main(String[] args) throws IOException {
        ConfigurableApplicationContext context = SpringApplication.run(ConsumerStart.class);
        DemoService demoService = context.getBean(DemoService.class);

        System.out.println((demoService.sayHello("Dubbo")));
    }
}

image.png

2.3、负载均衡

Dubbo内置了4种负载均衡策略,设置方式:
//定义服务的时候配置
@Service(version = "default", loadbalance = "roundrobin")
//消费者,这里会按消费者配置的为主(通常配置在消费端)
@Reference(version = "default", loadbalance = "roundrobin")
//或者
<dubbo:service interface="..." loadbalance="roundrobin"/>
//或者
<dubbo:reference interface="..." loadbalance="roundrobin"/>

随机负载均衡(Random LoadBalance)

随机负载均衡策略就是从多个Provider中随机选择一个。可通过权重对负载均衡产生影响。可以在Dubbo Admin中对Provider进行权重的设置。

轮询负载均衡(RoundRobin LoadBalance)

轮询负载均衡,就是依次的调用所有的Provider。和随机负载均衡策略一样,轮询负载均衡策略也有权重的概念。
轮询负载均衡算法可以让Dubbo调用严格按照我们设置的比例来分配。不管是少量的调用还是大量的调用。
但是轮询负载均衡算法也有不足的地方,存在慢的Provider累积请求的问题。
比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上,导致整个系统变慢。

最少活跃调用数负载均衡(LeastActive LoadBalance)

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差,使慢的机器收到更少。目的是让更慢的机器收到更少的请求。
最少活跃数的统计是放在消费者这边的。大致流程如下:
  1. 消费者会缓存所调⽤服务的所有提供者,⽐如记为p1、p2、p3三个服务提供者,每个提供者内都个属性记为active,默认位0。

  2. 消费者在调⽤次服务时,如果负载均衡策略是leastactive。

  3. 消费者端会判断缓存的所有服务提供者的active,选择最⼩的,如果都相同,则随机。

  4. 选出某⼀个服务提供者后,假设位p2,Dubbo就会对p2.active+1。

  5. 然后真正发出请求调⽤该服务,消费端收到响应结果后,对p2.active-1。

  6. 这样就完成了对某个服务提供者当前活跃调⽤数进⾏了统计并且并不影响服务调⽤的。

一致性Hash算法(ConsistentHash LoadBalance)

使用一致性Hash算法,让相同参数的请求总是发到同一Provider。 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
致性Hash负载均衡涉及到两个主要的配置:
hash.arguments : 当进行调用时候,根据调用方法的哪几个参数生成key,并根据key来通过一致性hash算法来选择调用结点。例如调用方法invoke(String s1,String s2); 若hash.arguments为1(默认值),则仅取invoke的参数1(s1)来生成hashCode。
hash.nodes: 为结点的副本数。
<dubbo:parameter key="hash.arguments" value="0,1" />
<dubbo:parameter key="hash.nodes" value="320" />

2.4、服务超时

在服务提供者和服务消费者上都可以配置服务超时时间,这两者是不⼀样的。消费者调⽤⼀个服务,分为三步:
  1. 消费者发送请求(⽹络传输)。

  2. 服务端执⾏服务。

  3. 服务端返回响应(⽹络传输)。

如果在服务端和消费端只在其中⼀⽅配置了timeout(超时),那么没有歧义,表示消费端调⽤服务的超时时间,消费端如果超过时间还没有收到响应结果,则消费端会抛超时异常,但是,服务端不会抛异常,服务端在执⾏服务后,会检查执⾏该服务的时间,如果超过timeout(超时),则会打印⼀个超时⽇志。服务会正常的执⾏完成。
如果在服务端和消费端各配了⼀个timeout(超时),那就⽐较复杂了,
假设
  1. 服务执⾏为5s。

  2. 消费端timeout=3s。

  3. 服务端timeout=6s。

那么消费端调⽤服务时,消费端会收到超时异常(因为消费端超时了,会打印⼀个超时⽇志),服务端⼀切正常(服务端没有超时)。
@Reference(version = "timeout", timeout = 3000)
//或者
@Service(version = "timeout", timeout = 4000)

2.4、集群容错

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

image.png

各节点关系:

  • 这里的 Invoker 是 Provider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息。

  • Directory 代表多个 Invoker,可以把它看成 List<Invoker> ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更。

  • Cluster 将 Directory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个。

  • Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等。

  • LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选。

Failover Cluster

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

重试次数配置如下:

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

Failfast Cluster

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

Failsafe Cluster

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

Failback Cluster

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

Forking Cluster

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

Broadcast Cluster

广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
现在广播调用中,可以通过 broadcast.fail.percent 配置节点调用失败的比例,当达到这个比例后,BroadcastClusterInvoker 将不再调用其他节点,直接抛出异常。 broadcast.fail.percent 取值在 0~100 范围内。默认情况下当全部调用失败后,才会抛出异常。 broadcast.fail.percent 只是控制的当失败后是否继续调用其他节点,并不改变结果(任意一台报错则报错)。broadcast.fail.percent 参数 在 dubbo2.7.10 及以上版本生效。
Broadcast Cluster 配置 broadcast.fail.percent。
broadcast.fail.percent=20 代表了当 20% 的节点调用失败就抛出异常,不再调用其他节点。
@reference(cluster = "broadcast", parameters = {"broadcast.fail.percent", "20"})

2.5、服务降级

可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
//调用出错了,直接返回123
@Reference(version = "timeout", timeout = 1000, mock = "fail: return 123")
//调用直接返回123
@Reference(version = "timeout", timeout = 1000, mock = "force: return 123")

2.6、本地存根

本地存根,名字很抽象,但实际上不难理解,本地存根就是⼀段逻辑,这段逻辑是在服务消费端执⾏的,这段逻辑⼀般都是由服务提供者提供,服务提供者可以利⽤这种机制在服务消费者远程调⽤服务提供者之前或之后再做⼀些其他事情,⽐如结果缓存,请求参数验证等等。
@Reference(version = "timeout", timeout = 1000, stub = "true")
private DemoService demoService;

stub对应的DemoServiceStub类

public class DemoServiceStub implements DemoService {

    private final DemoService demoService;

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

    @Override
    public String sayHello(String name) {
        // 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
        try {
            return demoService.sayHello(name); // safe  null
        } catch (Exception e) {
            // 你可以容错,可以做任何AOP拦截事项
            return "容错数据";
        }
    }
}

2.7、本地伪装

本地伪装就是Mock,Dubbo中Mock的功能相对于本地存根更简单⼀点,Mock其实就是Dubbo中的服务容错/服务降级的解决⽅方案。

2.8、参数回调

对于某个服务接⼝中的某个⽅法,如果想⽀持消费者在调⽤这个⽅法时能设置回调逻辑,那么该⽅法就需要提供⼀个⼊参⽤来表示回调逻辑。
interface工程添加接口
public interface CallbackService {
    String getName(String name);

    //回调方法
    default String getName(String name,String key,CallbackServiceListener callbackServiceListener){
        return null;
    }
}

public interface CallbackServiceListener {
    void changed(String data);
}

public class CallbackServiceListenerImpl implements CallbackServiceListener {
    @Override
    public void changed(String data) {
        System.out.println("changed:"+data);
    }
}

provider工程添加服务

@Service(version = "callback",
        methods = {@Method(name="siteName",arguments = {@Argument(index=2,callback = true)})},
        callbacks = 3)
public class CallbackServiceImpl implements CallbackService {
    @Override
    public String getName(String name) {
        return null;
    }

    @Override
    public String getName(String name, String key, CallbackServiceListener callbackServiceListener) {
        callbackServiceListener.changed("provider data");
        return "callback:key="+key+",name="+name;
    }
}

consumer工程

@RestController
public class CallbackController {
    @Reference(version = "callback")
    CallbackService callbackService;

    @RequestMapping("/callback")
    public String getName(@RequestParam("name") String name){
        return callbackService.getName(name,"key1",new CallbackServiceListenerImpl());
    }
}

image.png

提供方的控制台可以看到如下信息:

image.png

版权声明

非特殊说明,本文由Zender原创或收集发布,欢迎转载。

上一篇:4、Netty心跳检测机制 下一篇:2、Dubbo SPI

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

作者文章
热门