作者:幻好

在分布式服务架构的背景下,为了服务间高效的通信,常常使用 RPC 进行解决,提到 RPC 就不得不学习 Dubbo

设计背景

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

dubbo-architecture-roadmap.jpg

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

dubbo-home.png

RPC

在介绍 Dubbo 之前,我们得先了解什么是 RPC?

RPC,Remote Procedure Call 即远程过程调用,远程过程调用其实对标的是本地过程调用。当项目中服务开始增加时,实现服务之间的相互通信。

861f9486338357891cb26e78ca964ad0.jpg

而 Dubbo 的设计就是为了实现面向接口代理的高性能RPC调用,提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节。

初识 Dubbo

首先我们先来了解下 Dubbo 的架构图:

dubbo-architecture.jpg

节点 角色说明
Provider 服务提供方
Consumer 需要调用远程服务的服务消费方
Registry 注册中心
Container 服务运行的容器
Monitor 监控中心

Dubbo 整体的运行流程:

  1. 首先服务提供者Provider 启动,然后向注册中心注册所能提供的服务。
  2. 服务消费者Consumer 启动,向注册中心订阅自己所需的服务,并获取服务提供者的地址。
  3. 注册中心将提供者元数据通知给 Consumer,通过负载均衡选择一个 Provider 直接调用
  4. 服务提供方元数据发生变更时,注册中心将基于长连接推送变更数据给服务消费者
  5. 服务提供者和消费者都会在内存中记录着调用的次数和时间,定时的发送统计数据到监控中心

Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。

连通性

  • 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
  • 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
  • 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
  • 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
  • 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
  • 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
  • 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
  • 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者

健壮性

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

伸缩性

  • 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
  • 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者

升级性

当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。

实践

使用 Maven 引入 Dubbo

首先搭建SpringBoot + Zookeeper + Dubbo 项目,引入相关依赖。

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>dubbo-dependencies-bom</artifactId>
        <version>${dubbo.version}</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
</dependencies>

定义服务接口

定义一个服务接口 ProvideService,该接口可以处理我们相关业务。

@Service
public class ProvideServiceImpl implements ProvideService {
    @Override
    public String provideHello(String say) {
        System.out.println("Provider say: " + say);
        return say;
    }
}

配置服务

通过传统的 Spring XML 的方式来装配并暴露 Dubbo 服务,方便项目的配置管理,也可使用注解的方式进行配置。

<?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="provide"/>

    <!-- 监控中心的配置 -->
    <dubbo:monitor protocol="registry"/>

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

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

    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.demodubbo.provider.service.ProvideService"
                   ref="provideService"/>

    <!-- 本地bean实现服务 -->
    <bean id="provideService" class="com.demodubbo.provider.service.impl.ProvideServiceImpl"/>
</beans>

服务提供方的启动类

通过注解的方式,加载服务的配置文件。

@ComponentScan(value = {"com.demodubbo.provider"})
@ImportResource(value = "provide.xml")
@SpringBootApplication
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

启动服务提供方

启动项目后,可以在控制台查看暴露的服务信息。

[DUBBO] The service ready on spring started. service: com.demodubbo.provider.service.ProvideService, dubbo version: 2.6.6, current host: 192.168.199.178

[DUBBO] Export dubbo service com.demodubbo.provider.service.ProvideService to local registry, dubbo version: 2.6.6, current host: 192.168.199.178

[DUBBO] Export dubbo service com.demodubbo.provider.service.ProvideService to url dubbo://192.168.199.178:20881/com.demodubbo.provider.service.ProvideService?anyhost=true&application=provide&bean.name=com.demodubbo.provider.service.ProvideService&bind.ip=192.168.199.178&bind.port=20881&dubbo=2.0.2&generic=false&interface=com.demodubbo.provider.service.ProvideService&methods=provideHello&pid=1784&side=provider&timestamp=1626011040996, dubbo version: 2.6.6, current host: 192.168.199.178

[DUBBO] Start NettyServer bind /0.0.0.0:20881, export /192.168.199.178:20881, dubbo version: 2.6.6, current host: 192.168.199.178

发起调用

@RestController
public class ConsumerController {

    @Autowired
    private ProvideService providerService;

    @GetMapping("/hello")
    public void sayHello(){
        String str = providerService.provideHello("Not hello, just hi. ");
        System.out.println("consumer get str = " + str + " from provide");
    }
}