Dubbo基础入门学习笔记

基础知识

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

分布式系统(distributed system)是建立在网络之上的软件系统。
《分布式系统原理与范型》定义:
“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”

发展演变:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

RPC

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

RPC基本原理图:
在这里插入图片描述
在各个服务器组件建立连接的速度和系列化/反系列化的速度决定了RPC框架的性能好坏。

RPC的两大模块:通讯和系列化。

dubbo

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

官方网站:http://dubbo.apache.org/zh-cn/

dubbo的设计架构:
在这里插入图片描述
dubbo的特性:
在这里插入图片描述

注册中心

在这里插入图片描述
dubbo可以使用以下的支持中心,官方推荐使用zookeeper注册中心。
在这里插入图片描述

下载zookeeper

zookeeper官网:https://zookeeper.apache.org/

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下载后解压:
在这里插入图片描述
新建data文件夹:
在这里插入图片描述
进入conf目录:
在这里插入图片描述
然后打开zoo.cfg文件,修改配置:
把里面文件的存放文件路径改为刚刚建的data文件夹里:

dataDir=../data

打开终端,输入cd空格,然后把解压的目录拉到终端里,回车进入zookeeper目录,然后进入bin目录,输入./zkServer.sh start启动服务器
在这里插入图片描述
./zkCli.sh启动客户端
在这里插入图片描述
测试:
在这里插入图片描述
中途跑去补了一下zookeeper的理论,笔记写得不怎么样,有兴趣就看看。(Zookeeper入门学习笔记

zookeeper的基本命令

ls /:查看根目录
quit:退出客户端
./zkServer.sh stop:停止服务器
在这里插入图片描述

监控中心

dubbo本身并不是一个服务软件。它其实就是一个jar包能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。所以你不用在Linux上启动什么dubbo服务。
但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序,不过这个监控即使不装也不影响使用。

下载dubbo-admin

图形化的服务管理页面;安装时需要指定注册中心地址,即可从注册中心中获取到所有的提供者/消费者进行配置管理。

dubbo在github的网址:https://github.com/apache/dubbo
在这里插入图片描述

点进Dubbo-Admin后,默认是develop
分支,我们需要切换到master分支。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下载后解压
在这里插入图片描述
查看/dubbo-admin-master/dubbo-admin-master/dubbo-admin/src/main/resources目录下的application.properties文件。
在这里插入图片描述
从终端进入到dubbo-admin目录,输入mvn clean package清理后在打包。(前提需要安装配置好maven,安装maven的步骤
在这里插入图片描述
然后在dubbo-admin目录下就会生成一个target目录。
在这里插入图片描述
dubbo-admin-0.0.1-SNAPSHOT.jar就是刚刚打包生成的jar包。
在这里插入图片描述
终端进入target目录,输入java -jar dubbo-admin-0.0.1-SNAPSHOT.jar运行。
在这里插入图片描述
启动成功,默认是7001端口。(可以在application.properties修改)
在这里插入图片描述
然后在浏览器输入http://localhost:7001/,账号密码默认都是root。
在这里插入图片描述
在这里就可以看到开启的zk的服务器和客户端。
在这里插入图片描述
按control+c停止项目。
在这里插入图片描述
可以把jar放到桌面或任意点击到的地方,方便启动。

编写服务提供者和消费者

在这里插入图片描述
在这里插入图片描述

新建项目

创建一个空项目
在这里插入图片描述
在这里插入图片描述

新建模块

在这里插入图片描述

  • 创建服务提供者模块:
    创建一个空的maven项目
    在这里插入图片描述
    在这里插入图片描述

  • 再创建一个服务消费者模块:
    同样创建一个空的maven项目:
    在这里插入图片描述

  • 再创建一个公共的API模块(用于存放bean、接口、异常等公共类)
    同样创建一个空的maven项目:
    在这里插入图片描述
    在这里插入图片描述

  • gmall-interface公共的API模块

在main/java包下创建com/angenin/bean/UserAddress

/**
 * 用户地址
 */
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-否

	//有参无参构造器,get/set方法

在angenin包下新建service/UserService接口

/**
 * 用户服务
 */
public interface UserService {

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

}

在service包下再创建OrderService接口

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

复制这个公共API的依赖坐标
在这里插入图片描述

  • user-service-provider服务提供者

在pom.xml中引入上面公共API的依赖,上面复制的依赖要放到< dependency >标签里。

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>gmall-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

在main/java包下新建com/angenin/service/impl/UserServiceImpl

public class UserServiceImpl implements UserService {
    @Override
    public List<UserAddress> getUserAddressList(String userId) {
        UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
        UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");

        return Arrays.asList(address1,address2);
    }
}

引入dubbo

	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>dubbo</artifactId>
		<version>2.6.7</version>
	</dependency>

因为我们使用的注册中心是zookeeper,所以还需要导入操作zookeeper的客户端。(dubbo2.0以后使用的是curator)

	<dependency>
		<groupId>org.apache.curator</groupId>
		<artifactId>curator-framework</artifactId>
		<version>4.2.0</version>
	</dependency>

然后在resources下新建一个名为provider.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"
       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-com.angenin.service-provider"  />

    <!-- 2. 指定注册中心的位置 -->
<!--    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>-->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />

    <!-- 3. 指定通信规则(通信协议?通信端口)服务提供者和消费者之间的通信 -->
    <dubbo:protocol name="dubbo" port="20880" />


    <!-- 4. 暴露服务(interface暴露的是服务的接口,ref是指向服务真正的实现对象) -->
    <dubbo:service interface="com.angenin.service.UserService" ref="userServiceImpl" />

    <!-- 服务的实现 -->
    <bean id="userServiceImpl" class="com.angenin.service.impl.UserServiceImpl"/>
</beans>

然后在angenin包下新建MainApplication.java(进行测试)

public class MainApplication {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
        context.start();
        System.in.read(); // 按任意键退出
    }
}

运行main方法
在这里插入图片描述
出现错误:
在这里插入图片描述
引入netty依赖

   <dependency>
       <groupId>io.netty</groupId>
       <artifactId>netty-all</artifactId>
       <version>4.1.48.Final</version>
   </dependency>

然后重新运行main方法,到管理页面就可以看到提供者提供的服务。(保存此main方法一直运行着,为消费者提供服务)
在这里插入图片描述

  • order-service-consumer服务消费者

在pom.xml中引入上面公共API的依赖和dubbo、zookeeper客户端还有netty的依赖。

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>gmall-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.7</version>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.2.0</version>
        </dependency>

	   <dependency>
	       <groupId>io.netty</groupId>
	       <artifactId>netty-all</artifactId>
	       <version>4.1.48.Final</version>
	   </dependency>
    </dependencies>

在main/java包下新建com/angenin/service/impl/OrderServiceImpl

@Service    //是spring的注解,不是dubbo的
public class OrderServiceImpl implements OrderService {

    @Autowired
    UserService userService;

    @Override
    public List<UserAddress> initOrder(String userId) {
        System.out.println("用户id:" + userId);
        //1、查询用户的收货地址
        List<UserAddress> addressList = userService.getUserAddressList(userId);
        for (UserAddress userAddress : addressList) {
            System.out.println(userAddress.getUserAddress());
        }
        return addressList;
    }
}

在resources目录下新建名为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-4.3.xsd
       http://dubbo.apache.org/schema/dubbo
       http://dubbo.apache.org/schema/dubbo/dubbo.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.angenin.service.impl"/>

    <dubbo:application name="order-service-consumer"  />

    <dubbo:registry address="zookeeper://127.0.0.1:2181" />

    <!-- 声明需要调用的远程服务的接口(生成远程服务代理)interface引用提供者暴露的接口 -->
    <dubbo:reference id="userService" interface="com.angenin.service.UserService" />
</beans>

在angenin包下新建MainApplication.java(进行测试)

public class MainApplication {

    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");

        OrderService bean = context.getBean(OrderService.class);
        bean.initOrder("1");
        System.out.println("调用完成");
        System.in.read();
    }

}

运行main方法
在这里插入图片描述
在这里插入图片描述
在管理页面可以看到服务的提供者和消费者。
在这里插入图片描述

dubbo-monitor-simple

简单的监控中心。

使用终端进入dubbo-monitor-simple目录
在这里插入图片描述
mvn package命令进行打包
在这里插入图片描述
在这里插入图片描述
打包完成后,生成target目录,因为我们需要修改配置,所以不能直接使用java -jar运行生成的jar包,需要解压jar包对应的.tar.gz压缩包。
在这里插入图片描述
进入解压后目录的conf目录,确保dubbo.properties配置文件的注册中心地址正确即可。(8080端口容易被占用,建议修改,我改成了8090)在这里插入图片描述
然后用终端进入assembly.bin目录,输入./start.sh启动。
在这里插入图片描述
如果用http://localhost:8080访问不了,就用本机的ip加端口号访问。
在这里插入图片描述
在管理页面可以看到启动的simple-monitor。
在这里插入图片描述
如果想让其能监控到服务的调用信息,需要在项目中进行一些配置。

到order-service-consumer项目的consumer.xml文件中添加:

    <!--表示从注册中心发现监控中心地址-->
    <dubbo:monitor protocol="registry"/>
    <!--  直连监控中心  -->
<!--    <dubbo:monitor address="192.168.0.106:7070"/>-->

在user-service-provider项目的provider.xml中也要添加:

    <!--连接监控中心-->
    <dubbo:monitor protocol="registry"/>

然后重新启动提供者和消费者的main方法,然后在监控中心的管理页面就可以看到了。(需要先启动提供者)
在这里插入图片描述

与springboot进行整合

新建模块
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
不选模块,直接创建,然后再新建一个模块
在这里插入图片描述
选择web模块
在这里插入图片描述
在这里插入图片描述

  • boot-user-server-provider的provider

把user-service-provider的service包复制到boot-user-server-provider的provider包下
在这里插入图片描述
然后在pom.xml中引入公共API(gmall-interface)的依赖坐标

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

导入dubbo-starter依赖和zookeeper的客户端依赖

  <dependency>
      <groupId>org.apache.dubbo</groupId>
      <artifactId>dubbo-spring-boot-starter</artifactId>
      <version>2.7.6</version>
  </dependency>
  
  <dependency>
       <groupId>org.apache.curator</groupId>
       <artifactId>curator-framework</artifactId>
       <version>4.2.0</version>
   </dependency>

   <dependency>
       <groupId>org.apache.curator</groupId>
       <artifactId>curator-recipes</artifactId>
       <version>4.2.0</version>
   </dependency>

在resources目录下的application.properties对dubbo进行配置

#根据之前的xml配置的内容进行相应的配置
dubbo.application.name=boot-user-server-provider

dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper

dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

dubbo.monitor.protocol=registry

#暴露服务不需要在这里配置,只需要在要暴露的类上加上dubbo的@Service注解

在UserServiceImpl类上加上两个@Service注解

@org.apache.dubbo.config.annotation.Service //暴露服务
@Service    //添加到容器中

在主配置类上加上@EnableDubbo,开启基于注解的dubbo。

启动项目后,管理页面出现服务提供者
在这里插入图片描述
在这里插入图片描述

  • boot-order-service-consumer

在pom.xml中引入依赖(同上)

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

  <dependency>
      <groupId>org.apache.dubbo</groupId>
      <artifactId>dubbo-spring-boot-starter</artifactId>
      <version>2.7.6</version>
  </dependency>
  
  <dependency>
       <groupId>org.apache.curator</groupId>
       <artifactId>curator-framework</artifactId>
       <version>4.2.0</version>
   </dependency>

   <dependency>
       <groupId>org.apache.curator</groupId>
       <artifactId>curator-recipes</artifactId>
       <version>4.2.0</version>
   </dependency>

在resources目录下的application.properties对dubbo进行配置

server.port=8081	#把项目的端口号改为8081,不改的话会和其他别的冲突

dubbo.application.name=boot-order-service-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.monitor.protocol=registry

#声明需要调用的远程服务的接口可以不需要在这里配置了,可以直接使用@Reference远程调用

在主配置类上加上@EnableDubbo注解。

把order-service-consumer的server包放到boot-order-service-consumer的consumer包下。
然后在consumer包下新建controller/OrderController,用于查询。

@RestController
public class OrderController {

    @Autowired
    @Reference  //使用dubbo的@Reference注解远程调用
    OrderService orderService;

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

}

启动项目
在这里插入图片描述
在这里插入图片描述

dubbo配置

dubbo的配置参数

官方文档:http://dubbo.apache.org/zh-cn/docs/user/references/xml/introduction.html
在这里插入图片描述
在这里插入图片描述

配置来源

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

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

覆盖关系

下图展示了配置覆盖关系的优先级,从上到下优先级依次降低:
在这里插入图片描述

配置种类

在官方文档的实例里列举了所有配置种类:启动时检查、集群容错、负载均衡、线程模型、直连提供者、只订阅、只注册、静态服务、多协议等等。

启动时检查

消费者启动后,会到注册中心寻找需要用到的服务(即使还没使用,但远程调用了),没找到就会报错(默认 check=“true”),把check改为false就可以跳过这个检查。

不启动user-service-provider提供者,直接启动order-service-consumer消费者后,报错:

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'orderServiceImpl': Unsatisfied dependency expressed through field 'userService'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Failed to check the status of the service com.angenin.service.UserService. No provider available for the service com.angenin.service.UserService from the url zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=order-service-consumer&dubbo=2.0.2&interface=com.angenin.service.UserService&methods=getUserAddressList&pid=39633&register.ip=192.168.0.106&side=consumer&timestamp=1590999460690 to the consumer 192.168.0.106 use dubbo version 2.6.7

注释掉消费者中使用服务的代码,然后在consumer.xml配置文件的dubbo:reference标签中加入check="false"。(@Reference中也有check属性可以设置)
在这里插入图片描述
在这里插入图片描述
再次运行消费者的main方法,没有报错:
在这里插入图片描述
加上check=false后,启动后不会去注册中心检查,只有使用到服务并且没找到才会报错。

也可以使用

    <!--  配置当前消费者的统一规则:所有的服务都不检查  -->
    <dubbo:consumer check="false"/>

在这里插入图片描述

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

<dubbo:registry check="false" />

springboot中的dubbo.properties(application.properties)

dubbo.reference.com.foo.BarService.check=false
dubbo.reference.check=false
dubbo.consumer.check=false
dubbo.registry.check=false
timeout属性

timeout=“xxxx”(返回时间,单位毫秒,默认1000毫秒),超过这个时间还没返回就会立即中止连接,防止阻塞,造成性能下降。

我们给order-service-consumer的配置文件中dubbo:reference加上timeout="3000"并且把main方法中调用服务的注释去掉。
然后给user-service-provider的UserServiceImpl类的getUserAddressList方法加上Thread.sleep(4000);

启动提供者,在启动消费者。
在这里插入图片描述

Exception in thread "main" com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method getUserAddressList in the service com.angenin.service.UserService. Tried 3 times of the providers [192.168.0.106:20880] (1/1) from the registry 127.0.0.1:2181 on the consumer 192.168.0.106 using the dubbo version 2.6.7. Last error is: Invoke remote method timeout. method: getUserAddressList, provider: dubbo://192.168.0.106:20880/com.angenin.service.UserService?anyhost=true&application=order-service-consumer&bean.name=com.angenin.service.UserService&check=false&default.check=false&dubbo=2.0.2&generic=false&interface=com.angenin.service.UserService&methods=getUserAddressList&monitor=dubbo%3A%2F%2F127.0.0.1%3A2181%2Fcom.alibaba.dubbo.registry.RegistryService%3Fapplication%3Dorder-service-consumer%26dubbo%3D2.0.2%26pid%3D40284%26protocol%3Dregistry%26refer%3Dapplication%253Dorder-service-consumer%2526dubbo%253D2.0.2%2526interface%253Dcom.alibaba.dubbo.monitor.MonitorService%2526pid%253D40284%2526register.ip%253D192.168.0.106%2526timestamp%253D1591000941631%26registry%3Dzookeeper%26timestamp%3D1591000941614&pid=40284&register.ip=192.168.0.106&remote.timestamp=1591000753808&side=consumer&timeout=3000&timestamp=1591000936534, cause: Waiting server-side response timeout. start time: 2020-06-01 16:42:45.373, end time: 2020-06-01 16:42:48.378, client elapsed: 1 ms, server elapsed: 3004 ms, timeout: 3000 ms, request: Request [id=2, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=getUserAddressList, parameterTypes=[class java.lang.String], arguments=[1], attachments={path=com.angenin.service.UserService, interface=com.angenin.service.UserService, version=0.0.0, timeout=3000}]], channel: /192.168.0.106:58227 -> /192.168.0.106:20880

timeout="3000改为5000,再启动,成功查出数据。
在这里插入图片描述

dubbo:method给指定方法的设置属性。(优先于在其他标签配置)

    <dubbo:method name="getUserAddressList" timeout="2000"/>

需要加到dubbo:reference标签中:
在这里插入图片描述
再次重新启动,因为我们在这里给getUserAddressList方法的返回时间设置为2秒,所以保错。
在这里插入图片描述

Exception in thread "main" com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method getUserAddressList in the service com.angenin.service.UserService. Tried 3 times of the providers [192.168.0.106:20880] (1/1) from the registry 127.0.0.1:2181 on the consumer 192.168.0.106 using the dubbo version 2.6.7. Last error is: Invoke remote method timeout. method: getUserAddressList, provider: dubbo://192.168.0.106:20880/com.angenin.service.UserService?anyhost=true&application=order-service-consumer&bean.name=com.angenin.service.UserService&check=false&default.check=false&dubbo=2.0.2&generic=false&getUserAddressList.timeout=2000&interface=com.angenin.service.UserService&methods=getUserAddressList&monitor=dubbo%3A%2F%2F127.0.0.1%3A2181%2Fcom.alibaba.dubbo.registry.RegistryService%3Fapplication%3Dorder-service-consumer%26dubbo%3D2.0.2%26pid%3D40647%26protocol%3Dregistry%26refer%3Dapplication%253Dorder-service-consumer%2526dubbo%253D2.0.2%2526interface%253Dcom.alibaba.dubbo.monitor.MonitorService%2526pid%253D40647%2526register.ip%253D192.168.0.106%2526timestamp%253D1591001688537%26registry%3Dzookeeper%26timestamp%3D1591001688528&pid=40647&register.ip=192.168.0.106&remote.timestamp=1591000753808&side=consumer&timeout=5000&timestamp=1591001683452, cause: Waiting server-side response timeout. start time: 2020-06-01 16:55:10.215, end time: 2020-06-01 16:55:12.220, client elapsed: 3 ms, server elapsed: 2002 ms, timeout: 2000 ms, request: Request [id=2, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=getUserAddressList, parameterTypes=[class java.lang.String], arguments=[1], attachments={path=com.angenin.service.UserService, interface=com.angenin.service.UserService, version=0.0.0, timeout=5000}]], channel: /192.168.0.106:58357 -> /192.168.0.106:20880

xml中各个标签的覆盖关系:(其他属性遵循这个优先级)

  • 方法级优先,接口级次之,全局配置再次之。(精确优先)
  • 如果级别一样,则消费方优先,提供方次之。(同级别时消费者优先,如果提供者级别高(方法级别),消费者级别低(全局级别),那么提供者优先)
    在这里插入图片描述
retries属性

retries=“xx”(重试次数,默认2次(不包含第一次调用,即第一次调用失败后的重试次数)),设置连接失败后给予重试的次数,如果是0表示不重试。
幂等:设置重试次数。(查询、删除、修改(重复多次效果都一样的操作))
非幂等:不能设置重试次数。(新增(每次都会做改变的操作))
order-service-consumer的dubbo:reference标签中加入retries="3"
然后在user-service-provider的UserServiceImpl类getUserAddressList方法里加上System.out.println("请求次数");。(此时UserServiceImpl每次会睡3秒,而消费者只给2秒的返回时间)
重启提供者,再启动消费者。
消费者方因为返回时间超时报错
在这里插入图片描述
提供者方打印了四次(也说明了,retries不包含第一次连接)
在这里插入图片描述

多版本

灰度发布:发布新版本时,先让一部分用户使用新版本,其他用户进行使用旧版本,等新版本用了一段时间后,没出现问题,再全部改为使用新版本。

在user-service-provider中复制一份UserServiceImpl,让旧版本打印old,新版本打印new。
在这里插入图片描述

在配置文件修改和添加新旧版本的服务

    <dubbo:service interface="com.angenin.service.UserService" ref="userServiceImpl01" version="1.0.0" />
    <bean id="userServiceImpl01" class="com.angenin.service.impl.UserServiceImpl"/>

    <!--    新版本-->
    <dubbo:service interface="com.angenin.service.UserService" ref="userServiceImpl02" version="2.0.0" />
    <bean id="userServiceImpl02" class="com.angenin.service.impl.UserServiceImpl2"/>

然后就可以在order-service-consumer配置文件中的dubbo:reference标签中用version="2.0.0"指定使用新版本。

启动提供者,再启动消费者。
在这里插入图片描述
version="*"随机使用。
在这里插入图片描述

本地存根

消费者远程调用时,本地存根先对调用的方法或参数进行判断,也可以增加一些功能,然后再由本地存根决定是否要进行远程调用。
在这里插入图片描述
创建本地存根:
在gmall-interface的server包下新建impl/UserServiceStub的UserService的实现类(一般都是放在公共的API中)

public class UserServiceStub implements UserService {

    //新建调用的接口对象
    private final UserService userService;

    //必须要有有参构造器,由dubbo传入userService远程的代理对象
    public UserServiceStub(UserService userService) {
        this.userService = userService;
    }

    @Override
    public List<UserAddress> getUserAddressList(String userId) throws Exception {
        System.out.println("UserServiceStub");
        if(!"".equals(userId)){
            return userService.getUserAddressList(userId);
        }
        return null;
    }
}

然后在order-service-consumer的consumer.xml配置文件中的dubbo:reference标签里添加stub="com.angenin.service.impl.UserServiceStub"(UserServiceStub的全类名)
启动提供者,启动消费者,本地存根有被调用:
在这里插入图片描述

springboot与dubbo的三种整合方式

  1. 导入dubbo-starter和zookeeper客户端,给主配置类加上@EnableDubbo注解,在application.properties中配置属性,使用@Service暴露服务,使用@Reference引用服务。

  2. 导入dubbo-starter和zookeeper客户端,使用dubbo的xml配置文件,在主配置类上加上@ImportResource(locations = "classpath:provider.xml")注解,把provider.xml配置文件导入。(主配置类就不需要加@EnableDubbo注解,也不用使用@Service和@Reference,因为已经在xml中配置了)

  3. 使用配置类,把每个组件手动创建到容器中,在主配置类上添加@EnableDubbo(scanBasePackages = "com.example.provider.config")注解,开启基于注解的Dubbo并扫描dubbo的配置类,使用@Service暴露服务,使用@Reference引用服务。

dubbo的配置类

@Configuration  //往容器中注册 xxxConfig 代替 dubbo:xxx 标签
public class MyDubboConfig {

    @Bean   //替代dubbo:application标签
    public ApplicationConfig applicationConfig(){
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("boot-user-server-provider");
        return applicationConfig;
    }

    @Bean   //替代dubbo:registry
    public RegistryConfig registryConfig(){
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("zookeeper://127.0.0.1:2181");
        //也可以
//        registryConfig.setProtocol("zookeeper");
//        registryConfig.setAddress("127.0.0.1:2181");
        return registryConfig;
    }

    @Bean   //替代dubbo:protocol
    public ProtocolConfig protocolConfig(){
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName("dubbo");
        protocolConfig.setPort(20880);
        return protocolConfig;
    }

    @Bean   //替代dubbo:service   泛型为暴露的接口
    public ServiceConfig<UserService> userServiceConfig(UserService userService){
        ServiceConfig<UserService> userServiceConfig = new ServiceConfig<>();
        userServiceConfig.setInterface(UserService.class);
        //需要将提供者UserService的实现类添加到容器中(这个组件在创建时就会从容器中寻找并传入)
        userServiceConfig.setRef(userService);
        userServiceConfig.setVersion("1.0.0");

        //配置每一个method的信息(dubbo:method标签)
        MethodConfig methodConfig = new MethodConfig();
        methodConfig.setName("getUserAddressList");
        methodConfig.setTimeout(2000);

        //将method的设置关联到service配置中
        List<MethodConfig> methods = new ArrayList<>();
        methods.add(methodConfig);
        userServiceConfig.setMethods(methods);

        return userServiceConfig;
    }


    @Bean
    public MonitorConfig monitorConfig(){
        MonitorConfig monitorConfig = new MonitorConfig();
        monitorConfig.setProtocol("registry");
        return monitorConfig;
    }
}

高可用

zookeeper宕机与dubbo直连

现象:zookeeper注册中心宕机,还可以消费dubbo暴露的服务。
原因:
健壮性

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

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

集群下dubbo负载均衡配置

在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。
修改负载均衡策略:官方示例
使用loadbalance属性可以修改负载均衡策略。
有四种可以选择:random(默认)、roundrobin、leastactive、consistenthash

在dubbo的@service注解里可以使用weight属性设置权重。(不过这样是固定的)
可以在管理页面动态修改提供者的权重值。
在这里插入图片描述

负载均衡策略

1. Random LoadBalance

随机,按权重设置随机概率。
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。(随机访问,设置权重可以增加或减小概率)
在这里插入图片描述

2. RoundRobin LoadBalance

轮循,按公约后的权重设置轮循比率。
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。(按照顺序访问,可以设置权重增加或减少被轮循到的次数)
在这里插入图片描述

3. LeastActive LoadBalance

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。(访问前先查询每一个上一次响应的速度,然后访问上一次响应速度最快的)
在这里插入图片描述

4. 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” />
在这里插入图片描述

服务降级

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

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

其中:

  • mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。(调用时直接返回null,不处理)
    在管理页面点击指定消费者的屏蔽即可:
    在这里插入图片描述
  • 还可以改为 mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。(调用失败后返回null,不重试)
    在管理页面点击指定消费者的容错即可:
    在这里插入图片描述

集群容错

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

集群容错模式

  • 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具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。
(Hystrix在springcloud的视频里会详细讲)

  1. 导入hystrix-starter依赖
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
     </dependency>
    
  2. 在主配置类上加上@EnableHystrix注解,开启访服务容错。(提供者和消费者都有导入依赖,加注解开启服务容错)
  3. 在提供者暴露服务的方法上加上@HystrixCommand注解把方法交给Hystrix进行代理,然后出现异常,就会进行容错。
  4. 在消费者进行远程调用的方法上加上@HystrixCommand(fallbackMethod = "xxx")注解,当远程调用的方法出现异常时,回调xxx方法(xxx方法在消费者远程调用方法同一个类中)。

原理

RBC原理

在这里插入图片描述
一次完整的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这些步骤都封装起来,这些细节对用户来说是透明的,不可见的。

netty通信原理

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

BIO:(Blocking IO(阻塞IO))

在这里插入图片描述

NIO (Non-Blocking IO(非阻塞IO))

在这里插入图片描述
Selector一般称为选择器 ,也可以翻译为多路复用器,Selector监听事件(通道的姿态),连接成功会做什么,就绪了做什么,读就绪和写就绪了做什么,都由Selector来监听并处理。
通道的状态有:Connect(连接就绪)、Accept(接受就绪)、Read(读就绪)、Write(写就绪)。

netty的基本原理

netty就是基于NIO多路复用模型。
在这里插入图片描述

dubbo原理

框架设计

在这里插入图片描述
官方文档:http://dubbo.apache.org/zh-cn/docs/dev/design.html

启动解析、加载配置信息

在spring中由DubboBeanDefinitionParser(bean标签解析器)的parse方法对dubbo的xml配置文件的标签进行解析,解析完后放到对应的xxxConfig里(ServiceBean和ReferenceBean不同)。

在DubboNamespaceHandler(Dubbo的名称空间处理器)的init方法中注册了很多的标签解析器。

    public void init() {
        this.registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        this.registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        this.registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        this.registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        this.registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        this.registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        this.registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        this.registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        this.registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        this.registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }

ServiceBean的解析牵扯到服务暴露的过程,如何把服务注册到注册中心,让别人来引用。
ReferenceBean的解析牵扯到服务引用的过程。

服务暴露

在这里插入图片描述
上面是从文档里拿的完整的图,不太清晰;下面是视频中截取的。
在这里插入图片描述
在这里插入图片描述
ServiceBean实现了InitializingBean和ApplicationListener< ContextRefreshedEvent >。
当组件创建完后会来调用InitializingBean里面的afterPropertiesSet方法。
当IOC容器刷新完成,IOC容器里所有对象都创建完后,会回调ApplicationListener的onApplicationEvent方法。

  • 即:ServiceBean会在容器创建完对象后,调用afterPropertiesSet方法,还会在IOC容器准备完成以后调用onApplicationEvent方法。
    • 在afterPropertiesSet方法中,把之前配置的xxxConfig信息都保存到ServiceBean中。
    • 在onApplicationEvent方法中,同过如果通过if(this.isDelay() && !this.isExported() && !this.isUnexported())判断后,会调用export方法来暴露服务。
      • 由doExportUrls方法来暴露url地址。
        • Protocol的export来暴露执行器,主要是DubboExporter和RegistryExporter。
          • DubboExporter的openServer方法开启服务器。
          • RegistryExporter会把每一个服务以及它的url地址对应信息保存到ProviderConsumerRegTable注册表里,以后远程调用url地址,按照注册表里的url地址拿到对应的执行器,实现远程调用。

在这里插入图片描述

服务引用

在这里插入图片描述
文档里的图不清晰,下面从视频中截取的图比较清晰。
在这里插入图片描述
服务引用主要是dubbo:reference标签,而这个标签对应ReferenceBean(是一个FactoryBean)。

  • ReferenceBean通过getObject调用get方法,获取对象。

    • get在init方法中调用createProxy方法,创建代理对象。
      • createProxy方法中,refprotocol.refer方法从注册中心获取想远程引用的接口。(refprotocol还是DubboExporter和RegistryExporter这两个)
        • RegistryExporter中的refer根据url得到注册中心的信息,给doRefer方法传入注册中心的信息,引用的接口和注册中心的地址。

          • doRefer中的directory.subscribe订阅注册中心服务提供者提供的服务。
        • 然后来到DubboExporter中的refer方法执行远程引用,通过getClients根据url获取客户端,新建一个invoker。

          • getClients方法里根据连接数新建客户端,然后initClient方法获取url地址然后进行初始化。
            • initClient方法中通过层层的connect获取到一个NettyClient来监听url。

          订阅完成后,获取到注册中心返回的invoker,然后把这个invoker和其他信息(如url)一起保存到ProviderConsumerRegTable注册表里,然后把invoker代理对象层层返回。

          ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
          

    ReferenceBean通过getObject调用get方法,获取invoker代理对象。
    在这里插入图片描述

服务调用

在这里插入图片描述
这个代理对象进行方法调用的过程:

  • 调用时首先来到InvokerInvocationHandler的invoke方法获取到要执行的方法信息和参数信息,然后把要执行的方法信息和参数信息封装到一个RpcInvocation对象中传给invoke方法(MockClusterInvoker类的invoke)。
    • 然后在这个invoke方法里在调用一个invoke方法。(AbstractClusterInvoker的invoke)
      • 在这个invoke里有一个list(invocation)会在注册中心找有几个invoke可以执行(不同版本),这里有找到了两个可以执行的方法(1版本和2版本)。在这里插入图片描述
      • 然后把获取到负载均衡机制(random)放到loadbalance对象中,然后调用doInvoke方法。
        在这里插入图片描述
        • doInvoke方法中把负载均衡机制和可执行的invoke传给select,选出要执行的invoke。
          在这里插入图片描述
          • 然后继续调用invoke进入层层filter中的invoke,最后从AbstractInvoker(抽象的invoker)的doInvoke方法进入DubboInvoker的doInvoke。
            • doInvoke方法先获取方法的信息,然后会拿到客户端(客户端在服务引用时暴露过一次),这个客户端保存了我们要连上哪个服务,然后获取到的这个客户端会通过request方法发起请求并get获取到返回的结果并层层返回(如果超时就会报错然后进行重试从select方法拿重新选出要执行的invoke再进行以上步骤,也会换客户端)。

总结:层层包装,底层用netty进行通信。

番外(自己点进去看的,不一定写得对):
客户端的request->ReferenceCountExchangeClient的request->HeaderExchangeChannel的request
HeaderExchangeChannel的request会创建一个Request对象,把信息传入到这个对象后,channel.send(req)发送出去。

能设计出这样的框架真厉害,世界上牛人真的太多了,每次接触源码都感觉自己稍微前进了一点,变强了一点。
雷老师:学无止境,希望大家不要停止探索的步伐。
就算我们不能成为很厉害的人,但我们也不应停下脚步,一点一点的拉近与牛人间的差距。学无止境,祝各位学业有成,互利共勉。

学习视频:https://www.bilibili.com/video/BV1ns411c7jV?p=1

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值