深入理解RPC之Dubbo的应用及原理解析

1,607 阅读28分钟

分布式系统相关

分布式系统定义

分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。 分布式系统(distributed system)是建立在网络之上的软件系统。

为什么会出现分布式系统

随着互联网的发展,网站的应用规模不断扩大,常规的垂直应用架构已经无法应对,分布式服务架构以及流动计算机架构势在必行,亟需一个治理系统确保架构有条不紊的进行。

分布式应用架构的演变过程

  • 单一应用架构

当网站流量很小时,只需要一个应用,将所有的功能都部署在一起,以减少部署节点和成本,此时,用于 简化增删改查工作量的数据访问框架(ORM) 是关键。

适合小型网站、小型管理系统,将所有功能都部署到一个功能里,简单易用

缺点是:性能扩展比较难、协同开发问题、不利于升级维护。

  • 垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成不相干的几个应用,以提升效率。此时,用于 加速前端页面开发的web框架(MVC) 是关键。

通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更加的方便,更有针对性。

缺点是:功用模块无法重复利用,开发性的浪费。

  • 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前段应用能更快的速度响应多变的市场需求,此时,用于 提高业务复用及整合的分布式框架(RPC) 是关键。

  • 流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现。此时需要增加一个调度中心基于访问压力实施管理集群容量,提高集群利用率。此时, 用于提高计算机利用率的资源调度和治理中心(SOA:Service Oriented Architecture) 是关键

RPC

RPC定义

RPC(Remote Procedure Call)是指远程过程调用,是一种进程间的通信方式,它是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)过程或函数,而不用程序员显示的编码这个远程调用的细节,即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。

RPC的基本原理

假设client段想要调用server的方法,调用过程如下图所示:

首先client通过client stub(可理解为clientserver交互的助手)与server端建立socket连接,然后通过网络将需要调用server端的方法信息(如方法名、参数列表、返回值类型等信息)传递给server端的server stubserver stub收到信息后将这些信息传递给serverserver使用信息里的参数执行方法,得到返回值后将返回值交给server stubserver stub在通过socket连接将返回值传递给client端中的client stubclient stub将从server中得到的返回值交给client

决定RPC效率的两个重要因素:一个是通信效率,另一个是序列化和反序列化的效率。

常见的RPC框架

Dubbo、gPRC、Thrift、HSF(High Speed Service Framework)

Dubbo核心概念

Dubbo简介

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

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

Dubbo设计架构

Dubbo执行流程
  • Dubbo容器启动后,Provider(服务提供者)会将自己提供的服务注册到注册中心中,这样注册中心便知道有哪些服务上线了。
  • 当Consumer(服务消费者)启动后,会从注册中心中订阅需要的服务,而且比如某一个服务进行了变更(比如某个服务下线了),注册中心还可以以长连接的方式给服务消费者发送服务变更通知。
  • 服务消费者拿到需要调用的服务之后,可以同步调用服务提供者提供的服务(invoke),而且比如服务消费者调用了某个查询服务,而这个服务在很多节点上部署,服务消费者还可以根据Dubbo提供的负载均衡算法选择一个节点进行调用。
  • 服务消费者调用服务后的信息(比如调用时间、调用服务信息)会每隔一段时间统计发送到Dubbo监控中心,监控中心就可以监控到服务的运行状态。
  • 而且根据上图发现,Dubbo容器启动、服务生产者注册自己的服务、服务消费者从注册中心中订阅服务是在Dubbo应用启动时完成的,而服务消费者调用服务生产者的服务是同步过程,注册中心向服务生产者发送服务变更信息以及服务消费者和服务生产者向监控中心发送服务信息是异步进行的。

Dubbo的配置

搭建Dubbo注册中心

Dubbo官方文档中建议使用Zookeeper注册中心。

搭建Dubbo监控中心

  • Dubbo本身并不是一个服务软件,它其实就是一个jar包能够帮助Java程序连接到Zookeeper,并利用Zookeeper进行消费和提供服务,所以不用在Linux上启动什么Dubbo服务。
  • 但是为了让用户更好的管理监控众多Dubbo服务,官方提供了一个可视化的监控程序,不过即使不安装这个监控也不影响Dubbo的使用。
Dubbo监控中心安装步骤
  • 首先进入进入下载页面

  • 下载Dubbo监控中心

  • 下载完成之后修改配置文件信息,如图所示,如果Zookeeper是单机模式,修改 application.properties 文件中的 dubbo.registry.address 为zookeeper部署的ip地址。

  • 如果Zookeeper是以集群的模式启动的,修改 application.properties 文件中的 dubbo.registry.address 为zookeeper集群的部署的ip地址(我的集群是三台Zookeeper节点的集群,IP地址分别为 192.168.51.101、192.168.51.101、192.168.51.101 )。v

  • 修改完成之后使用终端(Windows下使用cmd)打包

  • 首先进入dubbo-admin目录

  • 执行 mvn clean package命令打包(前提是电脑已经配置了MAVEN)

  • 若打包结束后如下图,则表示打包成功,

  • 打好的包会放在dubbo-admin目录下的target目录下。

  • 将打好的包放入自己喜欢的目录下,我是直接放在了 incubator-dubbo-ops-master 根目录下,然后在终端中执行Java命令运行jar包(前提是Zookeeper已经启动)。

  • 运行成功如下图所示,Dubbo端口为7001

  • 测试:浏览器地址栏输入 localhost:7001 进入,账号密码均为root。

  • 显示此界面表示Dubbo运行成功

基于Dubbo的HelloWorld

基于普通MAVEN项目的HelloWorld

  • 使用Idea新建MAVEN项目 dubboHelloWorld 并设置 groupIdcom.gmall
  • 在项目中创建 Module 起名为 gmall-interface
  • gmall-interface 中创建 com.gmall.bean.UserAddress 实体类
package com.gmall.bean;

import java.io.Serializable;

/**
 * 用户地址
 *
 * @author lfy
 */
public class UserAddress implements Serializable {

    private Integer id;
    private String userAddress; //用户地址
    private String userId; //用户id
    private String consignee; //收货人
    private String phoneNum; //电话号码
    private String isDefault; //是否为默认地址    Y-是     N-否

    public UserAddress() {
        super();
        // TODO Auto-generated constructor stub
    }

    public UserAddress(Integer id, String userAddress, String userId, String consignee, String phoneNum,
                       String isDefault) {
        super();
        this.id = id;
        this.userAddress = userAddress;
        this.userId = userId;
        this.consignee = consignee;
        this.phoneNum = phoneNum;
        this.isDefault = isDefault;
    }

    public Integer getId() {
        return id;
    }

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

    public String getUserAddress() {
        return userAddress;
    }

    public void setUserAddress(String userAddress) {
        this.userAddress = userAddress;
    }

    public String getUserId() {
        return userId;
    }

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

    public String getConsignee() {
        return consignee;
    }

    public void setConsignee(String consignee) {
        this.consignee = consignee;
    }

    public String getPhoneNum() {
        return phoneNum;
    }

    public void setPhoneNum(String phoneNum) {
        this.phoneNum = phoneNum;
    }

    public String getIsDefault() {
        return isDefault;
    }

    public void setIsDefault(String isDefault) {
        this.isDefault = isDefault;
    }


}
  • gmall-interface 中创建 com.gmall.service.UserServicecom.gmall.service.OrderService 接口
package com.gmall.service;

import java.util.List;

import com.gmall.bean.UserAddress;

/**
 * 用户服务
 *
 * @author lfy
 */
public interface UserService {

    /**
     * 按照用户id返回所有的收货地址
     *
     * @param userId
     * @return
     */
    public List<UserAddress> getUserAddressList(String userId);

}
package com.gmall.service;

import java.util.List;

import com.gmall.bean.UserAddress;

public interface OrderService {

    /**
     * 初始化订单
     *
     * @param userId
     */
    public List<UserAddress> initOrder(String userId);

}
  • 在项目中创建 Module 起名为 user-service-provider 并创建 com.gmall.service.impl.UserServiceImpl 实现 UserService
package com.gmall.service.impl;

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

import com.gmall.bean.UserAddress;
import com.gmall.service.UserService;

public class UserServiceImpl implements UserService {

    @Override
    public List<UserAddress> getUserAddressList(String userId) {
        System.out.println("UserServiceImpl.....old...");
        // TODO Auto-generated method stub
        UserAddress address1 = new UserAddress(1, "山东省济南市数娱广场A座", "1", "邢老师", "010-56253825", "Y");
        UserAddress address2 = new UserAddress(2, "山东省济南市数娱广场A座", "1", "王老师", "010-56253825", "N");
		/*try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/
        return Arrays.asList(address1, address2);
    }

}
  • 在项目中创建 Module 起名为 order-service-consumer 并创建 com.gmall.service.impl.OrderServiceImpl 实现 OrderService
package com.gmall.service.impl;

import com.gmall.bean.UserAddress;
import com.gmall.service.OrderService;
import com.gmall.service.UserService;

import java.util.List;

public class OrderServiceImpl implements OrderService {

    UserService userService;

    public List<UserAddress> initOrder(String userId) {

        List<UserAddress> addressList = userService.getUserAddressList(userId);
        System.out.println(addressList);

        return null;
    }
}
  • 此时由于在 user-service-providerorder-service-consumer 并没有创建 UserServiceOrderService 接口,所以需要在这两个 Module 中的 pom 文件中导入 gmall-interface
    <dependencies>

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

    </dependencies>
  • 到此虽然项目已经不报错,但是由于 order-service-consumer 中的 OrderServiceImpl 调用的是 UserService ,是一个接口,它的实现类有可能存在于别的项目或者别的服务器中,所以此时肯定是无法运行的,所以此时需要用Dubbo来改造进行远程调用。

Dubbo改造的步骤

  • 将服务提供者注册到注册中心

(1):导入 Dubbo 依赖,Dubbo依赖中包含了相关的 Springjar 包,所以在使用时可以使用 Spring 的方式使用 Dubbo

<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.2</version>
</dependency>

(2):由于注册中心使用的是 Zookeeper ,所以还需要导入 Zookeeper 客户端的依赖, dubbo2.6 之前的版本引入 zkclient 操作 zookeeper ,而 dubbo 2.6 及以后的版本引入 curator 操作 zookeeper

                <dependency>
			<groupId>com.101tec</groupId>
			<artifactId>zkclient</artifactId>
			<version>0.10</version>
		</dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.10</version>
        </dependency>

(3):通过 Spring 配置服务提供者(在 user-service-provider 中配置 provider.xml )

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


    <!--   1.指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名) -->
    <dubbo:application name="user-service-provider"/>
    <!--  2.指定注册中心的位置  -->
    <dubbo:registry address="zookeeper://192.168.51.101:2181?backup=192.168.51.102:2181,192.168.51.103:2181"/>
    <!--  3.指定通信规则(通信协议和通信端口)  -->
    <dubbo:protocol name="dubbo" port="20880"/>
    <!--  4.暴露服务 interface:需要暴露的接口 ref:指向服务的真正的实现对象 -->
    <dubbo:service interface="com.gmall.service.UserService" ref="userServiceImpl"/>
    <!--  服务的实现  -->
    <bean id="userServiceImpl" class="com.gmall.service.impl.UserServiceImpl"/>
</beans>

(4):测试

package com.gmall.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;

public class MainApplication {

    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
        context.start();
        System.in.read();
    }
}

这段代码的意思是,加载resource目录下的provider.xml配置文件,运行启动起来之后会加载配置文件,然后读取需要连向的Zookeeper和需要暴露的服务。服务启动之后刷新localhost:7001,若绿色标注处的服务数和应用数为1,则表示测试成功。

通过下面的操作可以看到服务的具体信息

至此,服务提供者就配置好了

  • 让服务消费者去注册中心订阅服务提供者的服务地址

(1):和配置服务提供者相同导入需要的依赖 (2):通过Spring配置服务消费者(在 order-service-consumer 配置 consumer.xml )

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
		">
    <context:component-scan base-package="com.gmall.service.impl"/>
    <!--   1.指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名) -->
    <dubbo:application name="order-service-consumer"/>
    <!--  2.指定注册中心的位置  -->
    <dubbo:registry address="zookeeper://192.168.51.101:2181?backup=192.168.51.102:2181,192.168.51.103:2181"/>
    <!--  声明需要调用的远程服务的接口,生成远程服务代理  -->
    <dubbo:reference interface="com.gmall.service.UserService" id="userService"/>

</beans>

通过上面的配置在OrderServiceImpl类中可以使用@Autowired自动注入将UserService注入进来,同时OrderServiceImpl类名上可以加上@Service注解

(3):测试

package com.gmall.test;

import com.gmall.service.OrderService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;

public class MainApplication {

    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
        OrderService orderService = context.getBean(OrderService.class);
        orderService.initOrder("1");
        System.out.println("调用完成..");
        System.in.read();
    }
}

代码解析:首先读取consumer.xml配置文件,读取OrderService对象,然后调用initOrder方法。这里可以调用的原因是在consumer.xml配置文件中使用<context:component-scan base-package="com.gmall.service.impl"/>语句自动注入了OrderService的实现类,并且在consumer.xml文件中声明了远程服务的接口userService,所以在initOrder中的userService会远程调用getUserAddressList方法。并且在刚才的服务者配置中已经暴露出了UserServiceImpl实现类,所以可以直接调用。

再次刷新localhost:7001,可以看到服务消费者数为1

通过下图的方式可以看到服务生产者和服务消费者的具体信息

安装dubbo-monitor-simple(简单的监控中心)

-分别修改 maintest 下的 dubbo.properties 文件

  • 按下图所示修改

  • 修改完成后将 dubbo-monitor-simple 文件夹使用 mvn package 打包。
  • 打包完成后去target目录下,如下图所示,解压绿色标注的压缩文件,然后将解压好的文件夹移动到箭头指向的位置(不移动也可以,根据个人喜好)。

  • 如下图所示,根据自己电脑的操作系统,选择bat文件或者sh文件(windows选择bat,Linux或者Mac选择sh)启动或者停止服务。

  • 服务启动之后,浏览器输入local:8080访问简易监控中心,加载出如下图所示,表示安装成功。

  • 修改之前写过的代码,分别在consumer.xml文件和provider.xml文件中添加
//使得服务生产者和服务消费者都连接上监控中心
<dubbo:monitor protocol="registry"/>
  • 配置完毕后,重新启动两个项目,然后根据下图所示,若看到Service如下图所示,表示测试成功。

基于SpringBoot的HelloWorld

  • 首先使用Idea创建Spring项目

next后什么都不用选直接next->finish创建项目

  • 然后在创建好的项目中依次创建三个Module,名字为GroupId和项目的一致,名字依次为:boot-dubbo-interface、boot-order-service-consumer、boot-user-service-provider。

  • boot-gmall-interface中依次创建下图接口和类,内容直接复制上个例子中。

  • 依次在boot-order-service-consumer、boot-user-service-provider的pom文件中导入boot-dubbo-interface的依赖。

  • 在boot-user-service-provider中创建UserServiceImpl,注意,类的位置应该与SpringBoot的启动类在同一包目录下,负责启动项目后检测不到该服务生产者。

  • 接下来编写boot-user-service-provider的配置文件:application.properties。
#指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名)
dubbo.application.name=user-service-provider
#指定注册中心的位置
dubbo.registry.address=zookeeper://192.168.51.101:2181?backup=192.168.51.102:2181,192.168.51.103:2181
#指定注册中心类型
dubbo.registry.protocol=zookeeper
#指定通信规则(通信协议和通信端口)
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
#服务生产者连接监控中心
dubbo.mointor.protocol=registry
  • 在UserServiceImpl中添加Spring的@Component注解和@Service注解,注意这个Service注解是Dubbo的,不是Spring的.使用该注解后,标志该类被暴露出去,即这个类是服务生产者生产的服务。

  • 最后在项目的启动类上添加@EnableDubbo注解,表示开启基于注解的Dubbo功能。

  • 最后进行测试,启动主类,然后访问localhost:8080,得到下图的结果表示测试成功,服务生产者测试完毕。

  • 接下来配置服务消费者,首先在boot-order-service-consumer的pom文件中添加boot-dubbo-interface的依赖。
  • 然后修改boot-order-service-consumer的配置文件application.properties。
#因为这是一个web应用,而且8080端口被Dubbo的监控中心占用,所以需要修改默认端口
server.port=8082
dubbo.application.name=boot-order-service-consumer
dubbo.registry.address=zookeeper://192.168.51.101:2181?backup=192.168.51.102:2181,192.168.51.103:2181
dubbo.monitor.protocol=registry
  • 在OrderServiceImpl类名上添加Service注解,这里使用的是Spring的注解,因为OrderServiceImpl是服务消费者类,他不需要将该类暴露出去,然后在该类的成员变量UserService上添加@Reference注解,该注解可以使得服务消费者自动去注册中心查找需要消费的服务。

  • 新建一个OrderController,这里是普通的SpringBoot操作

  • 然后在该项目的启动类上添加@EnableDubbo注解。

  • 测试,依次启动服务生产者和服务消费者。

  • 浏览器输入localhost:8080/initOrder?uid=1,并访问localhost:8080,出现下图所示结果即表示测试成功。

SpringBoot整合Dubbo总结
  • 在pom文件中导入Dubbo相关依赖
  • 配置application.properties
  • 如果是要暴露服务,使用@Service注解,如果是消费服务,使用@Reference注解

关于Dubbo的配置

Dubbo的基本配置在Dubbo的官方文档写的很清楚,包括基于XML的配置以及在SpringBoot中的配置

Dubbo官网

Dubbo配置的策略

  • 可以在程序启动时,通过java虚拟机配置
  • 可以在xml文件中配置
  • 可以在SpringBoot的application.properties文件中配置
  • 这三种配置有优先策略,通过虚拟机配置优先级最高,其次是xml文件配置或者properties文件配置,最后是可以自定义dubbo.properties文件配置。

Dubbo常用配置

启动时检查check
  • 启动时检查check默认为true。Dubbo默认是服务消费者和服务生产者都去注册中心注册,然后服务消费者消费服务生产者提供的服务,假设此时注册中心中只有服务消费者进行了注册,而服务生产者没有注册,那么默认情况下消费者在启动时就会报错。若设置check为false,消费者启动时不会检查注册中心中是否有产品,而是在调用时检查。
  • dubbo:consumer是服务消费者的缺省配置,也是dubbo:reference的一些默认配置。
  • dubbo:registry是注册中心的缺省配置。
  • timeout:超时设置,服务消费者在调用服务生产者提供的服务时,由于网络原因,服务生产者需要执行一个方法很长时间,可以设置timeout,若在timeout时间内还是没有返回,则服务消费者停止消费,会报错。默认为1000ms,即1s。还可以使用dubbo:method指定特定方法的timeout。
  • Dubbo配置的覆盖关系 (1):方法级优先、接口级次之,全局配置优先级最低。 (2):如果级别一样,则消费者优先,提供方次之。

重试次数retries
  • 重试次数retries是一个整数,但是不包含第一次调用,比如设置3,则第一次调用失败后,还会调用三次。在幂等(比如查询、删除、修改)服务可以设置重试次数,在非幂等(比如新增操作)不能设置重试次数。
Dubbo的多版本机制

比如某个接口设计出了新版本的升级,但是不保证其稳定性,可以让系统中一部分节点使用新版本,其余节点还是使用老版本,若使用非常稳定,再让剩余的节点使用新功能。可以使用version属性设置接口的版本。

本地存根

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

配置流程

  • 在接口包中定义一个接口实现类,且实现类中声明一个接口对象并且声明有参构造,构造起传入的其实是真正的远程代理对象,然后在实现接口的方法中可以进行数据验证等操作。

  • 之后在xml文件中使用stub属性配置,属性值是实现类的全类名。

  • 在SpringBoot中可以通过如下配置

验证:

  • 传入的userId不为空字符串时:
  • 传入参数为空字符串时:

SpringBoot整合Dubbo的三种方式

  • 导入dubbo-starter依赖,然后在application.properties中进行配置,主要是使用@Service注解暴露服务,使用@Reference注解引用远程服务。使用这种方式必须在SpringBoot的启动类中声明@EnableDubbo使得开启基于注解的Dubbo功能。但是使用这种方式无法做到方法级别的精确配置,例如:timeout。
  • 如果需要使用到精确到方法的配置,可以保留xml文件,导入dubbo-starter依赖,将xml文件放入SpringBoot的Resource下,然后在启动类中声明@ImportResource(“classpath:配置文件名“)。如果使用这种方式,不会使用@Service和@Reference,因为配置文件中已经写好了需要暴露的和需要远程引用的服务。
  • 还可以使用注解API的方式配置Dubbo,创建一个配置类,将每一个组件手动的创建到容器中。配置类声明@Configuration注解,比如需要设置application属性,可以在类中定义一个application方法,方法声明@Bean注解,在方法中定义一个Application类对象,然后通过对象来设置属性。

Dubbo的高可用

Zookeeper宕机与Dubbo直连

  • 即使注册中心Zookeeper宕机,还可以消费Dubbo暴露的服务。 (1):即使Dubbo的监控中心宕机,也不会影响Dubbo的正常使用,只是丢失了部分采样数据。 (2):数据库宕机后,注册中心仍然可以通过缓存提供服务列表查询,但是不能注册新的服务。 (3):注册中心集群的任意一个节点宕机,将自动切换到另外一台。 (4):注册中心全部宕机,服务提供者和消费者仍然可以通过本地缓存通讯。 (5):服务提供者无状态,任意一台宕机后,不影响使用。 (6):服务提供者全部宕机,服务消费者应用将无法使用,并且会无限次重连等待服务提供者恢复。

Dubbo直连的配置

  • 服务消费者通过@Reference注解的url属性直接连接到服务提供者提供的服务的端口,即使注册中心(Zookeeper)宕机了,或者没有注册中心,服务消费者仍然可以使用服务。比如服务消费者需要远程调用UserService对象,而UserService对象注册的端口是20882,则可以使用@Reference(url="IP地址+20882")来使用该服务。

集群模式下Dubbo的负载均衡配置

在集群负载均衡时,Dubbo提供了多种负载均衡策略,如果不配置,默认为random随机调用。

负载均衡策略

可以使用Reference的loadbalance属性来实现 可以在localhost:7001中设置服务提供者的权重

  • 基于权重的随机负载均衡机制:Random LoadBalance 随机,按照权重设置随机率。比如orderService想要远程调用userService,而userService分别在三台机器上,我们可以给每台机器设置权重,比如三台机器的权重依次为100、200、50,则总权重为350,则选择第一台的概率就是100/350.

  • RoundRobin LoadBalance:基于权重的轮训负载均衡机制 轮询,按照公约后的权重设置轮询比率。如果没有权重,还是userService分别在三台机器上,那么三台机器轮流轮流处理服务消费者的请求,加上权重后,三台机器权重分别为100、200、50,则三台机器占总权重的比例分别为2/7、4/7、1/7, 三台机器轮流处理服务消费者的请求,前三次请求轮流执行,第四次和第五分别到一和二号机器,但是第六次请求不会让第三台机器处理,因为三号机器权重比是1/7,而一号机器已经处理了两次请求,所以第六次请求会让第二台机器处理。存在慢的提供者累计请求的问题,比如第二台机器很慢但是没宕机,当请求调用到第二台就卡在哪里,久而久之,所有的请求都会卡在调用第二台上。

  • LeastActive LoadBalance:最少活跃数负载均衡机制。还是三台服务器处理服务消费者的请求,比如三台服务器上一次处理请求所花费的时间分别为100ms、1000ms、300ms,则这一次请求回去上一次处理请求时间最短的机器,所以这次一号服务器处理这次请求。

  • ConsistentHash LoadBalance:一致性Hash负载均衡机制。

Dubbo的服务降级

什么是服务降级

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

Dubbo实现服务降级的两种方式

  • mock=force:return+null:表示消费方对该服务的方法都返回null值,不发起远程调用。用来屏蔽不重要的服务不可用时对调用方的影响,可以直接在Dubbo客户端(localhost:7001)对服务消费者设置,屏蔽掉即可。
  • mock=fall:return+null:表示消费方对该服务的方法调用在失败后,再返回null,不抛出异常。用来容忍不重要服务不稳定时对调用方的影响,可以直接在Dubbo客户端(localhost:7001)对服务消费者设置,容错掉即可。

集群容错

集群容错是指在集群调用失败时,Dubbo提供了多种容错方案,默认为failover。

Dubbo支持的集群容错模式
  • Failover Cluster:失败自动切换,当出现失败,重试其他服务器。通常用于读操作,但重试会带来更长延迟。可通过retries=n来设置重试次数。
  • Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增操作。
  • Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多的服务资源。通过过fork=n设置最大并行数。
  • Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错,通常用于通知所有服务提供者更新缓存或日志等本地资源信息。

集群模式配置

<dubbo:servrice cluster="failsafe">
//或者
<dubbo:servrice cluster="failsafe">

整合hystrix

什么是hystrix

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

如何使用hystrix

  • 首先导入hystrix依赖

  • 在SpringBoot运行主类中添加@EnableHystrix表示用注解方式使用Hystrix。

  • 在服务生产者需要被Hystrix代理的服务(方法)上添加@HystrixCommand。

  • 在服务消费者的SpringBoot主类中添加@EnableHystrix表示用注解方式使用Hystrix

  • 在服务消费者需要远程调用服务生产者的对象出使用@HystrixCommand(fallbackMethod = "hello"),表示该方法出错了,调用fallbackMethod中的回调函数进行容错。

Dubbo原理

RPC和Netty的原理

一次完整的RPC调用流程(同步调用)
  • (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这些步骤都封装起来,这些细节对于用户来说是透明的、不可兼得

Dubbo底层进行通信时是使用了Netty框架

Netty通信原理

Netty是一个异步事件驱动的网络应用程序框架,是基于NIO的多路复用模型实现的。

NIO和BIO的区别

BIO又称为Blocking IO,是阻塞的IO,如下图,假设这是一个服务器,当有网络请求时,会开启一个socket处理请求,并进行相应的业务逻辑处理,在业务逻辑没有处理完成之前Thread是不会得到释放的,所以使用BIO的话是肯定不能处理大量请求的,当大量请求访问服务器时,会使得大量线程阻塞等待业务逻辑的完成。

Netty基本原理

Dubbo的框架设计

  • Business业务逻辑层

  • RPC层:完成远程调用的层 (1):config配置层:对外配置接口,以ServiceConfig,ReferenceConfig为中心,可以直接初始化配置类,也可以通过spring解析配置生成配置类。 (2):proxy服务代理层:服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton, 以ServiceProxy为中心,扩展接口为ProxyFactory。 (3):registry注册中心层:封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory,Registry、RegistryService。 (4):cluster路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster,Directory,Router,LoadBalance。 (5):monitor监控层:RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory,Monitor,MonitorService。 (6):protocol远程调用层:封装RPC调用,以Invocation,Result为中心,扩展接口为Protocol,Invoker,Exporter。

  • Remoting:解决远程通信的层。 (1):exchange信息交换层:封装请求响应模式,同步转异步,以Request,Response为中心,扩展接口为Exchanger,ExchangeChannel,ExchangeClient,ExchangeServer。 (2):transport网络传输层:抽象mina和Netty为统一接口,以Message为中心,扩展接口为Channel,Transporter,Client,Server,Codec。 (3):serialize数据序列化层:可复用的一些工具,扩展接口为Serialization,ObjectInput,ObjectOutput,ThreadPoll。

Dubbo的标签解析原理

我们将Dubbo的配置写在了xml配置文件中,而Dubbo的配置文件是一个Spring的配置文件,启动Dubbo项目也是以Spring的方式启动的,Spring通过总接口BeanDefinitionParser来解析xml配置文件中的标签。该接口是Spring的解析器。而DubboBeanDefinitionParser是Dubbo的标签解析器。DubboBeanDefinitionParser通过parse方法方法解析标签。每一个标签有与之对应的Config对象,所以说解析标签的目的就是将标签中的每一个属性解析处理,放入对应的Config对象中。