Java进阶-Dubbo

简介

  1. 为什么要使用分布式?
    1. 为了性能扩展——系统负载高,单台机器无法承载,希望通过使用多台机器来提高系统的负载能力(更多是局部服务的负载能力,比如商品查询服务相对支付服务更需要负载)
    2. 为了增强可靠性——软件不是完美的,网络不是完美的,甚至机器本身也不可能是完美的,随时可能会出错,为了避免故障,需要将业务分散开保留一定的冗余度(一个服务暂时不用,不影响到其他服务正常使用。比如支付服务暂时不可用,不影响账单流水查询服务);
  2. 简介Dubbo
    1. dubbo是一个阿里巴巴开发的开源分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,是阿里巴巴集团的各成员站点的核心框架,每天为2,000+个服务提供3,000,000,000+次访问量支持,2017年7月贡献给 Apache 基金会。阿里自己用HSF;
    2. 当当网(dubboX):在Dubbo的基础上面增加了RESTful功能和FST序列化支持;
    3. 京东(jd-hydra):在Dubbo的基础上的一个分布式跟踪系统;
  3. 查看官方文档:https://dubbo.apache.org/zh/,官方文档写的非常好,可以参考

Dubbo入门

maven项目使用dubbo

  1. 创建父模块10-dubbo-demo
    1. 新建一个项目(new Module)-》选择Maven->next->name: 10-dubbo-demo Atrifact coordinates展开:groupid: com.zh.demo Atrifactid:dubbo-demo -> finished
    2. 由于该项目仅仅是用来管理子项目的,因此在pom.xml文件中添加

       <packaging>pom</packaging>
      
    3. 删除src文件夹,只留pom文件
    4. pom添加依赖

       <!--控制所有子项目依赖jar包的版本统一管理-->
       <dependencyManagement>
           <dependencies>
               <dependency>
                   <groupId>com.zh.demo</groupId>
                   <artifactId>demo-api</artifactId>
                   <version>1.0-SNAPSHOT</version>
               </dependency>
               <dependency>
                   <groupId>org.projectlombok</groupId>
                   <artifactId>lombok</artifactId>
                   <version>1.18.12</version>
               </dependency>
           </dependencies>
       </dependencyManagement>
       <dependencies>
           <dependency>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok</artifactId>
           </dependency>
       </dependencies>
              
       //配置jdk为8
       <build>
           <plugins>
               <plugin>
                   <groupId>org.apache.maven.plugins</groupId>
                   <artifactId>maven-compiler-plugin</artifactId>
                   <configuration>
                       <source>1.8</source>
                       <target>1.8</target>
                   </configuration>
               </plugin>
           </plugins>
       </build>
      
  2. 创建demo-api模块:点击10-dubbo-demo,右击->new->Module->Maven->next->demo-api->finish
  3. 同理创建demo-server、demo-client2个模块:在这两个模块添加对demo-api的依赖,以及dubbo依赖

     <properties>
         <dubbo.version>2.7.6</dubbo.version>
     </properties>
     <dependencies>
         <dependency>
             <groupId>com.zh.demo</groupId>
             <artifactId>demo-api</artifactId>
         </dependency>
         <!--dubbo依赖添加-->
         <dependency>
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo</artifactId>
             <version>${dubbo.version}</version>
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-dependencies-zookeeper</artifactId>
             <version>${dubbo.version}</version>
             <type>pom</type>
         </dependency>
     </dependencies>
    
  4. demo-api模块:添加接口类IHelloService

     package com.zh.demo;
     public interface IHelloService {
         public String greet(String name);
     }
    
  5. demo-server模块
    1. 添加实现类HelloServiceImpl

       package com.zh.demo;
       public class HelloServiceImpl implements IHelloService {
           @Override
           public String greet(String name) {
               System.out.println("name = " + name);
               return "hello:"+name;
           }
       }
      
    2. 在resources添加dubbo配置文件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.xsd
              http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
           <!--1 配置应用名字: 方便应用的监控管理-->
           <dubbo:application name="demo-server"/>
           <!--2 配置注册中心的地址:zk的服务地址-->
           <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
           <!--3 配置当前服务的通信的协议和通信端口,就是rpc使用的协议-->
           <dubbo:protocol name="dubbo" port="20880"/>
           <!--4 配置提供服务的Bean对象,在容器中添加对象-->
           <bean id="helloService" class="com.zh.demo.HelloServiceImpl"/>
           <!--5 申明提供的服务,ref: 具体引用容器中的那个对象,就是上面配置的那个实现 -->
           <dubbo:service interface="com.zh.demo.IHelloService" ref="helloService"/>
       </beans>
      
    3. 添加启动类,加载配置文件-AppServer

       package com.zh.demo;
       import org.springframework.context.support.ClassPathXmlApplicationContext;
       public class AppServer {
              
           public static void main(String[] args) throws Exception {
               /*专门用于读取配置文件*/
               //1 加载配置
               ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:provider.xml");
               //2 启动容器
               context.start();
               //3 阻塞, 服务一直运行
               System.in.read();
           }
       }
      
  6. demo-client模块
    1. 添加配置文件consumer.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.xsd
              http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
              
           <!--1 配置应用名称-->
           <dubbo:application name="demo-client"/>
           <!--2 配置注册中心-->
           <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
           <!--3 引入服务-->
           <dubbo:reference id="helloService" interface="com.zh.demo.IHelloService" />
       </beans>
      
    2. 添加启动类,加载配置文件-AppClient

       package com.zh.demo;
       import org.springframework.context.support.ClassPathXmlApplicationContext;
       public class AppClient {
           public static void main(String[] args) {
               //1 加载配置
               ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:consumer.xml");
               //2 启动容器
               context.start();
               IHelloService helloService= (IHelloService) context.getBean("helloService");
               String result = helloService.greet("tom");
               System.out.println("result = " + result);
           }
       }
      
  7. 测试:启动zk、启动AppServer、启动AppClient,结果:正常调用方法
  8. 官方文档查看:打开官方文档->github,点击查看Dubbo ecosystem 下的demo示例

springboot项目使用dubbo

  1. 创建父模块11-dubbo-sprintboot-demo
    1. 新建一个项目(new Module)-》选择Maven->next->name: 11-dubbo-sprintboot-demo Atrifact coordinates展开:groupid: com.zh.demo Atrifactid:dubbo-demo -> finished
    2. 由于该项目仅仅是用来管理子项目的,因此在pom.xml文件中添加

       <packaging>pom</packaging>
      
    3. 删除src文件夹,只留pom文件
  2. 创建demo-api模块(跟上面一样maven项目)
    1. 将上面的IHelloService直接拖到这个项目
  3. 创建demo-server、demo-client2个模块,为springboot项目
    1. 将HelloServiceImpl拖入到demo-server项目
    2. pom添加dubbo依赖

       <properties>
           <java.version>1.8</java.version>
           <spring-boot.version>2.3.0.RELEASE</spring-boot.version>
           <dubbo.version>2.7.6</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>
          
           </dependencies>
       </dependencyManagement>
       <dependencies>
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-web</artifactId>
           </dependency>
          
           <dependency>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok</artifactId>
               <optional>true</optional>
           </dependency>
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-test</artifactId>
               <scope>test</scope>
           </dependency>
           <dependency>
               <groupId>com.zh.demo</groupId>
               <artifactId>demo-api2</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.dubbo</groupId>
               <artifactId>dubbo-dependencies-zookeeper</artifactId>
               <version>${dubbo.version}</version>
               <type>pom</type>
           </dependency>
       </dependencies>
      
  4. demo-client模块
    1. 配置文件application.properties配置如下

       server.port=8000
       dubbo.application.name=demo-client2
       dubbo.registry.address=zookeeper://127.0.0.1:2181
      
    2. 新增HelloController类,bean对象导入用@Reference注解

       package com.zh.demo;
       import org.apache.dubbo.config.annotation.Reference;
       import org.springframework.web.bind.annotation.RequestMapping;
       import org.springframework.web.bind.annotation.RestController;
       @RestController
       public class HelloController {
              
           //使用dubbo的注解,注入这个实例
           @Reference
           private IHelloService helloService;
           @RequestMapping("say")
           public String say(String name){
               return helloService.greet(name);
           }
       }
      
  5. demo-server模块
    1. 配置文件application.properties配置如下

       server.port=7000
       dubbo.application.name=demo-server2
       dubbo.registry.address=zookeeper://127.0.0.1:2181
       # 生产者服务伴随网络服务的端口,往zk中注册的是这个网络服务的host+port(20881),并非当前服务的host+port(7000)
       dubbo.protocol.port=20881
       dubbo.protocol.name=dubbo
       # 注意要配置注解扫描包
       dubbo.scan.base-packages=com.zh.demo
      
    2. HelloServiceImpl实现使用dubbo的@Service注解

       package com.zh.demo;
       import org.apache.dubbo.config.annotation.Service;
       //这个注解是dubbo的
       @Service
       public class HelloServiceImpl implements IHelloService {
           @Override
           public String greet(String name) {
               System.out.println("name = " + name);
               return "hello:"+name;
           }
       }
      
  6. 测试:启动zk、启动DemoServerApplication、启动DemoClientApplication,浏览器访问:http://localhost:8000/say?name=tom 正常访问
  7. 官方文档查看
    1. 打开官方文档->github->找到Dubbo Spring Boot ,点击查看demo示例
    2. 官方文档非常详细,使用时用官方文档即可:https://dubbo.apache.org/zh/docsv2.7/user/quick-start/

高级进阶

主要是讲解dubbo的一些重要配置参数的使用 下面讲解的所有配置功能,在官方文档:https://dubbo.apache.org/zh/docsv2.7/user/examples/可以查看

Dubbo Admin使用

  1. dubbo有一个后台管理端,可以监控某个应用使用dubbo的情况
    1. 使用地址在官网github代码https://github.com/apache/dubbo,下的Dubbo ecosystem->Dubbo Admin ,可以具体查看如何使用
    2. 使用流程:下载源码、打包jar包、运行jar包,然后浏览器可以访问可视化页面
  2. 项目案例
    1. 新建maven父项目12-shop-demo
    2. 新建product、order2个模块,同理也是maven项目、而且是父项目
    3. product:新建product-api的maven项目、product-server的springboot项目,product-server的pom文件添加对product-api的依赖
    4. order:新建order-api的maven项目、order-server的springboot项目,order-server的pom文件添加对order-api、product-api的依赖
    5. product-server、order-server2个子项目添加dubbo依赖

       <properties>
           <java.version>1.8</java.version>
           <spring-boot.version>2.3.0.RELEASE</spring-boot.version>
           <dubbo.version>2.7.6</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>
           </dependencies>
       </dependencyManagement>
       <!-- Dubbo Spring Boot Starter -->
       <dependency>
           <groupId>org.apache.dubbo</groupId>
           <artifactId>dubbo-spring-boot-starter</artifactId>
           <version>2.7.6</version>
       </dependency>
       <dependency>
           <groupId>org.apache.dubbo</groupId>
           <artifactId>dubbo-dependencies-zookeeper</artifactId>
           <version>${dubbo.version}</version>
           <type>pom</type>
       </dependency>
      
    6. product-server、order-server2个子项目的配置文件如下application.properties

       //order-server
       server.port=7000
       dubbo.application.name=order-server
       dubbo.registry.address=zookeeper://127.0.0.1:2181
              
       //product-server
       server.port=6000
       dubbo.application.name=product-server
       dubbo.registry.address=zookeeper://127.0.0.1:2181
       dubbo.scan.base-packages=com.zh.demo
       dubbo.protocol.name=dubbo
       dubbo.protocol.port=20880
      
  3. product-server、order-server启动后,在Dubbo Admin中能查到服务提供者(product-server)、消费者(order-server)的IP地址跟端口信息

制定服务提供者绑定的注册IP地址

  1. 为什么服务提供者需要绑定指定的IP?服务提供者的PC对应的IP地址可以有多个–本机、局域网IP、外网IP,当服务提供者启动服务向ZK注册中心注册时,可能注册的是本机IP(127.0.0.1),那么服务消费者到zk的注册中心获取地址,然后调用的时候肯定调用不成功,因此服务的提供者必须绑定当前服务注册到zk的地址,确保消费者可以访问。

     127.0.0.1  本机ip----调用者、提供者无法调用
     192.168.48.100 局域网ip----服务调用者、提供者必须在同一网段
     202.45.67.234 外网IP---服务调用者、提供者都能调用,但是内网调用比较浪费性能
    
  2. 根据服务调用者、提供者在的网络范围来制定服务提供者的IP

    1. 服务提供者在启动项目的时候制定IP地址,在VM options添加: -DDUBBO_IP_TO_REGISTRY=192.168.48.100 启动参数
    2. 注意: 不能指定本地地址: 127.0.0.1和localhost,启动会报错

设置消费者启动的检查方式

  1. 正常情况下服务提供者必须先启动,再启动消费者服务,否则会报错;
  2. 但是可以设置消费者在启动时不检查服务提供者、也不检查注册中心zk是否正常启动
  3. 通过配置消费者(order-server)文件application.properties新增配置:有3中配置方法,provider.xml、application.properties、启动参数,详情看官方文档
配置消费者启动时不检查提供者是否正常提供服务
dubbo.consumer.check=false
配置注册中心连接不上时,服务不会报错,任然能正常启动,会自动连接注册中心zk
dubbo.registry.check=false

直连提供者

  1. 在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式
  2. 比如:在测试过程中,消费者直接绕过zk,指定连接本地的服务提供者,即消费者访问服务提供者不通过zk注册中心,直接通过地址链接某台服务器
  3. 消费者可以通过三种配置方式来指定绕过zk来访问服务提供者
    1. 通过 XML 配置

       <dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />
      
    2. 通过 -D 参数指定

       # 消费者添加启动配置 VM options添加 -D...
       java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20880
      
    3. 通过文件映射

      1. 如果服务比较多,也可以用文件映射,用 -Ddubbo.resolve.file 指定映射文件路径,此配置优先级高于 中的配置
       java -Ddubbo.resolve.file=xxx.properties
      

线程模型

  1. n个消费者发送请求到服务提供者,由于服务提供者需要查询数据库,因此响应比较慢,如果消费者发送请求过多,服务提供者不能及时消费响应就会造成问题
  2. 因此可以搞2个线程池,一个专门用来接收消费者的请求,一个专门用来处理具体任务,接收请求执行接收之后将线程切换到处理请求的线程池去处理
  3. 如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程(接收请求线程)上处理更快,因为减少了线程池调度。但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则 IO 线程阻塞,将导致不能接收其它请求。
  4. 如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。
  5. xml配置

     <dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" />
    
    1. Dispatcher
      1. all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
      2. direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行。
      3. message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
      4. execution 只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
      5. connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。
    2. ThreadPool
      1. fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
      2. cached 缓存线程池,空闲一分钟自动删除,需要时重建。
      3. limited 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
      4. eager 优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相比于cached:cached在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列)

负载均衡

  1. 集群负载均衡时,Dubbo 提供了多种均衡策略,默认为 random 随机调用,软负载
  2. 配置参数
    1. RandomLoadBalance
      1. 随机,按权重设置随机概率
      2. 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
    2. RoundRobin LoadBalance
      1. 轮询,按公约后的权重设置轮询比率。
      2. 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
    3. LeastActive LoadBalance
      1. 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
      2. 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
    4. ConsistentHash LoadBalance
      1. 一致性 Hash,相同参数的请求总是发到同一提供者。
      2. 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
  3. 配置方法见官方文档

     //服务端配置
     <dubbo:service interface="..." loadbalance="roundrobin" />
    

集群容错

  1. 当调用集群中某个服务报错时,可以进行自动重试
  2. xml设置重试次数

     # 重试次数
     <dubbo:service retries="2" />
     # 重试模式配置
     <dubbo:service cluster="failsafe" />
    

服务降级

  1. 服务A调用服务B,服务B调用服务C,服务B调用服务D,如果服务D挂了,不要影响服务ABC
  2. 改造服务器B实现兜底数据,当服务调用服务D失败时,不要报错,直接去调用兜底数据,保证ABC正常访问

源码剖析(略)

Table of Contents