Dubbo执行原理分析

一,基础知识

1.分布式基础理论

1.1,什么是分布式系统

分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。

分布式系统是建立在网络之上的软件系统。

1.2,发展演变

单一应用架构

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

在这里插入图片描述

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

缺点:

1.性能扩展比较难

2.协同开发问题

3.不利于升级维护

垂直应用架构

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

在这里插入图片描述

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

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

分布式服务架构

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

在这里插入图片描述

流动计算架构

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

在这里插入图片描述

1.3,什么是RPC

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

RPC基本原理

在这里插入图片描述

在这里插入图片描述

RPC两个核心模块:通讯,序列化。

2.dubbo概念

2.1,简介

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

官网

2.2,基本概念

在这里插入图片描述

服务提供者:暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。

服务消费者:调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

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

监控中心:服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

调用关系说明

服务容器负责启动,加载,运行服务提供者。

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

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

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

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

服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

3.dubbo-helloworld

项目结构

Dubbo-Nacos
	--common-api
	--cons
	--prod

Dubbo-Nacos–pom文件

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.2.6.RELEASE</version>
    </parent>
    <groupId>com.yhd</groupId>
    <artifactId>Dubbo-Nacos</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>common-api</module>
        <module>prod</module>
        <module>cons</module>
    </modules>
    <packaging>pom</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

common-api–pom文件

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
    </dependencies>

common-api–公共接口

public interface OrderService {

    void  createOrder(Integer uid,Integer skuid,Integer skunum);
}

prod–pom文件

    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>com.yhd</groupId>
            <artifactId>common-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-nacos</artifactId>
            <version>2.7.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.8</version>
        </dependency>
    </dependencies>

prod-application.yml

server:
  port: 8001
spring:
  application:
    name: service-prod
dubbo:
  # 配置服务信息
  application:
    name: service-prod
    # 禁用QOS同一台机器可能会有端口冲突现象
    qos-enable: false
    qos-accept-foreign-ip: false
  # 配置注册中心
  registry:
    address: nacos://121.199.31.160:8848
  # 设置协议-协议由提供方指定消费方被动接受
  protocol:
    name: dubbo
    port: 20880

ProdApplication

@EnableDubbo
@SpringBootApplication
public class ProdApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProdApplication.class,args);
    }
}

OrderServiceImpl

@DubboService
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Override
    public void createOrder(Integer uid, Integer skuid, Integer skunum) {
        log.info("createOrder()执行:参数为:"+uid+"..."+skuid+"..."+skunum);
    }
}

cons-pom文件

    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>com.yhd</groupId>
            <artifactId>common-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-nacos</artifactId>
            <version>2.7.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.8</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
    </dependencies>

application.yml

server:
  port: 8002
dubbo:
  # 配置服务信息
  application:
    name: service-cons
    # 禁用QOS同一台机器可能会有端口冲突现象
    qos-enable: false
    qos-accept-foreign-ip: false
  # 配置注册中心
  registry:
    address: nacos://121.199.31.160:8848
  # 设置超时时间
  consumer:
    timeout: 4000
spring:
  application:
    name: service-cons

ConsApplication

@EnableDubbo
@SpringBootApplication
public class ConsApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsApplication.class,args);
    }
}

OrderController

@RestController
@RequestMapping("order")
@Slf4j
public class OrderController {

    @DubboReference
    private OrderService orderService;

    @GetMapping("demo")
    public String createOrder(){
        orderService.createOrder(1,1,5);
        return "success";
    }
}

二,Dubbo原理

1.RPC原理

在这里插入图片描述

一次完整的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步都封装起来,这些细节对用户来说是透明的,不可见的。

2.netty通信原理

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

BIO(Blocking IO):
在这里插入图片描述

NIO(Non-Blocking IO):

在这里插入图片描述

selector 一般称为选择器,也可以翻译为多路复用器,connect(连接就绪),Accept(接受就绪),read(读就绪),Write(写就绪)

Netty基本原理

在这里插入图片描述

3.dubbo原理

3.1.dubbo原理-框架设计

在这里插入图片描述

config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类

proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory

registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService

cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance

monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService

protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter

exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer

transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec

serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
在这里插入图片描述

3.2.dubbo原理-启动解析,加载配置信息

在这里插入图片描述

在dubbo的META-INF目录下有一个 spring.handlers 配置文件。容器启动就会加载这个配置文件。

这个配置文件里面注册了一个 Bean 叫做 DubboNamespaceHandler ,这个 bean 就是dubbo 配置文件处理器。

在这个 bean里面有一个init(),这个方法里面会加载很多的 DubboBeanDefinitionParser ,DubboBeanDefinitionParser就是配置文件解析器。(除了 service 和 reference 对应的类叫 ServiceBean 和 ReferenceBean ,其他对应标签对应的类都是xxxConfig)。

DubboBeanDefinitionParser 的parse() 就是对标签的解析,他会判断这个标签所属的类型,然后根据标签的配置进行属性填充。最终将所有的bean注册到配置中心。

3.3.dubbo原理-服务暴露

当容器在加载ServiceBean 的时候,执行了服务暴露。onApplicationEvent 方法在经过一些判断后,会决定是否调用 export 方法导出服务。

export 方法对两项配置进行了检查,并根据配置执行相应的动作。首先是 export 配置,这个配置决定了是否导出服务。delay 配置顾名思义,用于延迟导出服务,这个就不分析了。下面,我们继续分析源码,这次要分析的是 doExport 方法。

Dubbo 允许我们使用不同的协议导出服务,也允许我们向多个注册中心注册服务。Dubbo 在 doExportUrls 方法中对多协议,多注册中心进行了支持。

首先是通过 loadRegistries 加载注册中心链接,然后再遍历 ProtocolConfig 集合导出每个服务。并在导出服务的过程中,将服务注册到注册中心。

配置检查完毕后,紧接着要做的事情是根据配置,以及其他一些信息组装 URL。URL 是 Dubbo 配置的载体,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。doExportUrlsFor1Protocol()首先是将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中,map 中的内容将作为 URL 的查询字符串。构建好 map 后,紧接着是获取上下文路径、主机名以及端口号等信息。最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。

doExportUrls()

    private void doExportUrls() {
        ServiceRepository repository = ApplicationModel.getServiceRepository();
        ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
        repository.registerProvider(
                getUniqueServiceName(),
                ref,
                serviceDescriptor,
                this,
                serviceMetadata
        );

        List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);

        for (ProtocolConfig protocolConfig : protocols) {
            String pathKey = URL.buildKey(getContextPath(protocolConfig)
                    .map(p -> p + "/" + path)
                    .orElse(path), group, version);
            // 服务注册
            repository.registerService(pathKey, interfaceClass);
            // TODO, uncomment this line once service key is unified
            serviceMetadata.setServiceKey(pathKey);
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

最终注册完成服务以后,doExportUrls()调用了doExportUrlsFor1Protocol()方法,而在doExportUrlsFor1Protocol()方法里面实际上调用了

Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));

==============
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);

而这个PROTOCOL.export(wrapperInvoker)实际上就是调用了openServer(url);开启服务器,完成注册服务到注册中心。

3.4.dubbo原理-服务引用

在Dubbo中,有两种方式调用远程的服务,一种是服务直连,一种是基于注册中心。服务直连并不适合线上生产环境。

从注册中心获取服务配置只是服务应用过程中的一环,除此之外,消费者还要经过invoker创建,代理类创建等步骤。

Dubbo服务应用的时机有两个,第一个是在Spring容器完成创建回调afterPropertiesSet方法时引用服务,第二个是在ReferenceBean对应的服务被注入到其他类中时引用。这两个服务引用的区别在于:第一个是饿汉式(通过配置文件配置dubbo.reference开启),第二个是懒汉式,默认情况下,Dubbo使用懒汉式。

从Dubbo的默认配置进行分析,整个过程从referenceBean的getObject()开始,当我们的服务被注入到其他类中时,Spring 会第一时间调用 getObject 方法,并由该方法执行服务引用逻辑。

按照惯例,在进行具体工作之前,需先进行配置检查与收集工作。接着根据收集到的信息决定服务引用的方式,有三种,第一种是引用本地 (JVM) 服务,第二是通过直连方式引用远程服务,第三是通过注册中心引用远程服务。不管是哪种引用方式,最后都会得到一个 Invoker 实例。

如果有多个注册中心,多个服务提供者,这个时候会得到一组 Invoker 实例,此时需要通过集群管理类 Cluster 将多个 Invoker 合并成一个实例。合并后的 Invoker 实例已经具备调用本地或远程服务的能力了,但并不能将此实例暴露给用户使用,这会对用户业务代码造成侵入。

此时框架还需要通过代理工厂类 (ProxyFactory) 为服务接口生成代理类,并让代理类去调用 Invoker 逻辑。避免了 Dubbo 框架代码对业务代码的侵入,同时也让框架更容易使用。

源码分析

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean,
        ApplicationContextAware, InitializingBean, DisposableBean {
            
    @Override
    public Object getObject() {
        return get();
    }

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.5.dubbo原理-服务调用

Dubbo 服务调用过程比较复杂,包含众多步骤,比如发送请求、编解码、服务降级、过滤器链处理、序列化、线程派发以及响应请求等步骤。

重点分析请求的发送与接收、编解码、线程派发以及响应的发送与接收等过程。

首先服务消费者通过代理对象 Proxy 发起远程调用,接着通过网络客户端 Client 将编码后的请求发送给服务提供方的网络层上,也就是 Server。Server 在收到请求后,首先要做的事情是对数据包进行解码。然后将解码后的请求发送至分发器 Dispatcher,再由分发器将请求派发到指定的线程池上,最后由线程池调用具体的服务。这就是一个远程调用请求的发送与接收过程。

在这里插入图片描述

源码分析

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值