关于后端:浅析Dubbo核心设计

59次阅读

共计 4974 个字符,预计需要花费 13 分钟才能阅读完成。

大家好,我是易安!

当今互联网时代,随着企业业务的一直扩大和用户量的减少,分布式系统已成为大型企业必不可少的组成部分。而 Dubbo 框架作为阿里巴巴开源的高性能 Java RPC 框架,始终以来都备受关注和应用。其外围设计思维和架构特点,对于分布式系统的开发和运维具备重要的参考价值。明天我将深入探讨 Dubbo 框架的外围设计,来帮忙你更好地了解分布式系统架构和设计思维。本文次要分为服务注册与动静发现、服务调用、网络通信模型、高度灵便的扩大机制和泛化调用五个局部。

服务注册与动静发现

咱们首先来看一下 Dubbo 的服务注册与动静发现机制。

Dubbo 的服务注册与发现机制如 下图 所示:

Dubbo 中次要包含四类角色,它们别离是注册核心(Registry)、服务调用者 & 生产端(Consumer)、服务提供者(Provider)和监控核心(Monitor)。

在实现服务注册与发现时,有三个要点。

  1. 服务提供者 (Provider) 在启动的时候在注册核心 (Register) 注册服务,注册核心 (Registry) 会存储服务提供者的相干信息。
  2. 服务调用者 (Consumer) 在启动的时候向注册核心订阅指定服务,注册核心将以某种机制(推或拉)告知生产端服务提供者列表。
  3. 当服务提供者数量变动(服务提供者扩容、缩容、宕机等因素)时,注册核心须要以某种形式 (推或拉) 告知生产端,以便生产端进行失常的负载平衡。

Dubbo 官网提供了多种注册核心,咱们抉择应用最为广泛的 ZooKeeper 进一步了解注册核心的原理。

咱们先来看一下 Zookeeper 注册核心中的数据存储目录构造。

能够看到,它的目录组织构造为 /dubbo/{ServiceName},其中,ServiceName 示意一个具体的服务,通常用包名 + 类名示意,在每一个服务名下又会创立四个目录,它们别离是:

  • providers,服务提供者列表;
  • consumers,消费者列表;
  • routers,路由规定列表(一个服务能够设置多个路由规定);
  • configurators,动静配置条目。

在 Dubbo 中,咱们能够在不重启消费者、服务提供者的前提下动静批改服务提供者、服务消费者的配置,配置信息发生变化后会存储在 configurators 子节点中。此时,服务提供者、消费者会动静监听配置信息的变动,变动一旦产生就应用最新的配置重构服务提供者和服务消费者。

基于 Zookeeper 注册核心的服务注册与发现有上面三个实现细节。

  1. 服务提供者启动时会向注册核心进行注册,具体是在对应服务的 providers 目录下减少一条记录(长期节点),记录服务提供者的 IP、端口等信息。同时服务提供者会监听 configurators 节点的变动。
  2. 服务消费者在启动时会向注册核心订阅服务,具体是在对应服务的 consumers 目录下减少一条记录(长期节点),记录消费者的 IP、端口等信息,同时监听 configurators、routers 目录的变动,所谓的监听就是利用 ZooKeeper 提供的 watch 机制。
  3. 当有新的服务提供者上线后,providers 目录会减少一条记录,注册核心会将最新的服务提供者列表推送给服务调用方(生产端),这样消费者能够立即收到告诉,晓得服务提供者的列表产生了变动。如果一个服务提供者宕机,因为它是长期节点,所以 ZooKeeper 会把这个节点移除,同样会触发事件,生产端一样能得悉最新的服务提供者列表,从而实现路由的动静注册与发现。

服务调用

接下来咱们再来看看服务调用。Dubbo 的服务调用设计非常优雅,其实现原理图如下:

服务调用重点论述的是客户端发动一个 RPC 服务调用时的所有实现细节,它包含服务发现、故障转移、路由转发、负载平衡等方面,是 Dubbo 实现灰度公布、多环境隔离的理论指导。

方才,咱们曾经就服务发现做了具体介绍,接下来咱们重点关注负载平衡、路由、故障转移这几个方面。

客户端通过服务发现机制,能动静发现以后存活的服务提供者列表,接下来要思考的就是如何从服务提供者列表中抉择一个服务提供者发动调用,这就是所谓的 负载平衡(LoadBalance)

Dubbo 默认提供了随机、加权随机、起码沉闷连贯、一致性 Hash 等负载平衡算法。

值得注意的是,Dubbo 不仅提供了负载平衡机制,还提供了智能路由机制,这是实现 Dubbo 灰度公布的重要实践根底。

所谓路由机制,是指设置肯定的规定对服务提供者列表进行过滤。负载平衡时,只在通过了路由机制的服务提供者列表中进行抉择。为了更好地了解路由机制的工作原理,你能够看看上面这张示意图:

咱们为查找用户信息服务设置了一条路由规定,即“查问机构 ID 为 102 的查问用户申请信息将被发送到新版本(192.168.3.102)上。具体的做法是,在进行负载平衡之前先执行路由抉择,依照路由规定对原始的服务提供者列表进行过滤,从中挑选出符合要求的提供者列表,而后再进行负载平衡。

接下来,客户端就要向服务提供者发动 RPC 申请调用了。近程服务调用通常波及到网络等因素,因而并不能保障 100% 胜利,当调用失败时应该采纳什么策略呢?

Dubbo 提供了上面五种策略:

  • failover,失败后抉择另外一台服务提供者进行重试,重试次数可配置,通常适宜实现幂等服务的场景;
  • failfast,疾速失败,失败后立刻返回谬误;
  • failsafe,调用失败后打印谬误日志,返回胜利,通常用于记录审计日志等场景;
  • failback,调用失败后,返回胜利,但会在后盾定时有限次重试,重启后不再重试;
  • forking,并发调用,收到第一个响应后果后返回给客户端。通常适宜实时性要求比拟高的场景。但这一策略节约服务器资源,通常能够通过 forks 参数设置并发调用度。

如果将服务调用落到底层,就不得不说说网络通信模型了,这部分蕴含了很多 性能调优伎俩

网络通信模型

来看看 Dubbo 的网络通信模型,如下图所示:

Dubbo 的网络通信模型次要包含 网络通信协定和线程派发机制(Dispatcher) 两局部。

网络传输通常须要自定义通信协议,咱们罕用的协定设计形式是 Header + Body, 其中 Header 长度固定,蕴含一个长度字段,用于记录整个协定包的大小。

同时,为了进步传输效率,咱们个别会对传输数据也就是 Body 的内容进行序列化与压缩解决。

Dubbo 反对目前反对 java、compactedjava、nativejava、fastjson、fst、hessian2、kryo 等序列化协定,生产环境默认为 hessian2。

网络通信模型的另一部分是线程派发机制。Dubbo 中会默认创立 200 个线程解决业务,这时候就须要线程派发机制来领导 IO 线程与业务线程如何分工。

Dubbo 提供了上面几种线程派发机制:

  • all,所有的申请转发到业务线程池中执行(IO 读写、心跳包除外,因为在 Dubbo 中这两种申请都必须在 IO 线程中执行,不能通过配置批改);
  • message,只有申请事件在线程池中执行,其余申请在 IO 线程上执行;
  • connection,求事件在线程池中执行,连贯和断开连接的事件排队执行(含一个线程的线程池);
  • direct,所有申请间接在 IO 线程中执行。

为什么线程派发机制有这么多种策略呢?其实这次要是思考到线程切换带来的开销问题。也就是说,咱们心愿通过多种策略让线程切换带来的开销小于多线程解决带来的晋升。

我举个例子,Dubbo 中的心跳包都必须在 IO 线程中执行。在解决心跳包时,咱们只需间接返回 PONG 包(OK)就能够了,逻辑非常简单,处理速度也很快。如果将心跳包转换到业务线程池,性能不升反降,因为切换线程会带来额定的性能损耗,得失相当。

网络编程中须要遵循一条最佳实际:IO 线程中不能有阻塞操作,通常将阻塞操作转发到业务线程池异步执行

与网络通信协定相干的参数定义在 dubbo:protocol,要害的设置属性如下。

  • threads,业务线程池线程个数,默认为 200。
  • queues,业务线程池队列长度,默认为 0,示意不反对排队,如果线程池满,则间接回绝。该参数与 threads 配合应用,次要是对服务端进行限流,一旦超过其解决能力,就拒绝请求,疾速失败,疏导客户端重试。
  • iothreads:默认为 CPU 核数再加一,用于解决网络读写。在生产实践中,通常的瓶颈在于业务线程池,如果业务线程无显著瓶颈(jstack 日志查问到业务线程根本没怎么干活),但吞吐量曾经无奈持续晋升了,能够思考调整 iothreads,减少 IO 线程数量,进步 IO 读写并发度。该值倡议放弃在“2*CPU 核数”以下。
  • serialization:序列化协定,新版本反对 protobuf 等高性能序列化机制。
  • dispatcher:线程派发机制,默认为 all。

高度灵便的扩大机制

Dubbo 呈现之后迅速成为微服务畛域最受欢迎的框架,除操作简略这个起因外,还有扩大机制的功绩。Dubbo 高度灵便的扩大机制堪称“王者级别的设计”。

Dubbo 的扩大设计次要是基于 SPI 设计理念,咱们来看下具体的实现计划。

Dubbo 所有的底层能力都通过接口来定义。用户在扩大时只须要实现对应的接口,定义一个对立的扩大目录(META-INF.dubbo.internal)寄存所有的扩大定义即可。要留神的是,目录下的文件名是须要扩大的接口的全名,像下图这样:

在首次应用对应接口实例时,能够扫描扩大目录中的文件,并依据文件中存储的 key-value 初始化具体的实例。

咱们以 RPC 模块为例看一下 Dubbo 强悍的扩大能力。家喻户晓,目前 gRPC 协定以优异的性能体现正在逐渐成为 RPC 畛域的王者,很多人误以为 gRPC 是来革 Dubbo 的“命”的。其实不然,咱们能够认为 Dubbo 是微服务体系的残缺解决方案,而 RPC 只是微服务体系中的重要一环,Dubbo 齐全能够排汇 gRPC,让 gRPC 成为 Dubbo 的近程调用形式。

具体的做法只须要在 dubbo-rpc 模块中增加一个 dubbo-rpc-grpc 模块,而后应用 gRPC 实现 org.apache.dubbo.rpc.protocol 接口,并将其配置在扩大目录中:

面对 gRPC 这么弱小的性能扩大机制,绝大部分人应该和我一样,都是作为中间件的利用人员,不须要应用模块级别的扩大机制。咱们通常只是联合利用场景来进行性能扩大。

Dubbo 在业务性能级别的扩大能够通过 Filter 机制来实现。Filter 的工作机制如下:

这里,过滤器链的执行机会是在服务消费者发动近程 RPC 申请之前。 最先执行的是生产端的过滤器链,每一个过滤器能够设置执行程序。服务端在解码之后、执行业务逻辑之前,也会首先调用过滤器链。

泛化调用

上面咱们介绍一下 Dubbo 的泛化调用机制,它也是实现 Dubbo 网关的实践根底。

咱们在开发 Dubbo 利用时通常会蕴含 API、Consumer、Provider 三个子模块。

其中 API 模块通常定义对立的服务接口,而 Consumer、Provider 模块都须要显示依赖 API 模块。这种设计理念尽管将 Provider 与 Consumer 进行理解耦合,但对 API 模块造成了强依赖,如果 API 模块产生扭转,Provider 和 Consumer 必须同时扭转。也就是说,一旦 API 模块发生变化,服务调用方、服务生产方都须要重新部署,这对利用公布来说十分不敌对。特地是在网关畛域,简直是不可承受的,如下图所示:

公司的微服务在不停地演进,如果网关须要跟着 API 模块不停地公布新版本,网关的可用性和稳定性都将受到极大挑战。怎么解决这个问题呢?

这就要说到 Dubbo 的机制了。泛化调用具体实现原理如下:

当服务生产端产生调用时,咱们应用 Map 来存储一个具体的申请参数对象,而后传输到服务提供方。因为服务提供方引入了模型相干的 Jar,服务提供方在执行业务办法之前,须要将 Map 转化成具体的模型对象,而后再执行业务逻辑。

Dubbo 的泛化调用在服务提供方的转化是通过 Filter 机制对立解决的,服务端并不需要关注生产方采取何种形式进行调用。

通过泛化调用机制,客户端不再须要依赖服务端的 Jar 包,服务端能够一直地演变,而不会影响客户端已有服务的运行。

小结

本文次要介绍了 Dubbo 的服务注册与发现、服务调用、网络通信模型、扩大机制还有泛化调用等外围工作机制

本文由 mdnice 多平台公布

正文完
 0