SpringBoot 整合 Dubbo--【持续更新】

SpringBoot 整合 Dubbo

Dubbo的介绍

Dubbo是什么?

Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SAO服务治理方案。简单来说,dubbo就是一个微服务框架。

其核心功能包含:

  • 远程通讯:提供了多种基于长连接的NIO框架抽象封装,包括多线程模型,序列化,以及"请求-响应" 模式的信息交换方式。
  • 集群容错:提供了基于接口方法的透明远程调用,包含多协议支持、以及软负载均衡、失败容错、地址路由、动态配置等集群支持。
  • 自动发现:基于注册中心目录服务,使服务消费方可以动态的查找服务提供方,使地址透明,使服务提供方可以平滑的增加或减少服务器。
Dubbo能做什么?
  • 透明化的远程方法调用:通过将公共的接口抽取出来,实现向调用本地方法一样调用远程方法,只需要少量的配置,没有任何API侵入。
  • 软负载均衡及容错机制:dubbo默认提供了4种负载均衡策略,Random LoadBalance、RoundRobin LoadBalance、LeastActive LoadBalance、ConsistentHash LoadBalance
  • 服务的自动注册与发现:服务的提供方不在写死,而是在注册中心基于接口名查询到服务提供者的IP地址,调用获取到的IP的服务提供方的相应服务;并且能够平滑的添加或删除服务节点。
Dubbo架构图解

在这里插入图片描述

组件介绍:

Container:服务运行容器。

Provider:暴露服务的服务提供方。

Registry:服务注册与发现的注册中心。

Consumer:远程调用服务的服务消费方。

Monitor:统计服务调用情况的监控中心

流程说明:
  • start:服务容器负责启动、加载、运行服务提供者。
  • register:服务提供者启动时,向注册中心注册自己提供的服务。
  • subsribe:服务消费者启动时,去注册中心订阅自己所需要的服务。
  • notify:注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更信息给消费者。
  • invoke:服务消费者从提供者地址列表中,基于负载均衡算法,选取一台服务提供者进行调用,若调用失败,则选取另一台。
  • count:服务消费者和提供者,在内存中累计调用次数和调用时间,定时发送一次统计数据到监控中心。

其中蓝色虚线的init属于初始化流程;红色虚线的async属于异步调用;红色实线的sync属于同步调用

Dubbo的简单使用

本文采用Dubbo与Zookeeper、SpringBoot框架的整合

准备工作

SpringBoot为 2.3.0.RELEASE 版本

dubbo为 2.7.7 版本

zookeeper服务程序为 3.6.1 版本

项目结构:

​ 主要分为三大模块:

gmall-interface:存放公共接口与实体类对象

boot-user-service-provider:服务提供者

boot-order-service-consumer:服务消费者

在这里插入图片描述

父工程依赖:

主要用于规范依赖版本 以及管理 各个功能模块

<?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.akieay.dubbo</groupId>
    <artifactId>springboot-dubbo</artifactId>
    <name>springboot-dubbo</name>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <modules>
        <module>gmall-interface</module>
        <module>boot-user-service-provider</module>
        <module>boot-order-service-consumer</module>
    </modules>

    <properties>
        <spring-boot.version>2.3.0.RELEASE</spring-boot.version>
        <dubbo.version>2.7.7</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>

    <build>
        <plugins>
            <!-- 跳过单元测试 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
通用模块抽取

在这里插入图片描述

/**
 * @author akieay
 */
public class UserAddress implements Serializable {

    private static final long serialVersionUID = 1L;

    private Integer id;

    private String userId;

    private String address;

    private String name;

    public UserAddress(Integer id, String userId, String address, String name, String mobile) {
        this.id = id;
        this.userId = userId;
        this.address = address;
        this.name = name;
        this.mobile = mobile;
    }

    private String mobile;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
}

import com.akieay.dubbo.gmall.entity.UserAddress;

import java.util.List;

/**
 * @author akieay
 */
public interface OrderService {

    List<UserAddress> initOrder(String userId);
}
import com.akieay.dubbo.gmall.entity.UserAddress;

import java.util.List;

/**
 * @author akieay
 */
public interface UserService {

    List<UserAddress> getUserAddressList(String userId);
}

服务提供者模块

依赖:

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

    <parent>
        <groupId>com.akieay.dubbo</groupId>
        <artifactId>springboot-dubbo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.akieay.dubbo</groupId>
    <artifactId>boot-user-service-provider</artifactId>
    <version>1.0-SNAPSHOT</version>

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

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

        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.7</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <!--dubbo客户端-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.0.1</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--zookeeper-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.6</version>
        </dependency>

        <dependency>
            <groupId>com.akieay.dubbo</groupId>
            <artifactId>gmall-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

配置文件:application.yml

server:
  port: 8002

spring:
  application:
    name: user-service-provider

dubbo:
  application:
    id: user-service-provider
    name: user-service-provider
  registry:
    id: zookeeper
    address: zookeeper://192.168.80.128:2181
  protocol:
    id: dubbo
    name: dubbo
    port: -1
  #  monitor:
#    protocol: registry
  scan:
    base-packages: com.akieay.dubbo.user.service
  config-center:
    timeout: 25000

debug: true

启动类配置

/**
 * @author akieay
 * @EnableDubbo: 开启基于注解的dubbo功能
 */
@SpringBootApplication
@EnableDubbo(scanBasePackages = {"com.akieay.dubbo.user.service"})
public class UserApplication {

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

}

服务提供者:

import com.akieay.dubbo.gmall.entity.UserAddress;
import com.akieay.dubbo.gmall.service.UserService;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Service;

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

/**
 * @author akieay
 */
@DubboService(interfaceClass = UserService.class)
@Service
public class UserServiceImpl implements UserService {

    @Override
    public List<UserAddress> getUserAddressList(String userId) {
        UserAddress userAddress1 = new UserAddress(1, "1", "测试地址一深圳宝安区", "小李", "0736-25347358");
        UserAddress userAddress2 = new UserAddress(2, "1", "测试地址—深圳龙华区", "小王", "0736-54682535");
        return Arrays.asList(userAddress1, userAddress2);
    }
}

服务消费者模块

依赖

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

    <parent>
        <groupId>com.akieay.dubbo</groupId>
        <artifactId>springboot-dubbo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.akieay.dubbo</groupId>
    <artifactId>boot-order-service-consumer</artifactId>
    <version>1.0-SNAPSHOT</version>

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

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

        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.7</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <!--dubbo客户端-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
        </dependency>
        <!--zookeeper-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.6</version>
        </dependency>

        <dependency>
            <groupId>com.akieay.dubbo</groupId>
            <artifactId>gmall-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

yml配置

server:
  port: 8001

dubbo:
  application:
    id: order-service-consumer
    name: order-service-consumer
  registry:
    id: zookeeper
    address: zookeeper://192.168.80.128:2181
    client: curator
  protocol:
    id: dubbo
    name: dubbo
    port: -1
  #  monitor:
  #    protocol: registry
  scan:
    base-packages: com.akieay.dubbo.order.service
  config-center:
    timeout: 25000

debug: true

启动类配置

/**
 * @author akieay
 */
@EnableDubbo
@SpringBootApplication
public class OrderApplication {

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

}

服务消费者:

import com.akieay.dubbo.gmall.entity.UserAddress;
import com.akieay.dubbo.gmall.service.OrderService;
import com.akieay.dubbo.gmall.service.UserService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author akieay
 */
@Service
public class OrderServiceImpl implements OrderService {

    @DubboReference(check = false, retries = 0 )
    private UserService userService;

    @Override
    public List<UserAddress> initOrder(String userId) {
        List<UserAddress> addressList = userService.getUserAddressList(userId);
        return addressList;
    }
}
import com.akieay.dubbo.gmall.entity.UserAddress;
import com.akieay.dubbo.gmall.service.OrderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author akieay
 */
@RestController
public class OrderController {

    @Resource
    private OrderService orderService;

    @GetMapping("/initOrder")
    public List<UserAddress> initOrder(@RequestParam("uid") String userId){
        return orderService.initOrder(userId);
    }
}
测试

启动服务提供者模块,然后再启动服务消费者模块,调用服务消费者的测试接口,即可看到 消费者 通过rpc远程调用了 提供者的 UserServiceImpl 的 getUserAddressList 方法

扩展示例

配置来源

首先,从Dubbo支持的配置来源说起,默认有四种配置来源:

  • JVM System Properties,-D参数
  • Externalized Configuration,外部化配置
  • ServiceConfig、ReferenceConfig等编程接口采集的配置
  • 本地配置文件dubbo.properties
覆盖关系

在这里插入图片描述

启动时检查

Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止Spring初始化完成,以便上线时,能及时发现问题,默认 check="true"

可以通过check="false"关闭检查,比如,测试时,有些服务不关心,或者出现循环依赖,必须有一方先启动。

另外,如果你的Spring容器是懒加载的,或者通过API编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false",总是会返回引用,当服务恢复时,能自动连上。

示例:

关闭某个服务的启动时检查 (没有提供者时报错):

[spring配置文件方式]
<dubbo:reference interface="com.akieay.dubbo.gmall.service.UserService" check="false" />
[注解方式]
@DubboReference(check = false)
private UserService userService;

关闭所有服务的启动时检查 (没有提供者时报错):

<dubbo:consumer check="false" />

关闭注册中心启动时检查 (注册订阅失败时报错):

<dubbo:registry check="false" />

通过 dubbo.properties

dubbo.reference.com.foo.BarService.check=false
dubbo.reference.check=false
dubbo.consumer.check=false
dubbo.registry.check=false

通过 -D 参数

java -Ddubbo.reference.com.foo.BarService.check=false
java -Ddubbo.reference.check=false
java -Ddubbo.consumer.check=false 
java -Ddubbo.registry.check=false
配置的含义

dubbo.reference.check=false,强制改变所有 reference 的 check 值,就算配置中有声明,也会被覆盖。

dubbo.consumer.check=false,是设置 check 的缺省值,如果配置中有显式的声明,如:<dubbo:reference check="true"/>,不会受影响。

dubbo.registry.check=false,前面两个都是指订阅成功,但提供者列表是否为空是否报错,如果注册订阅失败时,也允许启动,需使用此选项,将在后台定时重试。

超时:timeout

远程服务调用超时时间(毫秒) 默认: 1000

<dubbo:reference= "com.akieay.dubbo.gmall.service.UserService" id="userService" timeout= "5000">
    <dubbo:method name= "getUserAddressList" timeout="1000"></dubbo:method>
</dubbo:reference>
@DubboReference(check = false, timeout = 1000)
private UserService userService;

匹配规则:

  • 精确优先:(方法级优先,接口次之,全局配置再次之)
  • 消费者设置优先,(如果级别一样,则消费方优先,服务方次之)
重试次数: retries [不包含第一次调用]

多个服务提供者时,第一个服务提供者失败,可以重试其它的服务提供者

不包含第一次调用,0表示不重试

@DubboReference(check = false, timeout = 1000, retries = 0 )
private UserService userService;

幂等(设置重试次数)【查询、删除、修改】 非幂等(不能设置重试次数)【新增】

幂等:方法的多次执行结果 与 单次执行结果一致:即在条件相同的情况下,方法执行多次产生的结果 与 执行一次相同;

非幂等:方法执行多次结果 与 单次执行的结果不一致

多版本

当一个接口实现,出现不兼容升级时,可以用版本号过度,版本号不同的服务相互间不引用。

可以按照以下的步骤进行版本迁移:

  1. 在低压力时间段,先升级一半提供者为新版本
  2. 再将所有消费者升级为新版本
  3. 然后将剩下的一半提供者升级为新版本

老版本服务提供者配置:

<dubbo:service interface="com.akieay.dubbo.gmall.service.UserService" version="1.0.0" />
@DubboService(interfaceClass = UserService.class, version = "1.0.0")

新版本服务提供者配置:

<dubbo:service interface="com.akieay.dubbo.gmall.service.UserService" version="2.0.0" />
@DubboService(interfaceClass = UserService.class, version = "2.0.0")

老版本服务消费者配置:

<dubbo:reference id="userService" interface="com.akieay.dubbo.gmall.service.UserService" version="1.0.0" />
 @DubboReference(check = false, timeout = 1000, retries = 0, version = "1.0.0")

新版本服务消费者配置:

<dubbo:reference id="userService" interface="com.akieay.dubbo.gmall.service.UserService" version="2.0.0" />
 @DubboReference(check = false, timeout = 1000, retries = 0, version = "2.0.0")

如果不需要区分版本,可以按照以下的方式配置 [1]

<dubbo:reference id="userService" interface="com.akieay.dubbo.gmall.service.UserService" version="*" />
本地存根

远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub ,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。

配置

<dubbo:service interface="com.foo.BarService" stub="com.foo.BarServiceStub" />

@DubboReference(check = false, timeout = 1000, retries = 0, version = "1.0.0", stub = "com.akieay.dubbo.order.service.UserServiceStub")
private UserService userService;

Stub 实现

  1. Stub 必须有可传入 Proxy 的构造函数
  2. 在 interface 旁边放一个 Stub 实现,它实现 UserService接口,并有一个传入远程 UserService实例的构造函数。
/**
 * @author akieay
 */
public class UserServiceStub implements UserService {

    private final UserService userService;

    public UserServiceStub(UserService userService) {
        this.userService = userService;
    }

    @Override
    public List<UserAddress> getUserAddressList(String userId) {
        System.out.println("UserServiceStub 本地存根执行......");
        if(StringUtils.isEmpty(userId)){
            return null;
        }
        // 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
        try {
            return userService.getUserAddressList(userId);
        } catch (Exception e) {
            // 你可以容错,可以做任何AOP拦截事项
            return null;
        }
    }
}
springboot中dubbo的三种使用方法
  • 1)、导入dubbo-starter,在application.yml 配置属性,使用@DubboService【暴露服务】使用@DubboReference【引用服务】
  • 2)、保留 dubbo xml配置文件;导入 dubbo-starter,使用 @ImportResource 导入 dubbo 的配置文件即可
  • 3)、使用注解API 的方式;将每一个组件手动创建到容器中
高可用
1、zookeeper 宕机 与 dubbo 直连

现象:zookeeper 注册中心宕机了,还可以消费 dubbo 暴露的服务。

原因:

  • 监控中心宕掉不影响使用,只是丢失部分采样数据
  • 数据库宕掉后,注册中心仍然能通过缓存提供服务列表查询,但不能注册新服务
  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

高可用:通过设计,减少不能提供服务的时间;

dubbo 直连:

@DubboReference(url = "192.168.0.164:8002")
private UserService userService;

若采用dubbo 直连的方式,即便是没有注册中心【宕掉】,服务消费者也可以通过指定的url 访问服务提供者

2、集群下 dubbo 负载均衡配置

在集群负载均衡时,dubbo 提供了多种负载均衡策略,缺省为 random【默认使用基于权重的随机负载均衡机制】。

负载均衡策略:
  • Random LoadBalance

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

在这里插入图片描述

如图所示,根据权重将服务的调用概率分成了7等份,服务1占2份,服务2占4份,服务3占一份;在一次请求中,服务1被调用的几率是2/7,服务2被调用的几率是4/7,服务3被调用的几率是1/7;但这并不意味着下一次调用的就是机率高的;有可能服务3会被连续调用几次,而服务2没被调用;但是在大量调用时【如上万次或 10万次】,服务被调用的几率大致如图所示。

  • RoundRobin LoadBalance

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

在这里插入图片描述

如图所示,该策略下服务的调用将先基于轮询,然后考虑权重;如图,若有7次调用服务的请求,其调用顺序为 1、2、3、1、2、2、2;请求首先采用轮询的方式将所有服务提供者都调用一次,当进入第二轮时,3号服务的权重规定了:在每7次请求中,3号服务只被调用一次。所以,第二轮中将只调用1、2号服务;同理,在第三轮中,1号服务的次数也已经用完,将只调用2号服务。

  • LeastActive LoadBalance

最小活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。使慢的提供者收到更少的请求,因为越慢的提供者的调用前后计数差会越大。

在这里插入图片描述

如图所示,在服务调用时,会统计上一次调用服务所用的时间;所用时间少的,将优先被调用;若时间相同,则随机调用。【如上图将会调用服务1】

  • ConsistentHash LoadBalance

一致性 Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂掉时,原本发往该提供者的请求,基于虚拟机点,平摊到其它提供者,不会引起剧烈变动。算法参见:http://en.wikipedia.org/wiki/Consistent_hashing。缺省只对第一个参数 Hash,如果要修改,请配置 :

<dubbo:parameter key="hash.arguments" value="0,1" />

缺省用 160 份虚拟节点,如果要修改,请配置:

<dubbo:parameter key="hash.nodes" value="320" />

在这里插入图片描述

负载均衡策略的使用配置:

服务端服务级别

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

客户端服务级别

<dubbo:reference interface="..." loadbalance="roundrobin" />
@DubboReference(loadbalance = "random", check = false, timeout = 1000, retries = 0, version = "1.0.0")
private UserService userService;

服务端方法级别

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

客户端方法级别

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

更多:https://dubbo.apache.org/zh-cn/docs/user/demos/loadbalance.html

服务降级
什么是服务降级?

当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。

使用:

可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。

向注册中心写入动态配置覆盖规则:

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));

其中:

  • mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
  • 还可以改为 mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。

可以通过dubbo的监控中心实现:

在这里插入图片描述

其中:屏蔽 相当于第一种方法,即不发起远程调用;容错 相当于第二种方法,即在服务调用失败后返回null值。

集群容错

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

在这里插入图片描述

各节点关系:

  • 这里的 InvokerProvider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息
  • Directory 代表多个 Invoker,可以把它看成 List<Invoker> ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更
  • ClusterDirectory 中的多个 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

    广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。


集群模式配置

按照以下示例在服务提供方和消费方配置集群模式

<dubbo:service cluster="failsafe" />

<dubbo:reference cluster="failsafe" />
整合 Hystrix

Hystrix 旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix 具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。

配置 spring-cloud-starter-netflix-hystrix
<!--hystrix-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

然后再Application 启动类上添加 @EnableHystrix 注解,开启基于注册的 hystrix

@EnableHystrix
public class UserApplication8002 {
使用案例:

服务提供方

导入依赖,并开启基于注解的 hystrix【上面已给出示例】

在需要启用容错的方法上加上 @HystrixCommand 注解,如下:

public class UserServiceImpl implements UserService {

    @HystrixCommand
    @Override
    public List<UserAddress> getUserAddressList(String userId) {

服务消费方

导入依赖,并开启基于注解的 hystrix【上面已给出示例】

在服务调用方加上 @HystrixCommand 注解,并编写服务降级回调方法

@Service
public class OrderServiceImpl implements OrderService {

    @DubboReference(loadbalance = "random", check = false, timeout = 1000, retries = 0, version = "1.0.0")
    private UserService userService;

    /**
     *
     * @param userId
     * @return
     * defaultFallback: 服务降级回调方法
     */
    @HystrixCommand(defaultFallback = "initOrderFallBack")
    @Override
    public List<UserAddress> initOrder(String userId) {
        List<UserAddress> addressList = userService.getUserAddressList(userId);
        return addressList;
    }

    /**
     * 容错回调方法
     * @return
     */
    public List<UserAddress> initOrderFallBack(){
        return Arrays.asList(new UserAddress(1, "1", "服务容错地址", "服务容错测试", "0736-2208208820"));
    }
}

更多使用案例请参考dubbo官方文档:https://dubbo.apache.org/zh-cn/docs/user/quick-start.html

dubbo原理

一、RPC 与 Netty原理
1、RPC 原理

在这里插入图片描述

一次完整的 PRC 调用流程(同步调用,异步另说)如下:

  • 1)服务消费方(client)以本地调用方式调用服务;
  • 2)client stub 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
  • 3)client stub 找到服务地址,并将消息发送到服务端;
  • 4)server stub 收到消息后进行解码;
  • 5)server stub 根据解码结果调用本地的服务;
  • 6)本地服务执行并将结果返回给 server stub;
  • 7)server stub 将返回结果打包成消息并发送至消费方;
  • 8)client stub 接收到消息,并进行解码;
  • 9)服务消费方得到最终结果。

RPC 框架的目标就是要将 2~8 这些步骤都封装起来,这些细节对于用户来说是不可见的。

2、Netty 通信原理

Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。它极大地简化了TCP 和 UDP 套接字服务器等网络编程。

NIO 与 BIO

BIO:(Blocking IO),即阻塞式IO,NIO:(Non-Blocking IO),即非阻塞式IO

NIO模型

在这里插入图片描述

每一个请求一进来,都需要开一个线程来读取数据,并进行业务逻辑的调用;而且在业务逻辑执行完成之前,线程都是得不到释放的;当大量请求过来时,会有大量的线程在这阻塞等待业务逻辑的完成,所以我们的服务器就不能同时处理大量的请求。

BIO模型

在这里插入图片描述

Channel:通道

Selector:一般称为 选择器,也可以翻译为 多路复用器;

Connect(连接就绪)、Accept(接收就绪)、Read(读就绪)、Write(写就绪)

每一个请求过来都可以开启一个通道,Selector 来监听多个通道,当发现某一个通道里面的数据准备好了(比如某一个连接过来了),Selector可以监听状态(Connect、Accept、Read、Write),当某一个状态准备好了,Selector 就可以开一个线程去执行事件对应的处理。

Netty就是基于BIO实现的


Netty的基本原理

在这里插入图片描述

如图所示:

首先Netty 服务器启动,并绑定上一个监听端口(比如dubbo的20880),这样所有给这个端口发送的数据Netty 服务器都能够收到了;

1)服务器启动初始化 Channel(通道);

2)将 Channel 注册到 Selector(多路复用器)中;

3)Selector 轮询监听 accept(接收就绪)事件,即通道接收数据已经准备就绪了;生成一个任务队列,处理accept事件;

4)处理 accept,建立连接 channel(NioSocketChannel),即生成与客户端连接的通道;

5)将 NioSocketChannnel 注册到另一个 Selector 中,只不过这个 Selector 监听的是 Read与Write 事件;Read:即请求发来的数据已经接收完了,等待读取;Write:即可以给客户端通道里面写响应了;

6)轮询读写事件;

7)处理读写事件;比如读准备就绪了做一个任务,写准备就绪了做一个任务,这些任务都会抛给任务队列,Netty就把这个任务队列执行完;

boos:用来监听来自于绑定的端口的连接的所有准备就绪事件;worker:准备就绪后要做的工作由worker执行;


二、Dubbo原理-框架设计
整体设计

在这里插入图片描述

图例说明:

  • 图中左边淡蓝色背景(Consumer)的为:服务消费方使用的接口;右边淡绿色背景(Provider)的为:服务提供方使用的接口,位于中轴的为:双方都用到的接口。
  • 图中从下至上分为十层,各层均为单向依赖,右边黑色箭头(Depend)代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI;
  • 图中绿色小块(Interface)的为扩展接口,蓝色小块(Class)的为实现类,图中只显示用于关联各层的实现类。
  • 图中蓝色虚线(init)为初始化过程,即启动时组装链;红色实线(Call)为方法调用过程,即运行时调用链;紫色三角箭头(Inherit)为继承,可以把子类看作父类的同一个节点,线上文字为调用的方法。
各层说明
  • service 层:服务层,主要是用户编写的接口,对于用户编程来说主要关心的是这一层;
  • config 配置层:对外配置接口,以 ServiceConfigReferenceCondig 为中心,可以直接初始化配置类,也可以通过 Spring 解析配置生成配置类;
  • proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务端 Skeleton,以 ServiceProxy 为中心,扩展接口为 ProxyFactory
  • registry 注册中心层:封装服务地址的注册于发现,以服务 URL 为中心,扩展接口为 RegistryFacoryRegistryRegistryService
  • cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 ``Cluster,Directory,Router,LoadBalance`;
  • monitor 监控层:RPC 调用次数和调用事件监控,,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
  • protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
  • exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
  • transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
  • serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool

三、Dubbo原理-标签解析

主要参考:DubboBeanDefinitionParser.java DubboNamespaceHandler.java


四、Dubbo原理-服务暴露流程

在这里插入图片描述

五、Dubbo原理-服务引用流程

在这里插入图片描述

六、Dubbo原理-服务调用流程

更多:请参考https://dubbo.apache.org/zh-cn/docs/user/quick-start.html

案例代码 gitee 地址:https://gitee.com/ak_learning/springboot-dubbo.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值