【深入理解 Dubbo 与实战】1-2 什么是Dubbo

1 Dubbo - 高性能 RPC 通信框架

1.1 服务架构发展演变

在这里插入图片描述
1)单一应用架构:当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于**简化增删改查工作量的数据访问框架 ORM **是关键。

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

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

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

1.2 什么是 RPC

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

RPC 基本原理
在这里插入图片描述

1.3 Dubbo 介绍

Apache Dubbo 是一款高性能、轻量级的开源Java RPC框架。

1.3.1 基本概念

在这里插入图片描述

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

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

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

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

调用关系:

  • 服务容器负责启动,加载,运行服务提供者。
  • 服务提供者在启动时,向注册中心注册自己提供的服务。
  • 服务消费者在启动时,向注册中心订阅自己所需的服务。
  • 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  • 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

1.3.2 Dubbo 特性

分类特性
面向接口代理的高性能RPC调用提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节
服务自动注册与发现支持多种注册中心服务,服务实例上下线实时感知
运行期流量调度内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布、 同机房优先等功能
智能负载均衡内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟, 提高系统吞吐量
高度可扩展能力遵循微内核+插件的设计思想,所有核心能力如 Protocol、Transport、Serialization 被设计为扩展点,平等对待内置实现和第三方实现
可视化的服务治理与运维提供丰富服务治理、运维工具:随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数

1.2.3 Dubbo 解决什么问题

1)高性能、透明的RPC调用。只要涉及服务之间的通信,RPC就必不可少。Dubbo 可以让开发者像调用本地的方法一样调用远程服务,而不需要显式在代码中指定是远程调用。整个过程对上层开发者透明,Dubbo 会自动完成后续的所有操作,例如:负载均衡、路由、协议转换、序列化等。开发者只需要接收对应的调用结果即可。

2)服务的自动注册与发现。当服务越来越多时,服务URL配置管理变得非常困难,服务的注册和发现已经不可能由人工管理。此时需要一个服务注册中心,动态地注册和发现服务,使服务的位置透明。Dubbo 适配了多种注册中心,服务消费方(消费者)可以通过订阅注册中心,及时地知道其他服务提供者的信息,全程无须人工干预。

3)自动负载与容错。Dubbo提供了完整的集群容错机制,可以实现软件层面的负载均衡,以此降低硬件的压力。Dubbo 还提供了调用失败的各种容错机制,如Failover、Failfast、结果集合并等。

4)动态流量调度。在应用运行时,某些服务节点可能因为硬件原因需要减少负载; 或者某些节点需要人工手动下线;又或者需要实现单元化的调用、灰度功能。Dubbo提供了管理控制台,用户可以在界面上动态地调整每个服务的权重、路由
规则、禁用/启用,实现运行时的流量调度。

5)依赖分析与调用统计。当应用规模进一步提升,服务间的依赖关系变得错综复杂, 甚至分不清哪个应用要在哪个应用之前启动。服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器? Dubbo 可以接入三方 APM 做分布式链路追踪与性能分析,或者使用己有的独立监控中心来监控接口的调用次数及耗时,用户可以根据这些数据反推出系统容量。

1.4 Dubbo 总体结构

1.4.1 Dubbo 总体分层

Dubbo的总体分为业务层(Biz)、RPC层、Remote 三层。
在这里插入图片描述
Service和Config两层可以认为是API层,主要提供给API使用者,使用者无须关心底层的实现,只需要配置和完成业务代码即可;后面所有的层级合在一起,可以认为是SPI层,主要提供给扩展者使用,即用户可以基于Dubb。框架做定制性的二次开发,扩展其功能。

1.4.2 Dubbo 核心组件

层次作用
Service业务层。包括业务代码的接口与实现,即开发者实现的业务代码
config配置层。主要围绕ServiceConfig (暴露的服务配置)和ReferenceConfig (引用的服务配置)两个实现类展开,初始化配置信息。可以理解为该层管理了整个Dubbo的配置
proxy服务代理层。在Dubbo中,无论生产者还是消费者,框架都会生成一个代理类,整个过程对上层是透明的
registry注册层。负责Dubbo框架的服务注册与发现。当有新的服务加入或旧服务下线时,注册中心都会感知并通知给所有订阅方。整个过程不需要人工参与
cluster集群容错层。该层主要负责:远程调用失败时的容错策略(如失败重试、快速失败)
monitor监控层。这一层主要负责监控统计调用次数和调用时间等
protocol远程调用层。封装RPC调用具体过程,Protocol是Invoker暴露(发布一个服务让别人可以调用)和引用(引用一个远程服务到本地)的主功能入口,它负责管理Invoker的整个生命周期
exchange信息交换层。建立Request-Response模型,封装请求响应模式,如把同步请求转化为异步请求
transport网络传输层。把网络传输抽象为统一的接口,如 Mina 和 Netty 虽然接口不一样,但是 Dubbo 在它们上面又封装了统一的接口。用户也可以根据其扩展接口添加更多的网络传输方式
serialize序列化层。如果数据要通过网络进行发送,则需要先做序列化,变成二进制流。序列化层负责管理整个框架网络传输时的序列化/反序列化工作

1.4.3 Dubbo 总体调用过程

1)服务器端(服务提供者)在框架启动时,会初始化服务实例,通过 Proxy 组件调用具体协议(Protocol ),把服务端要暴露的接口封装成 Invoker(真实类型是 AbstractProxylnvoker),然后转换成 Exporter(用于暴露到注册中心的对象,它的内部属性持有了 Invoker对象),这个时候框架会打开服务端口等并记录服务实例到内存中,最后通过 Registry 把服务元数据注册到注册中心。

2)消费方在启动时会通过Registry在注册中心订阅服务端的元数据(包括IP和端口)。这样就可以得到刚才暴露的服务了。

3)消费者调用服务提供者的总体流程。
在这里插入图片描述
(1)调用过程从一个Proxy 开始的,Proxy 持有了一个 Invoker 对象。然后触发 invoke 调用。
(2)invoke 调用过程中,需要使用 Cluster,Cluster 负责容错,如调用失败的重试。
(3)Cluster 在调用之前会通过 Directory 获取所有可以调用的远程服务 Invoker 列表(一个接口可能有多个节点提供服务)。如果用户配置了路由规则(如指定某些方法只能调用某个节点),那么还会根据路由规则将 Invoker 列表过滤一遍。
(4)剩下 Invoker 继续通过 LoadBalance 方法做负载均衡,最终选出一个可以调用的 Invoker。
(5)这个 Invoker 在调用之前又会经过一个过滤器链,这个过滤器链通常是处理上下文、限流、计数等。
(6)接着,会使用 Client 做数据传输,如我们常见的 Netty Client 等。
(7)传输之前使用 Codec 接口做一些私有协议的构造。
(8)构造完成后,就对数据包做序列化(Serialization),然后传输到服务提供者端。
(9)服务提供者收到数据包,也会使用 Codec 处理协议头及一些半包、 粘包等。处理完成后再对完整的数据报文做反序列化处理。
(10)然后,这个 Request 会被分配到线程池 ThreadPool 中进行处理。
(11)Server 会处理这些 Request,根据请求查找对应的 Exporter 它内部持有了 Invoker 对象。
(12)Invoker 是被用装饰器模式一层一层套了非常多 Filter 的,因此在调用最终的实现类之前,又会经过一个服务提供者端的过滤器链。
(13)最终,我们得到了具体接口的真实实现并调用,再原路把结果返回。

2 第一个 Dubbo 程序

2.1 配置 Dubbo 环境

1)下载源码
项目 git 地址:https://github.com/apache/incubator-dubbo
下载 2.6.x 版本的源码。

2)导入 Idea
下面是 Dubbo 主要模块的作用

名称作用
dubbo-common通用逻辑模块,提供工具类和通用模型
dubbo-remoting远程通信模块,为消费者和服务提供者提供通信能力
dubbo-rpc容易和remote模块混淆,本模块抽象各种通信协议,以及动态代理
dubbo-cluster集群容错模块,RPC只关心单个调用,本模块则包括负载均衡、集群容错、 路由、分组聚合等
dubbo-registry注册中心模块
dubbo-monitor监控模块,监控Dubbo接口的调用次数、时间等
dubbo-config配置模块,实现了 API配置、属性配置、XML配置、注解配置等功能
dubbo-cintainer容器模块,如果项目比较轻量,没用到Web特性,因此不想使用 Web 容器,则可以使用这个 Main 方法加载 Spring 的容器
dubbo-filter过滤器模块,包含Dubbo内置的过滤器
dubbo-plugin插件模块,提供内置的插件,如QoS
dubbo-demo一个简单的远程调用示例模块
dubbo-test测试模块,包含性能测试、兼容性测试等

3)代码测试
我们可以使用 dubbo-demo 模块。该模块可以演示一个完整的消费者调用服务提供者的过程。首先启动 dubbo-demo-provider,然后启动 dubbo-demo-consumer,直接运行对应模块的 main 方法即可。由于Demo使用的是广播,因此需要先启动服务提供者。此时消费者会不断调用服务提供者的 com.alibaba.dubbo.demo.DemoService#sayHello。运行结果如下。
在这里插入图片描述
如果我们想直接调试某个模块的代码,在 Dubbo 的每个模块中,除了 main目录下的源码,还有一个 test 目录,里面有完整的单元测试代码。当需要调试某个功能的源码时,建议首先阅读对应的单元测试代码,然后在对应的源码中打断点、运行单元测试。

2.2 基于 XML 配置实现

2.2.1 编写 Echo 服务器

Dubbo 框架是面向接口的 RPC 调用框架,需要提供一个新的接口 Echoservice 来作为服务暴露使用,该接口会响应传入的消息。

1)在 dubbo-demo-api 模块中编写 EchoService.java 代码

public interface EchoService {

    String echo(String msg);
}

2)在 dubbo-demo-provider 模块中编写 EchoServiceImpl.java 代码

public class EchoServiceImpl implements EchoService {

    @Override
    public String echo(final String message) {
        String now = new SimpleDateFormat("HH:mm:ss").format(new Date());
        System.out.println("[" + now + "] Hello "
                + message + ", request from consumer: "
                + RpcContext.getContext().getRemoteAddress());
        return message;
    }
}

3)编写服务提供者的配置文件 echo-provider.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- 1. 服务提供方应用名称 -->
    <dubbo:application name="echo-provider"/>

    <!-- 2. 使用 zookeeper 作为注册中心 -->
    <dubbo:registry address="zookeeper://hadoop102:2181"/>

    <!-- 3. 使用 dubbo 协议并指定监听端口 -->
    <dubbo:protocol name="dubbo" port="20880"/>
    
    <!-- 4. 声明要暴露的服务 -->
    <dubbo:service interface="com.alibaba.dubbo.samples.echo.EchoService" ref="echoService"/>

    <!-- 实例化服务的实现类 -->
    <bean id="echoService" class="com.alibaba.dubbo.samples.echo.provider.EchoServiceImpl"/>
</beans>

4)编写 EchoProvider.java 启动服务

public class EchoProvider {

    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("META-INF/spring/echo-provider.xml");
        // 启动 spring 容器
        context.start();

        // 等待输入,防止程序结束
        System.in.read();
    }
}

5)在开始执行前请确保已经正确启动 ZooKeeper 服务器。Zookeeper 服务的搭建参考 Zookeeper 入门。启动 EchoProvider 服务后在终端中用 Telnet 模拟客户端调用。

# 使用Telnet连接本地20880端口
telnet localhost 20880

在这里插入图片描述
其中,invoke 指令会发起 Dubbo 服务调用,该指令采用[接口 .方法]格式调用,采用 JSON 格式传递参数。

注意:
Dubbo 框架启动时默认会通过 Spring 的容器来启动(本质上也是通过ClassPathXmlApplicationContext来启动服务的。一般生产环境中用 shell 脚本启动 Dubbo。对应的启动类是com.alibaba.dubbo.container.Main,它的原理是利用扩展点加载Spring 容器,然后激活Spring框架加载配置。具体实现参考 Springcontainer,本质上和我们手写启动器是一致的。

2.2.2 编写 Echo 客户端

在客户端只依赖服务暴露的接口的情况下,使用 Dubbo 框架能够让我们把关注点放在编写服务消费逻辑上,而不必去关心网络连接和序列化等底层技术。

1)编写消费者的配置文件 echo-consumer.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       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="echo-consumer"/>

    <!-- 2. 注册中心 -->
    <dubbo:registry address="zookeeper://hadoop102:2181"/>

    <!--3. 执行消费的服务-->
    <dubbo:reference id="echoService" check="false" interface="com.alibaba.dubbo.samples.echo.EchoService"/>
</beans>

2)编写 EchoConsumer.java 消费服务

public class EchoConsumer {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("META-INF/spring/echo-consumer.xml");
        context.start();

        // 获取代理对象
        EchoService echoService = context.getBean(EchoService.class);

        String reult = echoService.echo("hello world");
        System.out.println("echo result: " + reult);
    }
}

3)分别启动 EchoProvier 和 EchoConsumer 查看结果
在这里插入图片描述

2.3 基于注解实现

通过 XML 配置启动 Dubbo 服务是比较常见的方式,但 Dubbo 可以消除 XML 配置,直接使用注解来暴露服务。

2.3.1 编写服务器

1)通过注解暴露服务,只需要在要服务接口上标 @Service 注解即可

import com.alibaba.dubbo.config.annotation.Service;

@Service
public class AnnotationEchoServiceImpl implements EchoService {

    @Override
    public String echo(final String message) {
        String now = new SimpleDateFormat("HH:mm:ss").format(new Date());
        System.out.println("[" + now + "] Hello "
                + message + ", request from consumer: "
                + RpcContext.getContext().getRemoteAddress());
        return message;
    }
}

使用 @Service 注解后,由 Dubbo 服务将这个实现类提升为 Spring 容器的 Bean,并且负责配置初始化和服务暴露。

2)创建 Dubbo 配置

public class AnnotationEchoProvider {

    public static void main(String[] args) throws IOException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
        context.start();
        
        System.in.read();
    }

    /**
     * dubbo 配置类,指定扫描包路径
     */
    @EnableDubbo(scanBasePackages = "com.alibaba.dubbo.samples.echo")
    @Configuration
    static class ProviderConfiguration {

        /**
         * 1. 服务提供者名称
         */
        @Bean
        public ApplicationConfig applicationConfig() {
            ApplicationConfig applicationConfig = new ApplicationConfig();
            applicationConfig.setName("annotation-echo-provider");
            return applicationConfig;
        }

        /**
         * 2. 注册中心
         */
        @Bean
        public RegistryConfig registryConfig() {
            RegistryConfig registryConfig = new RegistryConfig();
            registryConfig.setAddress("zookeeper://hadoop102:2181");
            return registryConfig;
        }

        /**
         * 3. 协议
         */
        @Bean
        public ProtocolConfig protocolConfig() {
            ProtocolConfig protocolConfig = new ProtocolConfig();
            protocolConfig.setName("dubbo");
            protocolConfig.setPort(20880);
            return protocolConfig;
        }
    }
}

2.3.2 编写客户端

通过注解消费服务时,只需要 Reference 注解标注,该注解适用于对象字段和方法中。因此需要做一下特殊的包装。

1)基于注解包装消费

@Component
public class EchoConsumer {

    @Reference
    private EchoService echoService;

    public String echo(String name) {
        return echoService.echo(name);
    }
}

2)基于注解消费服务

public class AnnotationConsumer {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
        context.start();

        // 获取代理对象
        EchoConsumer consumer = context.getBean(EchoConsumer.class);

        String reult = consumer.echo("hello world");
        System.out.println("echo result: " + reult);
    }

    @ComponentScan("com.alibaba.dubbo.samples.echo")
    @EnableDubbo(scanBasePackages = "com.alibaba.dubbo.samples.echo")
    @Configuration
    static class ConsumerConfiguration {

        @Bean
        public ApplicationConfig applicationConfig() {
            ApplicationConfig applicationConfig = new ApplicationConfig();
            applicationConfig.setName("annotation-echo-consumer");
            return applicationConfig;
        }

        @Bean
        public ConsumerConfig consumerConfig() {
            return new ConsumerConfig();
        }

        @Bean
        public RegistryConfig registryConfig() {
            RegistryConfig registryConfig = new RegistryConfig();
            registryConfig.setAddress("zookeeper://hadoop102:2181");
            return registryConfig;
        }
    }
}

2.4 基于 API 实现

Dubbo 框架大部分场景都会在 Spring 中使用,但是不局限于这种场景。除了基于 XML 和注解的方式,Dubbo 框架还支持 API 的方式。

2.4.1 编写服务器代码

public class ApiEchoProvider {

    public static void main(String[] args) throws IOException {
        ServiceConfig<EchoService> serviceConfig = new ServiceConfig<EchoService>();
        serviceConfig.setApplication(new ApplicationConfig("api-echo-provider"));
        serviceConfig.setRegistry(new RegistryConfig("zookeeper://hadoop102:2181"));
        // 指定暴露的接口
        serviceConfig.setInterface(EchoService.class);
        // 指定接口的实现类
        serviceConfig.setRef(new EchoServiceImpl());
        // 暴露服务
        serviceConfig.export();

        System.in.read();
    }
}

2.4.2 编写客户端代码

public class ApiEchoConsumer {

    public static void main(String[] args) {
        ReferenceConfig<EchoService> referenceConfig = new ReferenceConfig<EchoService>();
        referenceConfig.setApplication(new ApplicationConfig("api-echo-consumer"));
        referenceConfig.setRegistry(new RegistryConfig("zookeeper://hadoop102:2181"));
        // 指定要消费的服务接口
        referenceConfig.setInterface(EchoService.class);

        // 创建远程连接并做动态代理转换
        EchoService service = referenceConfig.get();
        String result = service.echo("hello world");
        System.out.println("echo result: " + result);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值