《Dubbo》接入与调用流程

前言

在学习Dubbo的时候可以发现,Dubbo的实现原理很多,但是接入的方式简单且多样。就像一个接口的服务暴露,我们并不需要去关心如何实现暴露,因为Dubbo已经帮我们实现了并且隐藏。用户只需要关注以下的内容就行:

  • 关注业务场景,编写面向接口的业务代码
  • 少量的启动配置,比如配置中心和暴露的协议等

虽然Dubbo帮我们实现封装了那么多,让用户无须去关心实现底层。但是如果是为了学习,还是要自己深入去了解的。关于Dubbo的接入方式主要有基于XML的配置实现,基于注解的实现,基于API的实现。但是在演示代码之前也是要先了解Dubbo的一些基础知识。

Dubbo功能与角色简介

主要功能

  1. Remoting:远程通讯,提供对多种 NIO 框架抽象封装,包括“同步转异步”和“请求-响应”模式的信息交换方式。
  2. Cluster:服务框架,提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
  3. Registry:服务注册中心,服务自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

组件角色

关于Dubbo的组件主要角色,官网给的图片最能体现角色的职责与位置。

组件角色 说明
Provider 暴露服务的服务提供方
Consumer 调用远程服务的服务消费方
Registry 服务注册与发现的注册中心
Monitor 统计服务的调用次调和调用时间的监控中心
Container 服务运行容器

这里通过一个比较详细的流程图再次了解,执行顺序依然参考上方官方图。

基于XML配置实现

Dubbo的接入可以通过XML配置进行实现,这也是dubbo用的比较多的一种方式。通过XML配置进行对外接口的暴露,可以让我们将dubbo与业务代码尽可能少的耦合在一起,并且修改底层代码时候,并不需要改变和编译代码。

暴露的服务接口

我们把api抽取模块,这里演示只需要一个提供接口。

package com.lin.dubbo.samples.echo.api;

public interface EchoService {
    //打算暴露的接口
    String echo(String message);
}

我们需要有两个模块,一个provider模块,用来提供服务的实现和暴露。一个consumer模块,用来远程调用服务。需要注意的是,我们在两个模块里面是需要api模块依赖的。

provider模块

我们在生产者模块这里,主要需要做的是有实现api接口,通过xml配置文件配置dubbo和服务接口相关信息,需要一个main方法来加载容器和配置文件。

(1)实现api接口

(2)XML配置文件

(3)生产者服务提供

consumser模块

(1)XML配置文件指定服务消费和注册服务中心

(2)服务消费者消费

输出

我们需要先启动服务生产者之后才能再启动服务消费者,在消费者启动之后就可以看到两边的输出结果。

# 生产者输出结果
[15:53:21] Hello Hello World and CryFace !, request from consumer: /192.168.87.1:51797

# 消费者输出结果
echo result: Hello World and CryFace !

基于注解实现

基于注解接入dubbo我们就可以消除XML配置了,这种方式比较友好,但是与此同时也带来了业务代码耦合了一些Dubbo框架注解。

依然是我们的两个模块都需要引入api模块依赖。

provider模块

(1)服务接口实现

在我们provider里面,依旧要实现接口类。代码和上面实现一样,不过不同的是,我们需要在类上面加一个@Service注解,用来暴露服务接口。要注意的是这个@Service注解引入的是dubbo的。

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

@Service
public class EchoServiceImpl implements EchoService {...实现同上...}

(2)服务生产者提供

consumer模块

(1)消费包装

消费者这边我们主要通过@Reference注解来实现远程调用,但是需要注意的是该字段适用于对象字段和方法中,所以通过一个消费包装类来包装消费。

(2)服务消费者调用

输出

# provider
[17:07:20] Hello Hello world!, request from consumer: /192.168.87.1:51526
# consumer
result: Hello world!

基于API实现

虽然说Dubbo大部分应用场景都是与Spring中使用,使用的比较多也是XML和注解方式。但是还是需要提一下的是,Dubbo还支持API的方式。API的方式使用场景很少,不过某些特殊情况下还是很有用的。比如进行网关类的应用,需要动态消费不同版本的服务,通过API,可以根据前端请求参数动态构造不同版本的服务实例。

provider

(1)服务实现类

基于注解的实现去掉@Service。

(2)服务生产提供

consumer

输出

# 生产者
java-echo-provider is running
[17:27:59] Hello Hello world!, request from consumer: /192.168.87.1:55621
# 消费者
Hello world!

Dubbo服务调用流程

在Dubbo的代码中,我们可以看到一个完整的服务暴露,服务消费的流程。但是内部的流程是怎么样的呢,我们能知道吗?Dubbo的各个组件在什么地方又扮演了什么角色?

Dubbo基本分层

在Dubbo的架构里面总体分为了业务层(Biz)、RPC层、Remote三层。继续细分的话可以分为十层,每一层又有自己重要的实现接口如右。

原书图片

关于每一层的介绍,官网和更多详细书籍都有介绍,这里我用自己的话简述一下。

服务调用流程

在了解上面那些组件之后,我们再来看服务调用过程,就能比较清楚的认识到这些组件在整个服务调用流程中具体扮演了什么角色。

服务暴露

我们的服务暴露是在服务提供者中进行的,在框架启动的时候,开始进行整个流程。

  1. 首先会初始化我们的API实现接口的实例。

  2. 然后通过我们的Proxy组件去调用具体协议(Protocol),也就是加载我们的配置然后进行封装。

  3. 将服务端要暴露的接口封装成Invoker(可执行体),然后再封装成Exporter(再次封装是为了接口增强,主要是打开创建一个Netty Server 侦听服务,并接收客户端发来的各种请求)。这两个接口在上面图中也可以知道,其实都是Protocol下的接口。

  4. 然后通过Registry注册到注册中心,这便是一个整体的服务暴露过程。

服务调用总流程

(1)在Provider的服务暴露之后,我们的Consumer的调用流程也是从一个Proxy开始,Proxy持有Invoker对象,然后触发invoke调用。

(2)然后获取节点需要通过我们的Cluster,上面组件作用也说过了通过它的作用,主要是负责一些容错。在调用之前会先通过Directory获取所有可以调用的远程服务Invoker列表。如果配置了某些路由规则,比如某个接口只能调用某个节点的那就再过滤一遍 Invoker 列表。

(3)剩下的 Invoker 再通过 LoadBalance 做负载均衡选取一个,然后再通过Filter进行一些计数,限流,处理上下文之类的操作等。

(4)这个时候我们已经拿到了一个实际可用的Invoker,然后会使用Client做数据传输,比如常见的Netty Client。

(5)Codec接口主要是用来处理一些私有协议的构造。构造完成后,进行序列化,然后传输到服务提供者端,服务提供者收到数据包之后,处理协议,反序列化等等操作。

(6)序列化操作完之后我们的请求就被打到线程池去处理,然后查找对应的Exporter(内部持有Invoker),然后将装饰器模式嵌套了一层又一层的Filter剥掉,还原到一个原始的Invoker再到一个真实的服务接口实现类。最后将这个实现类真实调用并原路返回就行。

服务调用流程总结

关于服务调用的大概流程基本如上,但是也仅限认识了大概流程。具体每个组件的实现细节,哪些巧妙的地方还是无从而知,所以这些仍是需要我们去学习的地方的。

参考资料

[1] 《深入理解Apache Dubbo与实战》

[2] https://dubbo.apache.org/zh/docs/v2.7/user/quick-start/

posted @ 2021-05-02 01:18  CryFace  阅读(633)  评论(0编辑  收藏  举报