Dubbo笔记

文章目录

主要课程内容

  • 第一部分:项目架构演变过程

单体架构 到 微服务架构的演变、拉勾网架构演变

  • 第二部分:Dubbo 架构实战

Dubbo 的架构(调用流程 特性) 注册中心 dubbo 的开发案例(注解 和 XML) Dubbo 的相关配置

  • 第三部分:Dubbo 高级应用实战

SPI 负载均衡 异步调用 自定义线程池 路由规则 服务降级

  • 第四部分:Dubbo 源码分析

Dubbo 的整体设计、服务注册与发现的源码剖析、Dubbo 扩展 SPI 分析、集群容器的源码分析、网络通信原理的分析

一、项目架构演变过程

随着互联网的发展,用户群体逐渐壮大,网站的流量成倍增长,常规的单体架构已无法满足请求压力 暴增和业务的快速迭代,架构的变化势在必行。

1、单体架构

单体架构所有模块和功能都集中在一个项目中 ,部署时也是将项目所有功能部整体署到服务器中。如下图:

image.png

优点

  • 小项目开发快成本低
  • 架构简单
  • 易于测试
  • 易于部署

缺点

  • 大项目模块耦合严重 不易开发维护
  • 新增业务困难
  • 核心业务与边缘业务混合在一块,出现问题互相影响

2、垂直架构

根据业务把项目垂直切割成多个项目,因此这种架构称之为垂直架构。

为了避免上面提到的那些问题,我们开始做模块的垂直划分,做垂直划分的原则是基于拉勾的业务特性,核心目标,第一个是为了业务之间互不影响,第二个是在研发团队的壮大后为了提高效率,减少之间的依赖。

image.png

优点

  • 系统拆分实现了流量分担,解决了并发问题
  • 可以针对不同系统进行优化
  • 方便水平扩展,负载均衡,容错率提高
  • 系统间相互独立,互不影响,新的业务迭代时更加高效

缺点

  • 服务系统之间接口调用硬编码
  • 搭建集群之后,实现负载均衡比较复杂
  • 服务系统接口调用监控不到位 调用方式不统一
  • 服务监控不到位
  • 数据库资源浪费,充斥慢查询,主从同步延迟大

3、分布式架构(SOA )

SOA 全称为 Service Oriented Architecture,即面向服务的架构 。它是在垂直划分的基础上,将每个项目拆分出多个具备松耦合的服务,一个服务通常以独立的形式存在于操作系统进程中。各个服务之间通过网络调用,这使得构建在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。

我们在做了垂直划分以后,模块随之增多,系统之间的 RPC 逐渐增多,维护的成本也越来越高,一些通用的业务和模块重复的也越来越多,这个时候上面提到的接口协议不统一、服务无法监控、服务的负载均衡等问题更加突出,为了解决上面的这些问题,我们将通用的业务逻辑下沉到服务层,通过接口暴露,供其他业务场景调用。

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

image.png

解释说明:

分层: 按照业务性质分层 每一层要求简单 和 容易维护

  • 应用层: 距离用户最近的一层 也称之为接入层 使用 tomcat 作为 web 容器 接收用户请求 使用下游的 dubbo 提供的接口来返回数据 并且该层禁止访问数据库
  • 业务服务层: 根据具体的业务场景 演变而来的模块 比如 简历投递 职位搜索 职位推荐等
  • 基础业务层: 拉勾网招聘业务的核心 账号 简历 公司 职位
  • 基础服务层:这一层 是与业务无关的模块 是一些通用的服务
    这类服务的特点: 请求量大 逻辑简单 特性明显 功能独立消息服务(发邮件 短信 微信)
    附件解析 50% 自己上传附件简历 需要解析成 pdf
  • 存储层:不同的存储类型 Mysql Mongodb ES fastDFS

分级:按照业务性质分层 同一层的业务也要做好分级 依据业务的重要性进行分级 按照二八定律网站 80% 的流量 都在核心功能上面 要优先保证核心业务的稳定。

隔离:不同性质 不同重要性的业务做好隔离 包括 业务 缓存 DB 中间件 都要做好隔离 比如 核心业务的数据库 要和活动相关的数据库隔离

调用 :总体上调用要单向 可以跨层调用 但不能出现逆向调用

优点

  • 服务以接口为粒度,为开发者屏蔽远程调用底层细节,使用 Dubbo 面向接口远程方法调用 屏蔽了底层调用细节
  • 业务分层以后架构更加清晰 并且每个业务模块职责单一
  • 扩展性更强 数据隔离,权限回收,数据访问都通过接口 让系统更加稳定 安全
  • 服务应用本身无状态化 这里的无状态化指的是应用本身不做内存级缓存 而是把数据存入 DB
  • 服务责任易确定 每个服务可以确定责任人这样更容易保证服务质量和稳定

缺点

  • 粒度控制复杂 如果没有控制好服务的粒度 服务的模块就会越来越多 就会引发 超时 分布式事务等问题
  • 服务接口数量不宜控制 容易引发接口爆炸 所以服务接口建议以业务场景进行单位划分并对相近的业务做抽象防止接口爆炸
  • 版本升级兼容困难 尽量不要删除方法 字段 枚举类型的新增字段也可能不兼容
  • 调用链路长 服务质量不可监控 调用链路变长 下游抖动可能会影响到上游业务 最终形成连锁反应 导致服务质量不稳定 同时链路的变长使得服务质量的监控变得困难

4、微服务架构

微服务架构是一种将单个应用程序 作为一套小型服务开发的方法,每种应用程序都在其自己的进程中独立运行,并使用轻量级机制(通常是 HTTP 资源的 API)进行通信。这些服务是围绕业务功能构建的,可以 通过全自动部署机制进行独立部署。这些服务的集中化管理非常少,它们可以用不同的编程语言编写, 并使用不同的数据存储技术。

微服务是在 SOA 上做的升华 , 粒度更加细致,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”。

关于微服务架构的知识,后面 Spring Cloud 课程中会详细讲解。

二、Dubbo 架构与实战

1、Dubbo 架构概述

1.1 什么是 Dubbo

Apache Dubbo 是一款高性能的 Java RPC 框架。其前身是阿里巴巴公司开源的一个高性能、轻量级的开 源 Java RPC 框架,可以和 Spring 框架无缝集成。

1.2 Dubbo 的特性

参考官网 Apache Dubbo 官网

image.png

image.png

1.3 Dubbo 的服务治理

服务治理(SOA governance),企业为了确保项目顺利完成而实施的过程,包括最佳实践、架构原则、治理规程、规律以及其他决定性的因素。服务治理指的是用来管理 SOA 的采用和实现的过程。

参考服务治理文档

image.png

2、Dubbo 处理流程

image.png

节点说明

节点角色名称
Provider暴露服务的服务提供方
Consumer调用远程服务的服务消费方
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心
Container服务运行容器 负责启动 加载 运行服务提供者

调用关系说明:

虚线 代表异步调用

实线代表同步访问

蓝色虚线 是在启动时完成的功能

红色虚线 是程序运行中执行的功能

调用流程:

服务提供者在服务容器启动时向注册中心注册自己提供的服务

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

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

服务消费者从提供者地址列表中 基于软负载均衡算法 选一台提供者进行调用 如果调用失败 则重新选择一台

服务提供者和消费者在内存中的调用次数和调用时间定时每分钟发送给监控中心

3、服务注册中心 Zookeeper

通过前面的 Dubbo 架构图可以看到,Registry(服务注册中心)在其中起着至关重要的作用。Dubbo 官 方推荐使用 Zookeeper 作为服务注册中心。Zookeeper 是 Apache Hadoop 的子项目,作为 Dubbo 服 务的注册中心,工业强度较高,可用于生产环境,并推荐使用 。 Zookeeper 的安装及其使用见上一模块,此处不再赘述。

4、Dubbo 开发实战

4.1 实战案例介绍

在 Dubbo 中所有的的服务调用都是基于接口去进行双方交互的。双方协定好 Dubbo 调用中的接口,提 供者来提供实现类并且注册到注册中心上。 调用方则只需要引入该接口,并且同样注册到相同的注册中心上(消费者)。即可利用注册中心来实现集 群感知功能,之后消费者即可对提供者进行调用。

我们所有的项目都是基于 Maven 去进行创建,这样相互在引用的时候只需要以依赖的形式进行展现就可 以了。

并且这里我们会通过 maven 的父工程来统一依赖的版本。可参考 Dubbo 官方文档

程序实现分为以下几步骤:

  1. 建立 maven 工程 并且 创建 API 模块: 用于规范双方接口协定
  2. 提供 provider 模块,引入 API 模块,并且对其中的服务进行实现。将其注册到注册中心上,对外来 统一提供服务。
  3. 提供 consumer 模块,引入 API 模块,并且引入与提供者相同的注册中心。再进行服务调用。
4.2 注解方式代码实现

dubbo-base 模块

<?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>
    <artifactId>dubbo-base</artifactId>
    <packaging>jar</packaging>
    <groupId>galaxy</groupId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>service-api</module>
        <module>service-provider</module>
    </modules>
    <properties>
        <dubbo.version>2.7.12 </dubbo.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-common</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-registry-zookeeper</artifactId>
                <version>${dubbo.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-remoting-api</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-common</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-registry-nacos</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-rpc-dubbo</artifactId>
                <version>${dubbo.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-remoting-api</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-common</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-remoting-netty4</artifactId>
                <version>${dubbo.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-remoting-api</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-serialization-hessian2</artifactId>
                <version>${dubbo.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-common</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- 日志配置 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency>

        <!-- json数据化转换 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>



    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

接口协定 service-api 模块

/**
 * @author lane
 * @date 2021年06月16日 下午6:23
 */
public interface HelloService {

    String sayHello(String name);
}

创建接口提供者 provider 模块

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

    <artifactId>service-provider</artifactId>
    <dependencies>
        <dependency>
            <groupId>galaxy</groupId>
            <artifactId>service-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <dependency>
        <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-zookeeper</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-remoting-netty4</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-serialization-hessian2</artifactId>
        </dependency>
    </dependencies>

</project>

service 接口实现

package com.galaxy.service.impl;

import com.galaxy.service.HelloService;
import org.apache.dubbo.config.annotation.Service;

/**
 * @author lane
 * @date 2021年06月16日 下午6:22
 */
@Service
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        return "hello"+name;
    }
}

配置类及配置文件

package com.galaxy.config;

import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

/**
 * @author lane
 * @date 2021年06月16日 下午6:30
 */
@Configuration
@EnableDubbo(scanBasePackages = "com.galaxy.service.impl")
@PropertySource("classpath:/dubbo-provider.properties")
public class ProviderConfiguration {

    @Bean
    RegistryConfig registryConfig (){

        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("zookeeper://127.0.0.1:2181");

        return registryConfig;
    }
}
dubbo.application.name=service-provider
dubbo.protocol.name=dubbo 
dubbo.protocol.port=20880

dubbo.application.name: 当前提供者的名称

dubbo.protocol.name: 对外提供的时候使用的协议

dubbo.protocol.port: 该服务对外暴露的端口是什么,在消费者使用时,则会使用这个端口 并且使用指定的协议与提供者建立连接。

启动类

package com.galaxy.demo;

import com.galaxy.config.ProviderConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.io.IOException;

/**
 * @author lane
 * @date 2021年06月16日 下午6:35
 */
public class DubboMain {
    public static void main(String[] args) throws IOException, InterruptedException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);


        context.start();
        System.in.read();


    }
}

出现错误信息:Please add <dubbo:application name="…" /> to your spring config

解决方案 :把 dubbo 改为 2.7.12 版本就好了

image.png

消费者模块 service-consumer

pom.xml

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

    <artifactId>service-consumer</artifactId>
    <dependencies>
        <dependency>
            <groupId>galaxy</groupId>
            <artifactId>service-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-zookeeper</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-remoting-netty4</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-serialization-hessian2</artifactId>
        </dependency>
    </dependencies>

</project>

properties

编写消费者的配置文件。这里比较简单,主要就是指定了当前消费者的名称和注册中心的位置。通过这个注册中心地址,消费者就会注册到这里并且也可以根据这个注册中心找到真正的提供者列表。

dubbo.application.name = service-consumer
dubbo.registry.address = zookeeper://127.0.0.1:2181

编写启动类,这其中就会当用户在控制台输入了一次换行后,则会发起一次请求。

package com.galaxy.config;

import com.galaxy.bean.ConsumerComponent;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import java.io.IOException;

/**
 * @author lane
 * @date 2021年06月16日 下午7:08
 */

public class AnnotationConsumerMain {
    public static void main(String[] args) throws IOException {

        AnnotationConfigApplicationContext context  =new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
                context.start();
        ConsumerComponent consumerComponent = context.getBean(ConsumerComponent.class);
        while (true){
            System.in.read();
            String world = consumerComponent.hello("world");
            System.out.println(world);


        }

    }


    @Configuration
    @PropertySource("classpath:/dubbo-consumer.properties")
    @ComponentScan(basePackages = "com.galaxy.bean")
    @EnableDubbo
    static class ConsumerConfiguration{


    }
}

package com.galaxy.bean;

import com.galaxy.service.HelloService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Component;

/**
 * @author lane
 * @date 2021年06月16日 下午7:00
 */
@Component
public class ConsumerComponent {
    @Reference
    private HelloService helloService;

    public String hello(String name){
        String hello = helloService.sayHello(name);

        return hello;
    }
}

控制台输入内容后效果

image.png

配置介绍

下面我们来使用不同的方式来对 Dubbo 进行配置。每种配置方式各有不同,一般可以分为以下几个。

  1. 注解: 基于注解可以快速的将程序配置,无需多余的配置信息,包含提供者和消费者。但是这种方式有一个弊端,有些时候配置信息并不是特别好找,无法快速定位。
  2. XML: 一般这种方式我们会和 Spring 做结合,相关的 Service 和 Reference 均使用 Spring 集成后的。通过这样的方式可以很方便的通过几个文件进行管理整个集群配置。可以快速定位也可以快速更改。
  3. 基于代码方式: 基于代码方式的对上述配置进行配置。这个使用的比较少,这种方式更适用于自己公司对其框架与 Dubbo 做深度集成时才会使用。
4.3 xml 方式代码实现

我们一般 XML 会结合 Spring 应用去进行使用,将 Service 的注册和引用方式都交给 Spring 去管理。下面我们还是针对于上面的 demo 进行实现。
这里我们针对于 api 模块不做处理,还是使用原先的接口。从提供者和消费者做讲解。这了我们直接通过 spring 的方式去做讲解。

实际上在官方的入门文档已经写的非常清楚了可以参考 dubbo 快速入门

突然想贴一个图片

书签来源

image.png

整体架构图

image.png

项目依赖

<?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>galaxy</groupId>
    <artifactId>dubbo-base-xml</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>service-api</module>
        <module>service-provider</module>
        <module>service-consumer</module>
    </modules>
    <properties>
        <dubbo.version>2.7.10 </dubbo.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-common</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-registry-zookeeper</artifactId>
                <version>${dubbo.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-remoting-api</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-common</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-registry-nacos</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-rpc-dubbo</artifactId>
                <version>${dubbo.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-remoting-api</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-common</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-remoting-netty4</artifactId>
                <version>${dubbo.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-remoting-api</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-serialization-hessian2</artifactId>
                <version>${dubbo.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-common</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- 日志配置 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency>

        <!-- json数据化转换 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>



    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

service-api 模块同上

service-provider 模块

依赖

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

    <artifactId>service-provider</artifactId>
    <dependencies>
        <!--api模块-->
        <dependency>
            <groupId>galaxy</groupId>
            <artifactId>service-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-zookeeper</artifactId>
        </dependency>
        <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-registry-nacos</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-remoting-netty4</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-serialization-hessian2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-config-spring</artifactId>
            <version>2.7.10</version>
        </dependency>
    </dependencies>

</project>

接口

package com.galaxy.service.impl;

import com.galaxy.service.HelloService;

/**
 * @author lane
 * @date 2021年06月17日 上午10:33
 */
public class HelloServiceImpl implements HelloService {
    @Override
    public String syaHello(String name) {
        return "hello:"+name;
    }
}

配置文件

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

    <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="service-provider"/>

    <!-- 使用multicast广播注册中心暴露服务地址 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />

    <!-- 用dubbo协议在20881端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20881" />

    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.galaxy.service.HelloService" ref="helloService" />

    <!-- 和本地bean一样实现服务 -->
    <bean id="helloService" class="com.galaxy.service.impl.HelloServiceImpl" />
</beans>

日志文件 log4j.properties

log4j.rootCategory=INFO,CONSOLE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p %c.%M\(%F:%L\) - %m%n

启动类

package com.galaxy.service.demo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;

/**
 * @author lane
 * @date 2021年06月17日 上午11:02
 */
public class ProviderDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:dubbo-provider.xml");

        context.start();
        //先不要关掉,阻塞下
        System.in.read();

//        Thread.sleep(100000);


    }
}

image.png

消费者 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">
    <parent>
        <artifactId>dubbo-base-xml</artifactId>
        <groupId>galaxy</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>service-consumer</artifactId>
    <dependencies>
    <!--api模块-->
    <dependency>
        <groupId>galaxy</groupId>
        <artifactId>service-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-zookeeper</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-nacos</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-remoting-netty4</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-serialization-hessian2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-config-spring</artifactId>
            <version>2.7.10</version>
        </dependency>
    </dependencies>

</project>

配置文件

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

    <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
    <dubbo:application name="service-consumer" />
    <dubbo:application name="service-consumer" />
    <!-- 使用zookeeper注册中心暴露发现服务地址 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />

    <!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
    <dubbo:reference id="helloService" interface="com.galaxy.service.HelloService" />
</beans>

日志同上

启动类

package com.galaxy.demo;

import com.galaxy.service.HelloService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author lane
 * @date 2021年06月17日 上午11:10
 */
public class ConsumerDemo {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:dubbo-consumer.xml");
        applicationContext.start();//不加也可以
        HelloService helloService = (HelloService) applicationContext.getBean("helloService");

        String galaxy = helloService.syaHello("galaxy");
        //zk和提供者必须先启动
        System.out.println(galaxy);
    }
}

启动效果

image.png

引入启动模块。因为引用了 Spring 框架,所以再上一步的 helloService 会被当做一个 bean 注入到真实的环境中。在我们生产级别使用的时候,我们可以通过 Spring 中的包扫描机制,通过@Autowired 这种机制来进行依赖注入。

5、Dubbo 管理控制台 dubbo-admin

5.1 作用

主要包含:服务管理 、 路由规则、动态配置、服务降级、访问控制、权重调整、负载均衡等管理功能

如我们在开发时,需要知道 Zookeeper 注册中心都注册了哪些服务,有哪些消费者来消费这些服务。我 们可以通过部署一个管理中心来实现。其实管理中心就是一个 web 应用,原来是 war(2.6 版本以前)包需 要部署到 tomcat 即可。现在是 jar 包可以直接通过 java 命令运行。

5.2 控制台安装及访问

1.从 git 上下载项目 https://github.com/apache/dubbo-admin
2.修改项目下的 dubbo.properties 文件
注意 dubbo.registry.address 对应的值需要对应当前使用的 Zookeeper 的 ip 地址和端口号
• dubbo.registry.address=zookeeper://zk 所在机器 ip:zk 端口
• dubbo.admin.root.password=root
• dubbo.admin.guest.password=guest
3.切换到项目所在的路径 使用 mvn 打包
mvn clean package -Dmaven.test.skip=true
4.java 命令运行
java -jar 对应的 jar 包

访问步骤

1.访问 http://IP:端口 如 http://localhost:7001
2.输入 admin 的用户名 root,密码 root
3.点击菜单查看服务提供者和服务消费者信息

一些图示

image.png

image.png

image.png

image.png

6、Dubbo 配置项说明

具体可以参考官方文档说明,这里写几个最常用的配置

image.png

dubbo:application

对应 org.apache.dubbo.config.ApplicationConfig, 代表当前应用的信息

  1. name: 当前应用程序的名称,在 dubbo-admin 中我们也可以看到,这个代表这个应用名称。我们在真正时是时也会根据这个参数来进行聚合应用请求。
  2. owner: 当前应用程序的负责人,可以通过这个负责人找到其相关的应用列表,用于快速定位到责任人。
  3. qosEnable : 是否启动 QoS 默认 true
  4. qosPort : 启动 QoS 绑定的端口 默认 22222,提供者和消费者都是默认一样,可以通过 telnet ip 22222 连接 dubbo 命令界面
  5. qosAcceptForeignIp: 是否允许远程访问 默认是 false

dubbo:registry

org.apache.dubbo.config.RegistryConfig, 代表该模块所使用的注册中心。一个模块中的服务可以将其注册到多个注册中心上,也可以注册到一个上。后面再 service 和 reference 也会引入这个注册中心。

  1. id : 当当前服务中 provider 或者 consumer 中存在多个注册中心时,则使用需要增加该配置。在一些公司,会通过业务线的不同选择不同的注册中心,所以一般都会配置该值。
  2. address : 当前注册中心的访问地址。
  3. protocol : 当前注册中心所使用的协议是什么。也可以直接在 address 中写入,比如使用 zookeeper,就可以写成 zookeeper://xx.xx.xx.xx:2181
  4. timeout : 当与注册中心不再同一个机房时,大多会把该参数延长。

dubbo:protocol
org.apache.dubbo.config.ProtocolConfig, 指定服务在进行数据传输所使用的协议。

  1. id : 在大公司,可能因为各个部门技术栈不同,所以可能会选择使用不同的协议进行交互。这里在多个协议使用时,需要指定。
  2. name : 指定协议名称。默认使用 dubbo 。

dubbo:service
org.apache.dubbo.config.ServiceConfig, 用于指定当前需要对外暴露的服务信息,后面也会具体讲解。和 dubbo:reference 大致相同。

  1. interface : 指定当前需要进行对外暴露的接口是什么。
  2. ref : 具体实现对象的引用,一般我们在生产级别都是使用 Spring 去进行 Bean 托管的,所以这里面一般也指的是 Spring 中的 BeanId。
  3. version : 对外暴露的版本号。不同的版本号,消费者在消费的时候只会根据固定的版本号进行消费。

dubbo:reference
org.apache.dubbo.config.ReferenceConfig, 消费者的配置,这里只做简单说明,后面会具体讲解。

  1. id : 指定该 Bean 在注册到 Spring 中的 id。
  2. interface: 服务接口名
  3. version : 指定当前服务版本,与服务提供者的版本一致。
  4. registry : 指定所具体使用的注册中心地址。这里面也就是使用上面在 dubbo:registry 中所声明的 id。

dubbo:method
org.apache.dubbo.config.MethodConfig, 用于在制定的 dubbo:service 或者 dubbo:reference 中的更具体一个层级,指定具体方法级别在进行 RPC 操作时候的配置,可以理解为对这上面层级中的配置针对于具体方法的特殊处理。

  1. name : 指定方法名称,用于对这个方法名称的 RPC 调用进行特殊配置。
  2. async: 是否异步 默认 false

dubbo:service 和 dubbo:reference 详解
这两个在 dubbo 中是我们最为常用的部分,其中有一些我们必然会接触到的属性。并且这里会讲到一些设置上的使用方案。

  1. mock: 用于在方法调用出现错误时,当做服务降级来统一对外返回结果,后面我们也会对这个方法做更多的介绍。
  2. timeout: 用于指定当前方法或者接口中所有方法的超时时间。我们一般都会根据提供者的时长来具体规定。比如我们在进行第三方服务依赖时可能会对接口的时长做放宽,防止第三方服务不稳定导致服务受损。
  3. check: 用于在启动时,检查生产者是否有该服务。我们一般都会将这个值设置为 false,不让其进行检查。因为如果出现模块之间循环引用的话,那么则可能会出现相互依赖,都进行 check 的话,那么这两个服务永远也启动不起来。
  4. retries: 用于指定当前服务在执行时出现错误或者超时时的重试机制。
    1. 注意提供者是否有幂等,否则可能出现数据一致性问题
    2. 注意提供者是否有类似缓存机制,如出现大面积错误时,可能因为不停重试导致雪崩
  5. executes: 用于在提供者做配置,来确保最大的并行度。
    1. 可能导致集群功能无法充分利用或者堵塞
    2. 但是也可以启动部分对应用的保护功能
    3. 可以不做配置,结合后面的熔断限流使用
  <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application   name="service-provider2"   owner="lane" />

    <!-- 使用zookeeper注册中心暴露服务地址 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" id="r1"  timeout="10000"/>

    <!-- 用dubbo协议在20882端口暴露服务
    <dubbo:protocol name="dubbo" port="20882" /> -->
    <!-- 用dubbo协议在20883端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20883" />

    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.galaxy.service.HelloService"    ref="helloService"  />

    <!-- 和本地bean一样实现服务 -->
    <bean id="helloService" class="com.lagou.service.impl.HelloServiceImpl" />
   <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
    <dubbo:application name="service-consumer"     >
         <dubbo:parameter key="qos.enable" value="true" ></dubbo:parameter>
         <dubbo:parameter key="qos.port" value="33333"></dubbo:parameter>
         <dubbo:parameter key="qos.accept.foreign.ip" value="true" ></dubbo:parameter>
    </dubbo:application>
    <!--超时时间-->
     <dubbo:consumer timeout="2000"   check="false"  ></dubbo:consumer>

    <!-- 使用zookeeper注册中心暴露发现服务地址 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" id="1" timeout="10000"/>

    <!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
    <dubbo:reference id="helloService" interface="com.galaxy.service.HelloService" mock="true" />

实际测试

在提供者方法添加 sleep 4 秒,在消费者配置超时 3 秒如下

image.png

mock 测试

在提供方超时错误后,会走本地的 mock 方法返回结果

package com.galaxy.service;

import com.galaxy.service.HelloService;

/**
 * @author lane
 * @date 2021年06月17日 下午3:29
 */
public class HelloServiceMock implements HelloService {
    @Override
    public String syaHello(String name) {


        return "本地的默认:hello mock,提供者调用失败";
    }
}

测试结果

image.png

三、Dubbo 高级实战

1、SPI

1.1 SPI 简介

SPI 全称为 (Service Provider Interface) ,是 JDK 内置的一种服务提供发现机制。 目前有不少框架用它来做服务的扩展发现,简单来说,它就是一种动态替换发现的机制。使用 SPI 机制的优势是实现解耦,使得第三方服务模块的装配控制逻辑与调用者的业务代码分离。

1.2 JDK 中的 SPI

image.png

Java 中如果想要使用 SPI 功能,先提供标准服务接口,然后再提供相关接口实现和调用者。这样就可以通 过 SPI 机制中约定好的信息进行查询相应的接口实现。

SPI 遵循如下约定:

1、当服务提供者提供了接口的一种具体实现后,在 META-INF/services 目录下创建一个以“接口全 限定名”为命名的文件,内容为实现类的全限定名;

2、接口实现类所在的 jar 包放在主程序的 classpath 中;

3、主程序通过 java.util.ServiceLoader 动态装载实现模块,它通过扫描 META-INF/services 目录下的配置文件找到实现类的全限定名,把类加载到 JVM;

4、SPI 的实现类必须携带一个无参构造方法;

代码实现可以参考文末 GitHub 地址,这里就只贴一个截图就可以了

image.png

1.3 Dubbo 中的 SPI

dubbo 中大量的使用了 SPI 来作为扩展点,通过实现同一接口的前提下,可以进行定制自己的实现类。 比如比较常见的协议,负载均衡,都可以通过 SPI 的方式进行定制化,自己扩展。Dubbo 中已经存在的 所有已经实现好的扩展点。

image.png

下图中则是 Dubbo 中默认提供的负载均衡策略。

image.png

1.4 Dubbo 中扩展点使用方式

我们使用三个项目来演示 Dubbo 中扩展点的使用方式,一个主项目 main,一个服务接口项目 api,一个 服务实现项目 impl。

api 项目创建

(1)导入坐标 dubbo

(2)创建接口

在接口上 使用@SPI

impl 项目创建

(1)导入 api 项目 的依赖

(2)建立实现类,为了表达支持多个实现的目的,这里分别创建两个实现。分别为 HumanHelloService 和 DogHelloService 。

(3)SPI 进行声明操作,在 resources 目录下创建目录 META-INF/dubbo 目录,在目录下创建名称为 com.lagou.dubbo.study.spi.demo.api.HelloService 的文件,文件内部配置两个实现类名称和对应的全 限定名:

main 项目创建

(1)导入坐标

接口项目 和 实现类项目

(2)创建 DubboSpiMain

和原先调用的方式不太相同, dubbo 有对其进行自我重新实现 需要借助 ExtensionLoader,创建新的运 行项目。这里 demo 中的示例和 java 中的功能相同,查询出所有的已知实现,并且调用

代码实现

image.png

spi-api

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

    <artifactId>spi-api</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.10</version>
        </dependency>
    </dependencies>

</project>
package com.galaxy.service;

import org.apache.dubbo.common.extension.SPI;

/**
 * @author lane
 * @date 2021年06月17日 下午4:37
 */
@SPI
public interface HelloService {
    String syaHello();
}

spi-provider

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

    <artifactId>spi-provider</artifactId>
    <dependencies>
        <dependency>
            <groupId>galaxy</groupId>
            <artifactId>spi-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>
package com.galaxy.service.impl;

import com.galaxy.service.HelloService;

/**
 * @author lane
 * @date 2021年06月17日 下午4:38
 */
public class CatService implements HelloService {
    public String syaHello() {
        return "hi cat dubbo";
    }
}
----------------------

package com.galaxy.service.impl;

import com.galaxy.service.HelloService;

/**
 * @author lane
 * @date 2021年06月17日 下午4:38
 */
public class HumanService implements HelloService {
    public String syaHello() {
        return "hi man dubbo";
    }
}
man=com.galaxy.service.impl.HumanService
cat=com.galaxy.service.impl.CatService

spi-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">
    <parent>
        <artifactId>dubbo-spi-demo</artifactId>
        <groupId>galaxy</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spi-consumer</artifactId>
    <dependencies>
        <dependency>
            <groupId>galaxy</groupId>
            <artifactId>spi-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>galaxy</groupId>
            <artifactId>spi-provider</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>
package com.galaxy.demo;

import com.galaxy.service.HelloService;
import org.apache.dubbo.common.extension.ExtensionLoader;

import java.util.Iterator;
import java.util.Set;

/**
 * @author lane
 * @date 2021年06月17日 下午4:42
 */
public class Test {
    public static void main(String[] args) {
        //获取扩展加载器
        ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
        //遍历所有的扩展点 meta-info/dubbo
        Set<String> supportedExtensions = extensionLoader.getSupportedExtensions();
        for (String extension: supportedExtensions){
            HelloService extension1 = extensionLoader.getExtension(extension);
            System.out.println(extension1.syaHello());
        }
  
    }
}

运行结果

image.png

一个讨厌的问题

image.png

dubbo 自己做 SPI 的目的

  1. JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源
  2. 如果有扩展点加载失败,则所有扩展点无法使用
  3. 提供了对扩展点包装的功能(Adaptive),并且还支持通过 set 的方式对其他的扩展点进行注入
1.5 Dubbo SPI 中的 Adaptive 功能

Dubbo 中的 Adaptive 功能,主要解决的问题是如何动态的选择具体的扩展点。通过 getAdaptiveExtension 统一对指定接口对应的所有扩展点进行封装,通过 URL 的方式对扩展点来进行动态选择。 (dubbo 中所有的注册信息都是通过 URL 的形式进行处理的。)这里同样采用相同的方式进行 实现。

(1)创建接口

提供 URL 参数.注意这里的 URL 参数的类为 org.apache.dubbo.common.URL

HelloService

注解,并且在参数中

其中@SPI 可以指定一个字符串参数,用于指明该 SPI 的默认实现。

(2)创建实现类

与上面 Service 实现类代码相似,只需增加 URL 形参即可

(3)编写 DubboAdaptiveMain

最后在获取的时候方式有所改变,需要传入 URL 参数,并且在参数中指定具体的实现类参数

代码如下修改

package com.galaxy.service;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;

/**
 * @author lane
 * @date 2021年06月17日 下午4:37
 */
@SPI("human") //指定默认key
public interface HelloService {
    String sayHello();
    @Adaptive//必须加
    String sayHello(URL url);
}

-------
package com.galaxy.service.impl;

import com.galaxy.service.HelloService;
import org.apache.dubbo.common.URL;

/**
 * @author lane
 * @date 2021年06月17日 下午4:38
 */
public class CatService implements HelloService {
    public String sayHello() {
        return "hi cat dubbo";
    }

    @Override
    public String sayHello(URL url) {
        return "hello cat urlAdaptive";
    }
}

 public static void main(String[] args) {
        //key可以默认,可以指定dubbo自己转码HelloService ==>hello.service
        URL url = URL.valueOf("http://127.0.0.1:1027?hello.service=cat");

        testAdaptive(url);

    }

    public static void testAdaptive(URL url) {
        ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
        HelloService adaptiveExtension = extensionLoader.getAdaptiveExtension();
  //根据URL参数key选择不同的实现类来访问
        String s = adaptiveExtension.sayHello(url);
        System.out.println(s);
    }

运行结果

image.png

注意:

因为在这里只是临时测试,所以为了保证 URL 规范,前面的信息均为测试值即可,关键的点在于 hello.service 参数,这个参数的值指定的就是具体的实现方式。关于为什么叫 hello.service 是因为这个接口的名称,其中后面的大写部分被 dubbo 自动转码为 . 分割。

通过 getAdaptiveExtension 点进行封装来提供一个统一的类来对所有的扩展点提供支持(底层对所有的扩展点进行封装)

调用时通过参数中增加 URL 对象来实现动态的扩展点使用。

如果 URL 没有提供该参数,则该方法会使用默认在 SPI 注解中声明的实现。

1.6 Dubbo 调用时拦截操作

与很多框架一样,Dubbo 也存在拦截(过滤)机制,可以通过该机制在执行目标程序前后执行我们指定的代码。

Dubbo 的 Filter 机制,是专门为服务提供方和服务消费方调用过程进行拦截设计的,每次远程方法执行,该拦截都会被执行。这样就为开发者提供了非常方便的扩展性,比如为 dubbo 接口实现 ip 白名单功 能、监控功能 、日志记录等。

步骤如下:

(1)实现 org.apache.dubbo.rpc.Filter 接口

(2)使用 org.apache.dubbo.common.extension.Activate 指定生产端 消费端 如:

@Activate(group = {CommonConstants.CONSUMER)

(3)计算方法运行时间的代码实现

(4)在 META-INF.dubbo 新建 org.apache.dubbo.rpc.Filter 文件,并将当前类的全名写入

timerFilter=包名.过滤器的名字

注意:一般类似于这样的功能都是单独开发依赖的,所以再使用方的项目中只需要引入依赖,在调用接口时,该方法便会自动拦截。

代码实现

 	<dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.12</version>
        </dependency>
package com.galaxy.filter;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;

/**
 * @author lane
 * @date 2021年06月17日 下午6:22
 */
@Activate(group={CommonConstants.CONSUMER,CommonConstants.PROVIDER})
public class TimeFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        long start = System.currentTimeMillis();

        try {
            return invoker.invoke(invocation);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            long end = System.currentTimeMillis();
            System.out.println("执行时间为:"+(end-start)+"毫秒");
        }
        return null;
    }
}

META-INF/dubbo 下文件名为 com.galaxy.filter.TimeFilter 内容为如下

com.galaxy.filter.TimeFilter

在上面的 dubbo-base 项目下的 consumer 模块引入该 filter 的依赖即可

输入两个字符 1 和 enter 执行效果如下

image.png

2、负载均衡策略

2.1 负载均衡基本配置

负载均衡(Load Balance), 其实就是将请求分摊到多个操作单元上进行执行,从而共同完成工作任务。 负载均衡策略主要用于客户端存在多个提供者时进行选择某个提供者。 在集群负载均衡时,Dubbo 提供了多种均衡策略(包括随机、轮询、最少活跃调用数、一致性 Hash),缺省为 random 随机调用。 这块儿主要是来自于官方文档,已经写得很详细了 配置负载均衡策略,既可以在服务提供者一方配置,也可以在服务消费者一方配置,如下:

//在服务提供者一方配置负载均衡 @Service(loadbalance = "random") public class HelloServiceImpl implements HelloService {

public String sayHello(String name) {

return "hello " + name;

} }
//在服务消费者一方配置负载均衡策略 @Reference(check = false,loadbalance = "random")

image.png

image.png

2.2 自定义负载均衡器

负载均衡器在 Dubbo 中的 SPI 接口是 org.apache.dubbo.rpc.cluster.LoadBalance 可以通过实现这个接口来实现自定义的负载均衡规则。

(1)自定义负载均衡器 在上一节的案例基础上创建名称为 dubbo-spi-loadbalance 的 Maven 模块,并创建负载均衡器 OnlyFirstLoadbalancer。这里功能只是简单的选取所有机器中的第一个(按照字母排序 + 端口排序)。

(2)配置负载均衡器

在 dubbo-spi-loadbalance 工程的 META-INF/dubbo 目录下新建
org.apache.dubbo.rpc.cluster.LoadBalance 文件,并将当前类的全名写入

onlyFirst=包名.负载均衡器

(3)在服务提供者工程实现类中编写用于测试负载均衡效果的方法启动不同端口时方法返回的信息不同

(4)启动多个服务 要求他们使用同一个接口注册到同一个注册中心 但是他们的 dubbo 通信端口不同

(5)在服务消费方指定自定义负载均衡器 onlyFirst

(6)测试自定义负载均衡的效果

代码实现结果

image.png

3、异步调用

Dubbo 不只提供了堵塞式的的同步调用,同时提供了异步调用的方式。这种方式主要应用于提供者接口响应耗时明显,消费者端可以利用调用接口的时间去做一些其他的接口调用,利用 Future 模式来异步等待和获取结果即可。这种方式可以大大的提升消费者端的利用率。 目前这种方式可以通过 XML 的方式进行引入。

3.1 异步调用实现

(1)为了能够模拟等待,通过 int timeToWait 参数,标明需要休眠多少毫秒后才会进行返回。

String sayHello(String name, int timeToWait);

(2)接口实现 为了模拟调用耗时 可以让线程等待一段时间
(3)在消费者端,配置异步调用 注意消费端默认超时时间 1000 毫秒 如果提供端耗时大于 1000 毫秒会出现超时可以通过改变消费端的超时时间 通过 timeout 属性设置即可单位毫秒

<dubbo:reference id="helloService" interface="com.galaxy.service.HelloService">
<dubbo:method name="sayHello" async="true" />
</dubbo:reference>

(4)测试,我们休眠 100 毫秒,然后再去进行获取结果。方法在同步调用时的返回值是空,我们可以通过 RpcContext.getContext().getFuture() 来进行获取 Future 对象来进行后续的结果等待操作。

3.2 异步调用特殊说明

需要特别说明的是,该方式的使用,请确保 dubbo 的版本在 2.5.4 及以后的版本使用。 原因在于在 2.5.3 及之前的版本使用的时候,会出现异步状态传递问题。比如我们的服务调用关系是 A -> B -> C , 这时候如果 A 向 B 发起了异步请求,在错误的版本时,B 向 C 发起的请求也会连带的产生异步请求。这是因为在底层实现层面,他是通过 RPCContext 中的 attachment 实现的。在 A 向 B 发起异步请求时,会在 attachment 中增加一个异步标示字段来表明异步等待结果。B 在接受到 A 中的请求时,会通过该字段来判断是否是异步处理。但是由于值传递问题,B C 发起时同样会将该值进行传递,导致 C 误以为需要异步结果,导致返回空。这个问题在 2.5.4 及以后的版本进行了修正。

4、线程池

4.1 Dubbo 已有线程池

dubbo 在使用时,都是通过创建真实的业务线程池进行操作的。目前已知的线程池模型有两个和 java 中 的相互对应:

  • fix: 表示创建固定大小的线程池。也是 Dubbo 默认的使用方式,默认创建的执行线程数为 200,并且是没有任何等待队列的。所以再极端的情况下可能会存在问题,比如某个操作大量执行时,可能存在堵塞的情况。后面也会讲相关的处理办法。
  • cache: 创建非固定大小的线程池,当线程不足时,会自动创建新的线程。但是使用这种的时候需要注意,如果突然有高 TPS 的请求过来,方法没有及时完成,则会造成大量的线程创建,对系统的 CPU 和负载都是压力,执行越多反而会拖慢整个系统。
4.2 自定义线程池

在真实的使用过程中可能会因为使用 fix 模式的线程池,导致具体某些业务场景因为线程池中的线程数量 不足而产生错误,而很多业务研发是对这些无感知的,只有当出现错误的时候才会去查看告警或者通过 客户反馈出现严重的问题才去查看,结果发现是线程池满了。所以可以在创建线程池的时,通过某些手 段对这个线程池进行监控,这样就可以进行及时的扩缩容机器或者告警。下面的这个程序就是这样子 的,会在创建线程池后进行对其监控,并且及时作出相应处理。

(1)线程池实现, 这里主要是基于对 FixedThreadPool 中的实现做扩展出线程监控的部分

package com.galaxy;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.threadpool.support.fixed.FixedThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;

/**
 * @author lane
 * @date 2021年06月18日 下午3:39
 */
public class WatchingThreadPool extends FixedThreadPool implements Runnable {
    private static final Logger LOGGER = LoggerFactory.getLogger(WatchingThreadPool.class);
    //定义线程使用的阀值
    private static final double ALARM_PERCENT = 0.80;
    private final Map<URL, ThreadPoolExecutor> THREAD_POOL = new ConcurrentHashMap<>();
    public WatchingThreadPool(){
        //每隔三秒打印线程的使用情况
        Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(this,1,3, TimeUnit.SECONDS);

    }
    @Override
    public Executor getExecutor(URL url){
        Executor executor = super.getExecutor(url);
        if (executor instanceof ThreadPoolExecutor){
            THREAD_POOL.put(url, (ThreadPoolExecutor) executor);

        }
        return executor;
    }



    @Override
    public void run() {
        for (Map.Entry<URL,ThreadPoolExecutor> entry:THREAD_POOL.entrySet()){
            final URL url = entry.getKey();
            final ThreadPoolExecutor executor = entry.getValue();
            //计算指标
            int activeCount = executor.getActiveCount();
            int corePoolSize = executor.getCorePoolSize();
            double usedPercent = activeCount/(corePoolSize*1.0);
            LOGGER.info("线程池的状态:activeCount:"+activeCount+"corePoolSize:"+corePoolSize+"usedPercent:"+usedPercent);
            if (usedPercent>ALARM_PERCENT){
                LOGGER.error("超出警戒线:"+"host:"+url.getHost()+"ip:"+url.getIp()+"url:"+url);
            }

            LOGGER.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
            LOGGER.info("线程池的状态:[{}/{}:{}%]",activeCount,corePoolSize,usedPercent*100);
            if (usedPercent>ALARM_PERCENT){
                LOGGER.error("超出警戒线!host:{},ip:{},url:{}",url.getHost(),url.getIp(),url);
            }
        }
    }
}

(2)SPI 声明,创建文件 META-INF/dubbo/org.apache.dubbo.common.threadpool.ThreadPool

watching=com.galaxy.WatchingThreadPool

(3)在服务提供方项目引入该依赖
(4)在服务提供方项目中设置使用该线程池生成器

  <!--自定义线程池-->
    <dubbo:protocol threadpool="watching" />
<!--注解形式则是-->
dubbo.provider.threadpool=watching

(5)接下来需要做的就是模拟整个流程,因为该线程当前是每 1 秒抓一次数据,所以我们需要对该方法的提供者超过 1 秒的时间(比如这里用休眠 Thread.sleep ),消费者则需要启动多个线程来并行执行,来模拟整个并发情况。
(6)在调用方则尝试简单通过 for 循环启动多个线程来执行 查看服务提供方的监控情况

package com.galaxy.demo;

import com.galaxy.service.HelloService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * @author lane
 * @date 2021年06月17日 上午11:10
 */
public class ConsumerDemo {

    public static void main(String[] args) throws InterruptedException, IOException {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:dubbo-consumer.xml");
        applicationContext.start();//不加也可以
        HelloService helloService = (HelloService) applicationContext.getBean("helloService");

        while(true){
            Thread.sleep(10);
            new Thread(()->{

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String galaxy = helloService.syaHello("galaxy");
                System.out.println(galaxy);

            }).start();

        }
   }
}

初始状态

image.png

消费者请求时运行状态

image.png

5、路由规则

路由是决定一次请求中需要发往目标机器的重要判断,通过对其控制可以决定请求的目标机器。我们可 以通过创建这样的规则来决定一个请求会交给哪些服务器去处理。

5.1 路由规则快速入门

(1)提供两个提供者(一台本机作为提供者,一台为其他的服务器),每个提供者会在调用时可以返回不 同的信息 以区分提供者。

(2)针对于消费者,我们这里通过一个死循环,每次等待用户输入,再进行调用,来模拟真实的请求 情况。通过调用的返回值 确认具体的提供者。

(3)我们通过 ipconfig 来查询到我们的 IP 地址,并且单独启动一个客户端,来进行如下配置(这里假设 我们希望隔离掉本机的请求,都发送到另外一台机器上)。

package com.galaxy.route;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.registry.Registry;
import org.apache.dubbo.registry.RegistryFactory;

/**
 * @author lane
 * @date 2021年06月18日 下午5:02
 */
public class DubboRouterMain {
    public static void main(String[] args) {
        RegistryFactory
                registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
        Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://127.0.0.1:2181"));
        //配置排除ip为92.168.20.1的机器
        registry.register(URL.valueOf("condition://0.0.0.0/com.galaxy.service.HelloService?category=routers&force=true&dynamic=true&rule="
                + URL.encode("=> host != 192.168.20.1")));
    }

}

(4)通过这个程序执行后,我们就通过消费端不停的发起请求,看到真实的请求都发到了除去本机以 外的另外一台机器上。

5.2 路由规则详解

image.png

5.3 路由与上线系统结合

当公司到了一定的规模之后,一般都会有自己的上线系统,专门用于服务上线。方便后期进行维护和记 录的追查。我们去想象这样的一个场景,一个 dubbo 的提供者要准备进行上线,一般都提供多台提供者 来同时在线上提供服务。这时候一个请求刚到达一个提供者,提供者却进行了关闭操作。那么此次请求 就应该认定为失败了。

我们可以通过路由的规则,把预发布(灰度)的机器进行从 机器列表中移除。并且等待一定的时间,让其把现有的请求处理完成之后再进行关闭服务。同时,在启 动时,同样需要等待一定的时间,以免因为尚未重启结束,就已经注册上去。等启动到达一定时间之 后,再进行开启流量操作。

实现主体思路

1.利用 zookeeper 的路径感知能力,在服务准备进行重启之前将当前机器的 IP 地址和应用名写入 zookeeper。

2.服务消费者监听该目录,读取其中需要进行关闭的应用名和机器 IP 列表并且保存到内存中。

3.当前请求过来时,判断是否是请求该应用,如果是请求重启应用,则将该提供者从服务列表中移除。

实现步骤

(1)引入 Curator 框架,用于方便操作 Zookeeper

(2)编写 Zookeeper 的操作类,用于方便进行 zookeeper 处理

(3)编写需要进行预发布的路径管理器,用于缓存和监听所有的待灰度机器信息列表。

(4)编写路由类(实现 org.apache.dubbo.rpc.cluster.Router ),主要目的在于对 ReadyRestartInstances 中的数据进行处理,并且移除路由调用列表中正在重启中的服务。

(5)由于 Router 机制比较特殊,所以需要利用一个专门的 RouterFactory 来生成,原因在于并不是 所有的都需要添加路由,所以需要利用 @Activate 来锁定具体哪些服务才需要生成使用。

(6)对 RouterFactory 进行注册,同样放入到 META-INF/dubbo/org.apache.dubbo.rpc.cluster.RouterFactory 文件中。

(7)将 dubbo-spi-router 项目引入至 consumer 项目的依赖中。

(8)这时直接启动程序,还是利用上面中所写好的 consumer 程序进行执行,确认各个 provider 可以正常执行。

(9)单独写一个 main 函数来进行将某台实例设置为启动中的状态,比如这里我们认定为当前这台机器 中的 service-provider 这个提供者需要进行重启操作。

(10)执行完成后,再次进行尝试通过 consumer 进行调用,即可看到当前这台机器没有再发送任何请求

(11)一般情况下,当机器重启到一定时间后,我们可以再通过 removeRestartingInstance 方法对 这个机器设定为既可以继续执行。

(12)调用完成后,我们再次通过 consumer 去调用,即可看到已经再次恢当前机器的请求参数。

代码实现

ReadyRestartInstances


public class ReadyRestartInstances  implements PathChildrenCacheListener {
    private  static  final Logger  LOGGER  = LoggerFactory.getLogger( ReadyRestartInstances.class);
    private  static  final  String LISTEN_PATHS = "/lagou/dubbo/restart/instances";

    private   final   CuratorFramework  zkClient;
    // 当节点变化时 给这个集合赋值 重启机器的信息列表
    private volatile Set<String>  restartInstances = new HashSet<>();

    private  ReadyRestartInstances(CuratorFramework zkClient) {
        this.zkClient = zkClient;
    }
    public static  ReadyRestartInstances  create(){
        final   CuratorFramework  zookeeperClient = ZookeeperClients.client();
        try {
            // 检查监听路径是否存在
            final Stat stat =  zookeeperClient.checkExists().forPath(LISTEN_PATHS);
            // 如果监听路径不存在 则创建
            if (stat == null){
                zookeeperClient.create().creatingParentsIfNeeded().forPath(LISTEN_PATHS);
            }
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("确保基础路径存在");
        }
        final   ReadyRestartInstances  instances = new ReadyRestartInstances(zookeeperClient);
        // 创建一个NodeCache
        PathChildrenCache  nodeCache = new PathChildrenCache(zookeeperClient,LISTEN_PATHS,false);
        // 给节点缓存对象 加入监听
        nodeCache.getListenable().addListener(instances);
        try {
            nodeCache.start();
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("启动路径监听失败");
        }
        return instances;
    }
    /** 返回应用名  和  主机拼接后的字符串 */
    private  String   buildApplicationAndInstanceString(String  applicationName,String host){
        return  applicationName + "_" + host;
    }
    /** 增加重启实例的配置信息方法 */
    public void  addRestartingInstance(String applicationName,String host) throws  Exception{
         zkClient.create().creatingParentsIfNeeded().forPath(LISTEN_PATHS + "/" + buildApplicationAndInstanceString(applicationName,host));
    }
    /** 删除重启实例的配置信息方法 */
    public void removeRestartingInstance(String applicationName,String host) throws  Exception{
        zkClient.delete().forPath(LISTEN_PATHS + "/" + buildApplicationAndInstanceString(applicationName,host));
    }
    /** 判断节点信息 是否存在于 restartInstances */
    public  boolean  hasRestartingInstance(String applicationName,String host){
        return  restartInstances.contains(buildApplicationAndInstanceString(applicationName,host));
    }
    @Override
    public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
          // 查询出监听路径下 所有的目录配置信息
        final List<String>  restartingInstances = zkClient.getChildren().forPath(LISTEN_PATHS);
        // 给 restartInstances
        if(CollectionUtils.isEmpty(restartingInstances)){
            this.restartInstances = Collections.emptySet();
        }else{
            this.restartInstances = new HashSet<>(restartingInstances);
        }
    }
}

RestartingInstanceRouter



public class RestartingInstanceRouter  implements Router {
    private  final  ReadyRestartInstances  instances;
    private  final  URL  url;

    public RestartingInstanceRouter(URL url) {
        this.url = url;
        this.instances = ReadyRestartInstances.create();
    }

    @Override
    public URL getUrl() {
        return url;
    }

    @Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {

    // 如果没有在重启列表中 才会加入到后续调用列表
        return  invokers.stream().filter(i->!instances.hasRestartingInstance(i.getUrl().getParameter("remote.application"),i.getUrl().getIp()))
            .collect(Collectors.toList());
}
    @Override
    public boolean isRuntime() {
        return false;
    }

    @Override
    public boolean isForce() {
        return true;
    }

    @Override
    public int getPriority() {
        return 0;
    }
}

RestartingInstanceRouterFactory


@Activate
public class RestartingInstanceRouterFactory implements RouterFactory {
    @Override
    public Router getRouter(URL url) {
        return new RestartingInstanceRouter(url);
    }
}

ZookeeperClients



public class ZookeeperClients {
    private final CuratorFramework  client;
    private  static  ZookeeperClients  INSTANCE;

    static {
        RetryPolicy  retryPolicy  = new ExponentialBackoffRetry(1000,3);
        CuratorFramework  client  = CuratorFrameworkFactory.newClient("127.0.0.1:2181",retryPolicy);
        INSTANCE = new ZookeeperClients(client);
        client.start();
    }

    private  ZookeeperClients(CuratorFramework client) {
        this.client = client;
    }
    public  static  CuratorFramework client(){
        return  INSTANCE.client;
    }
}

META-INF/dubbo/org.apache.dubbo.rpc.cluster.RouterFactory

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

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

</project>

resumer 中 main

package com.galaxy.route;

import com.lagou.router.ReadyRestartInstances;

public class ServerRestartMain {
    public static void main(String[] args) throws  Exception {
        //如果这台机器在更新,就不要让消费者调用这台服务器,既是把该节点加入到zk中的重启列表,路由就不会路由到该机器
        //ReadyRestartInstances.create().addRestartingInstance("service-provider","192.168.20.1");
        //删除不让路由机器列表中的该机器节点信息
        ReadyRestartInstances.create().removeRestartingInstance("service-provider","192.168.20.1");
    }
}

6、服务动态降级

6.1 什么是服务降级

服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务有策略的降低服务级别, 以释放服务器资源,保证核心任务的正常运行。

6.2 为什么要服务降级

而为什么要使用服务降级,这是防止分布式服务发生雪崩效应,什么是雪崩?就是蝴蝶效应,当一个请 求发生超时,一直等待着服务响应,那么在高并发情况下,很多请求都是因为这样一直等着响应,直到 服务资源耗尽产生宕机,而宕机之后会导致分布式其他服务调用该宕机的服务也会出现资源耗尽宕机, 这样下去将导致整个分布式服务都瘫痪,这就是雪崩。

6.3 dubbo 服务降级实现方式

第一种 在 dubbo 管理控制台配置服务降级

屏蔽和容错

mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可 用时对调用方的影响。

mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳 定时对调用方的影响。

image.png

第二种 指定返回简单值或者 null

<dubbo:reference id="xxService" check="false" interface="com.xx.XxService"

timeout="3000" mock="return null" />

<dubbo:reference id="xxService2" check="false" interface="com.xx.XxService2"

timeout="3000" mock="return 1234" />

如果是标注 则使用@Reference(mock=“return null”) 也支持 @Reference(mock=“force:return null”)

第三种 使用 java 代码 动态写入配置中心

RegistryFactory registryFactory =

ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension() ; 
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://IP:端口"));

registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?

category=configurators&dynamic=false&application=foo&mock=force:return+null"));

第四种 整合整合 hystrix 会在后期 SpringCloud 课程中详细讲解

四、Dubbo 源码剖析

1、源码下载和编译

源码下载、编译和导入步骤如下:

(1)dubbo 的项目在 github 中的地址为: https://github.com/apache/dubbo

(2)进入需要进行下载的地址,执行 git clone https://github.com/apache/dubbo.git

(3)为了防止 master 中代码不稳定,进入 dubbo 项目 cd dubbo 切入到最近的 release 分支 checkout 2.7.6-release(不必要,看哪个可以)

(4)进行本地编译,进入 dubbo 项目 cd dubbo 进行编译操作 mvn clean install -DskipTests

(5)使用 IDE 引入项目。

建议使用 ssh 的地址进行下载,git@github.com:apache/dubbo.git

image.png

image.png

2、架构整体设计

2.1 Dubbo 调用关系说明

image.png

在这里主要由四部分组成:

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

Protocol 负责提供者和消费者之间协议交互数据

Service 真实的业务服务信息 可以理解成接口 和 实现

Container Dubbo 的运行环境

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

Protocol 负责提供者和消费者之间协议交互数据

Cluster 感知提供者端的列表信息 Proxy 可以理解成 提供者的服务调用代理类 由它接管 Consumer 中的接口调用逻辑 Registry: 注册中心,用于作为服务发现和路由配置等工作,提供者和消费者都会在这里进行注册

Monitor: 用于提供者和消费者中的数据统计,比如调用频次,成功失败次数等信息。

启动和执行流程说明:

提供者端启动 容器负责把 Service 信息加载 并通过 Protocol 注册到注册中心

消费者端启动 通过监听提供者列表来感知提供者信息 并在提供者发生改变时 通过注册中心及时 通知消费端

消费方发起 请求 通过 Proxy 模块 利用 Cluster 模块 来选择真实的要发送给的提供者信息

交由 Consumer 中的 Protocol 把信息发送给提供者

提供者同样需要通过 Protocol 模块来处理消费者的信息 最后由真正的服务提供者 Service 来进行处理

2.2 整体的调用链路

image.png

淡绿色代表是提供者

淡蓝色代表是消费者

绿色块代表是接口

蓝色块代表是实现类

暗红色代表是调用

虚线代表初始化

左边三层分别是业务逻辑层、PRC 远程调用、Remoting 远程传输

整体链路调用的流程:

  1. 消费者通过 Interface 进行方法调用 统一交由消费者端的 Proxy 通过 ProxyFactory 来进行代理 对象的创建 使用到了 jdk javassist 技术
  2. 交给 Filter 这个模块 做一个统一的过滤请求 在 SPI 案例中涉及过
  3. 接下来会进入最主要的 Invoker 调用逻辑

通过 Directory 去配置中新读取信息 最终通过 list 方法获取所有的 Invoker 通过

Cluster 模块 根据选择的具体路由规则 来选取 Invoker 列表

通过 LoadBalance 模块 根据负载均衡策略 选择一个具体的 Invoker 来处理我们的请求

如果执行中出现错误 并且 Consumer 阶段配置了重试机制 则会重新尝试执行

  1. 继续经过 Filter 进行执行功能的前后封装 Invoker 选择具体的执行协议
  2. 客户端 进行编码和序列化 然后发送数据
  3. 到达 Consumer 中的 Server 在这里进行 反编码 和 反序列化的接收数据
  4. 使用 Exporter 选择执行器
  5. 交给 Filter 进行一个提供者端的过滤 到达 Invoker 执行器
  6. 通过 Invoker 调用接口的具体实现 然后返回
2.3 Dubbo 源码整体设计

image.png

图例说明:

图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于 中轴线上的为双方都用到的接口。

图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可 以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。

图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。

图中蓝色虚线为初始化过程,即启动时组装链,

红色实线为方法调用过程,即运行时调时链,

紫色 三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。

Dubbo 源码整体设计与调用链路十分相似。只不过这里可以看到接口的一些具体实现以及左侧也 有更为详细的层次划分,我们在后面的源码解析时也会着重介绍其中比较重要的模块实现。

interface 业务接口

->proxy 代理接口

->filter 过滤器 mock cache 之类的

->invoker 选择执行器 先从注册中心获取所有路由 list 根据 load balance 选择具体的提供者

->filter monitor 相关的统计

->protocol invoker 选择协议 dubbo 还是 rmi、hessian、http

->transport client 选择 netty、mina

->codec 编码

->serialized 序列化

-> 解码和反序列化

->transport server

->exchange

->protocol export rmi

->fliter monitor

->invoker 从注册中心获取具体

->fliter

->implement

分层介绍

10 层分为三大层

业务逻辑层 1

RPC 远程调用层 2-7

Remoting 远程数据传输 8910

image.png

3、服务注册与消费源码剖析

3.1 注册中心 Zookeeper 剖析

注册中心是 Dubbo 的重要组成部分,主要用于服务的注册与发现,我们可以选择 Redis、Nacos、 Zookeeper 作为 Dubbo 的注册中心,Dubbo 推荐用户使用 Zookeeper 作为注册中心。

注册中心 Zookeeper 目录结构

我们使用一个最基本的服务的注册与消费的 Demo 来进行说明。

例如:只有一个提供者和消费者。 com.lagou.service.HelloService

public interface HelloService { String sayHello(String name); }

image.png

可以在这里看到所有的都是在 dubbo 层级下的

dubbo 跟节点下面是当前所拥有的接口名称,如果有多个接口,则会以多个子节点的形式展开

每个服务下面又分别有四个配置项

consumers: 当前服务下面所有的消费者列表(URL)

providers: 当前服务下面所有的提供者列表(URL)

configuration: 当前服务下面的配置信息信息,provider 或者 consumer 会通过读取这里的配 置信息来获取配置

routers: 当消费者在进行获取提供者的时,会通过这里配置好的路由来进行适配匹配规则。

可以看到,dubbo 基本上很多时候都是通过 URL 的形式来进行交互获取数据的,在 URL 中也会保存 很多的信息。后面也会对 URL 的规则做详细介绍。

image.png

通过这张图我们可以了解到如下信息:

提供者会在 providers 目录下进行自身的进行注册。

消费者会在 consumers 目录下进行自身注册,并且监听 provider 目录,以此通过监听提供者增 加或者减少,实现服务发现。 Monitor 模块会对整个服务级别做监听,用来得知整体的服务情况。以此就能更多的对整体情况做 监控。

3.2 服务的注册过程分析

服务注册(暴露)过程

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

3.3 URL 规则详解 和 服务本地缓存

3.3.1 URL 规则详解

image.png

3.3.2 服务本地缓存

在上面我们有讲到 dubbo 有对路径进行本地缓存操作。这里我们就对本地缓存进行讲解。

dubbo 调用者需要通过注册中心(例如:ZK)注册信息,获取提供者,但是如果频繁往从 ZK 获取信 息,肯定会存在单点故障问题,所以 dubbo 提供了将提供者信息缓存在本地的方法。

Dubbo 在订阅注册中心的回调处理逻辑当中会保存服务提供者信息到本地缓存文件当中(同步/异步两 种方式),以 URL 纬度进行全量保存。

Dubbo 在服务引用过程中会创建 registry 对象并加载本地缓存文件,会优先订阅注册中心,订阅注册中 心失败后会访问本地缓存文件内容获取服务提供信息。

image.png

image.png

image.png

3.4 Dubbo 消费过程分析

服务消费流程

image.png

首先 ReferenceConfig 类的 init 方法调用 createProxy() ,期间 使用 Protocol 调用 refer 方法生 成 Invoker 实例(如上图中的红色部分),这是服务消费的关键。接下来使用 ProxyFactory 把 Invoker 转换为客户端需要的接口(如:HelloService)。

protocol 对象获取 invoker 对象里面有 zk 信息和接口方法参数信息,通过 proxyFactory 获取 proxy 对象 refer 供业务调用

4、Dubbo 扩展 SPI 源码剖析

4.1 getExtensionLoader 的加载过程

SPI 在之前都有使用过,其中最重要的类就是 ExtensionLoader 它是所有 Dubbo 中 SPI 的入口。

image.png

image.png

image.png

image.png

image.png

image.png

image.png

4.2 根据 name 获取扩展点的方法 getExtension

image.png

image.png

image.png

image.png

4.3 Adaptive 功能实现原理

Adaptive 的主要功能是对所有的扩展点进行封装为一个类,通过 URL 传入参数的时动态选择需要使用的 扩展点。其底层的实现原理就是动态代理,这里我们会通过源码的形式告诉大家,他是如何通过动态代 理进行加载的。

(1)这里我们 getAdaptiveExtension 方法讲起,这个里面就是真正获取该类。这里可以看到 ExtentionLoader 中大量的使用了 Holder 和加锁的方式去进行唯一创建。

image.png

image.png

image.png

image.png

5、集群容错源码剖析

在对集群相关代码进行分析之前,这里有必要先来介绍一下集群容错的所有组件。包含 Cluster、 Cluster Invoker、Directory、Router 和 LoadBalance 等。

image.png

集群工作过程可分为两个阶段,

第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例,即上图中的 merge 操作。

第二个阶段是在服务消费者进行远程调用 时。以 FailoverClusterInvoker 为例,该类型 Cluster Invoker 首先会调用 Directory 的 list 方法列举 Invoker 列表(可将 Invoker 简单理解为服务提供者)。

Directory 的用途是保存 Invoker 列表,可简单 类比为 List。其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持 有的 Invoker 列表会随着注册中心内容的变化而变化。

每次变化后,RegistryDirectory 会动态增删 Invoker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。

当 FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后,它会通过 LoadBalance 从 Invoker 列 表中选择一个 Invoker。最后 FailoverClusterInvoker 会将参数传给 LoadBalance 选择出的 Invoker 实例的 invoke 方法,进行真正的远程调用。

Dubbo 主要提供了这样几种容错方式:

Failover Cluster - 失败自动切换 失败时会重试其它服务器

Failfast Cluster - 快速失败 请求失败后快速返回异常结果 不重试

Failsafe Cluster - 失败安全 出现异常 直接忽略 会对请求做负载均衡

Failback Cluster - 失败自动恢复 请求失败后 会自动记录请求到失败队列中

Forking Cluster - 并行调用多个服务提供者 其中有一个返回 则立即返回结果

5.1 信息缓存接口 Directory

Directory 是 Dubbo 中的一个接口,主要用于缓存当前可以被调用的提供者列表信息。我们在消费者进 行调用时都会通过这个接口来获取所有的提供者列表,再进行后续处理。

image.png

image.png

image.png

image.png

5.2 路由规则实现原理

这里我们具体来讲解一下 RouterChain ConditionRouter 的实现来做说明。这里我们主要对 ConditionRouter 的实现来说明

image.png

image.png

image.png

image.png

image.png

5.3 Cluster 组件

下面我们再来看看再 Dubbo 中也是很关键的组件:Cluster 它主要用于代理真正的 Invoker 执行时做处理,提供了多种容错方案。

(1)我们首先来看一下他的接口定义。这里我们在之前也有见到过 doRefer 那里也是真正调用它来生成的位置。

image.png

image.png

image.png

image.png

5.4 负载均衡实现原理

通过上面一小节我们也有看到在 是如何负载均衡的。Cluster 中经过负载选择真正 Invoker 的代码,这里我们再来细追踪下负载均衡

image.png

image.png

5.5 Invoker 执行逻辑

image.png

image.png

image.png

image.png

6、网络通信原理剖析

主要讲解 Dubbo 在网络中如何进行通信的。由于请求都是基于 TCP 的,那么 Dubbo 中是如何处理粘包和 拆包的问题,这里我们也会有讲解。

dubbo 协议采用固定长度的消息头(16 字节)和不定长度的消息体来进行数据传输,消息头定义了底层 框架(netty)在 IO 线程处理时需要的信息,协议的报文格式如下:

6.1 数据包结构讲解

image.png

image.png

image.png

image.png

6.2 数据协议 ExchangeCodec 详解

image.png

image.png

image.png

image.png

image.png

image.png

image.png

6.3 处理粘包和拆包问题

当发生 TCP 拆包问题时候 这里假设之前还没有发生过任何数据交互,系统刚刚初始化好,那么这个时候在 InternalDecoder 里面的 buffer 属性会是 EMPTY_BUFFER。当发生第一次 inbound 数据的时候,第一次 在 InternalDecoder 里面接收的肯定是 dubbo 消息头的部分(这个由 TCP 协议保证),由于发生了拆包情 况,那么此时接收的 inbound 消息可能存在一下几种情况

1、当前 inbound 消息只包含 dubbo 协议头的一部分

2、当前 inbound 消息只包含 dubbo 的协议头

3、当前 inbound 消息只包含 dubbo 消息头和部分 payload 消息

通过上面的讨论,我们知道发生上面三种情况,都会触发 ExchangeCodec 返回 NEED_MORE_INPUT,由于 在 DubboCountCodec 对于返回 NEED_MORE_INPUT 会回滚读索引,所以此时的 buffer 里面的数据可以当作 并没有发生过读取操作,并且 DubboCountCodec 的 decode 也会返回 NEED_MORE_INPUT,在 InternalDecoder 对于当判断返回 NEED_MORE_INPUT,也会进行读索引回滚,并且退出循环,最后会执 行 finally 内容,这里会判断 inbound 消息是否还有可读的,由于在 DubboCountCodec 里面进行了读索引 回滚,所以此时的 buffer 里面不是完整的 inbound 消息,等待第二次的 inbound 消息的到来,当第二次 inbound 消息过来的时候,再次经过上面的判断。

粘粘包不完整的放入缓冲区,等待下次完整再解析

image.png

image.png

image.png

当发生 TCP 粘包的时候 是 tcp 将一个 dubbo 协议栈放在一个 tcp 包中,那么有可能发生下面几种情况

1、当前 inbound 消息只包含一个 dubbo 协议栈

2、当前 inbound 消息包含一个 dubbo 协议栈,同时包含部分另一个或者多个 dubbo 协议栈内容 如果发生只包含一个协议栈,那么当前 buffer 通过 ExchangeCodec 解析协议之后,当前的 buffer 的 readeIndex 位置应该是 buffer 尾部,那么在返回到 InternalDecoder 中 message 的方法 readable 返回 的是 false,那么就会对 buffer 重新赋予 EMPTY_BUFFER 实体,而针对包含一个以上的 dubbo 协议栈,当然 也会解析出其中一个 dubbo 协议栈,但是经过 ExchangeCodec 解析之后,message 的 readIndex 不在 message 尾部,所以 message 的 readable 方法返回的是 true。那么则会继续遍历 message,读取下面的 信息。

最终要么 message 刚好整数倍包含完整的 dubbo 协议栈,要不 ExchangeCodec 返回 NEED_MORE_INPUT,最后将未读完的数据缓存到 buffer 中,等待下次 inbound 事件,将 buffer 中的消息合 并到下次的 inbound 消息中,种类又回到了拆包的问题上。

dubbo 在处理 tcp 的粘包和拆包时是借助 InternalDecoder 的 buffer 缓存对象来缓存不完整的 dubbo 协议栈数据,等待下次 inbound 事件,合并进去。所以说在 dubbo 中解决 TCP 拆包和粘包的时候是通过 buffer 变量来解决的。

结语

讲师程道,老架构师了, 不是很想写什么,有点累,对于源码只要知道整体流程就可以了,至于细节了解下就可以了,需要真正的去了解的时候再看吧,想全记住也不太现实,现在感觉头有点疼。这篇文章是第二篇用思源笔记整理的了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值