Skip to main content

Dubbo

服务发现与注册​

  • 服务提供者 Provider 在启动时,向注册中心注册自己提供的服务
  • 服务消费者 Consumer 在启动时,向注册中心订阅自己所需的服务
  • 注册中心 Registry 返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
  • 服务消费者 Consumer 从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用

注册中心​

  • Zookeeper(推荐)
    • 分布式协调服务,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,可用于生产环境,并推荐使用
  • Nacos
    • 阿里推出的动态服务发现、服务配置、服务元数据及流量管理、多租户的开源项目,同时支持注册中心、配置中心分离和合并部署
  • Multicast
    • 不需要启动任何中心节点,只要广播地址就可以互相发现,组播受网络结构限制,只适合小规模应用或开发阶段使用
  • Redis
    • 使用 Redis 的 Key/Map 结构存储数据结构,使用 Redis 的 Publish/Subscribe 事件通知数据变更
  • Simple
    • 本身就是一个普通的 Dubbo 服务,可以减少第三方依赖,使整体通讯方式一致

负载均衡策略​

  • RandomLoadBalance - 加权随机,默认算法,默认权重相同
    • 按权重设置随机概率
    • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重
    • 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上
  • RoundRobinLoadBalance - 加权轮询,借鉴于 Nginx 的平滑加权轮询算法,默认权重相同
    • 按公约后的权重设置轮询比率,循环调用节点
    • 同样存在慢的提供者累积请求的问题
  • LeastActiveLoadBalance - 最少活跃优先 + 加权随机,背后是能者多劳的思想
    • 活跃数越低,越优先调用,相同活跃数的进行加权随机(活跃数指调用前后计数差(针对特定提供者:请求发送数 - 响应返回数),表示特定提供者的任务堆积量,活跃数越低,代表该提供者处理能力越强)
    • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大,相对的,处理能力越强的节点,处理更多的请求。
  • ShortestResponseLoadBalance - 最短响应优先 + 加权随机,关注响应速度
    • 在最近一个滑动窗口中,响应时间越短,越优先调用,相同响应时间的进行加权随机
    • 使得响应时间越快的提供者,处理更多的请求
    • 可能会造成流量过于集中于高性能节点的问题
  • ConsistentHashLoadBalance - 一致性 Hash,确定的入参,确定的提供者,适用于有状态请求
    • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动
    • 缺省只对第一个参数 Hash
    • 缺省用 160 份虚拟节点

集群容错方案​

  • Failover Cluster - 失败自动切换,当出现失败,重试其它服务器,通常用于读操作,但重试会带来更长延迟(缺省)
  • Failfast Cluster - 快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作,比如新增记录
  • Failsafe Cluster - 失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作
  • Failback Cluster - 失败自动恢复,后台记录失败请求,定时重发,通常用于消息通知操作
  • Forking Cluster - 并行调用多个服务器,只要一个成功即返回,通常用于实时性要求较高的读操作,但需要浪费更多服务资源
  • Broadcast Cluster - 广播调用所有提供者,逐个调用,任意一台报错则报错,通常用于通知所有提供者更新缓存或日志等本地资源信息
  • Available Cluster - 调用目前可用的实例(只调用一个),如果当前没有可用的实例,则抛出异常,通常用于不需要负载均衡的场景
  • Mergeable Cluster - 将集群中的调用结果聚合起来返回结果,通常和 group 一起配合使用
  • ZoneAware Cluster - 多注册中心订阅的场景,注册中心集群间的负载均衡
    • 指定优先级
    • 同中心优先
    • 权重轮询
    • 缺省值:选择一个可用的注册中心

超时设置​

  • 服务提供者端设置超时时间
  • 服务消费者端设置超时时间
  • 消费者端设置了超时时间,以消费者端为主
  • 调用服务不成功时,默认是会重试两次

协议​

  • dubbo
    • 单一长连接,适合大并发小数据量的服务调用,以及消费者远大于提供者。TCP 协议,异步, Hessian 序列化。
  • rest
    • 基于标准的 Java REST API - JAX-RS 2.0(Java API for RESTful Web Services 的简写)实现的 REST 调用支持。多个短连接,HTTP 协议,同步,json/xml 序列化。
  • HTTP
    • 基于 HTTP 表单的远程调用协议,采用 Spring HttpInvoker 实现。多个短连接,HTTP 协议,同步,JSON 序列化,传入参数大小混合,提供者个数多于消费者,可给应用程序和浏览器 JS 调用。
  • Hessian
    • 集成 Hessian 服务,采用 Servlet 暴露服务,内嵌 Jetty 作为服务器默认实现,提供与 Hession 服务互操作。多个短连接,HTTP 协议,同步,Hessian 序列化,传入参数较大,提供者大于消费者,提供者压力较大,可传文件。
  • gRPC
    • 使用 HTTP/2 通信,带来 Stream、反压、Reactive 编程等能力。
  • RMI
    • 采用 JDK 标准的 RMI 协议实现,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多。多个短连接,TCP 协议,同步,JDK 标准序列化。
  • WebService
    • 基于 WebService 的远程调用协议,集成 CXF 实现,提供和原生 WebService 的互操作。多个短连接,HTTP 协议,同步,SOAP 文本序列化,适用系统集成和跨语言调用。
  • Thrift
    • 原生协议的基础上添加了一些额外的头信息,比如 service name,magic number等。使用 Thrift 协议同样需要使用 Thrift 的 IDL 编译生成相应的 Java 代码。
  • Memcache
    • 基于 Memcache 实现的 RPC 协议。
  • Redis
    • 基于 Redis 实现的 RPC 协议。

序列化​

  • avro
  • fastjson
  • FST (推荐)
  • gson
  • hessian2 (默认)
  • jdk
  • kryo (推荐)
  • protobuf
  • protostuff

服务容器​

  • Spring Container
  • Jetty Container
  • Log4j Container

线程模型​

  • 如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程上处理更快,因为减少了线程池调度。
  • 但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则 IO 线程阻塞,将导致不能接收其它请求。
  • 如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报可能引发死锁异常,但不会真死锁。
<dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" />

Dispatcher​

  • all - 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等
  • direct - 所有消息都不派发到线程池,全部在 IO 线程上直接执行
  • message - 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行
  • execution - 只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行
  • connection - 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池

ThreadPool​

  • fixed - 固定大小线程池,启动时建立线程,不关闭,一直持有(缺省)
  • cached - 缓存线程池,空闲一分钟自动删除,需要时重建
  • limited - 可伸缩线程池,但池中的线程数只会增长不会收缩,只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题
  • eager - 优先创建Worker线程池,在任务数量大于 corePoolSize 但是小于 maximumPoolSize 时,优先创建 Worker 来处理任务;当任务数量大于 maximumPoolSize 时,将任务放入阻塞队列中,阻塞队列充满时抛出RejectedExecutionException(相比于 cached:cached 在任务数量超过 maximumPoolSize 时直接抛出异常而不是将任务放入阻塞队列)

SPI​

  • dubbo spi 实现原理和 java spi 相似,只不过增强了一些功能和优化。java spi 的是把所有的 spi 都加载到内存,但对于 dubbo 来说可能只需要加载用户指定的实现方式,而不需要全部加载进来,全部加载也会有性能问题,所以 dubbo 实现的是在有用到的时候去加载这些扩展组件

重要的注解​

  • @SPI
    • 被此注解标记的接口,就表示是一个可扩展的接口,并标注默认值
  • @Adaptive
    • 有两种使用方式,一种是注解在类上,一种是注解在方法上
  • @Activate
    • 需要标注在类上或者方法上,并注明被激活的条件,以及所有的被激活实现类中的排序信息

SPI 声明加载目录​

  • META-INF/services/ - 用来兼容 Java SPI
  • META-INF/dubbo/ - 存放用户自定义的 SPI 配置
  • META-INF/dubbo/internal/ - 存放 Dubbo 内部使用的 SPI 配置

加载流程​

  1. 通过 SPI 接口类型查找/创建 ExtensionLoader (从 extension loader cache 中找,没有则创建)
  2. 通过 SPI 名称用 ExtensionLoader 查找/创建 SPI 实现实例(从 instance cache 中找,没有则创建)

实现实例创建流程​

  1. 通过 SPI 名称在 class cache 查找 SPI 实现类名,没有则从 SPI 的 3 个配置目录读取出声明的 SPI 实现类名放入 class cache
  2. 从 class cache 查找实现类,创建 SPI 实现实例,放入 instance cache
  3. 实例注入与包装

调用流程​

  • 消费者通过代理对象 Proxy 发起远程调用,接着通过网络客户端 Client 将编码后的请求发送给服务提供方的网络层上,也就是 Server。Server 在收到请求后,首先要做的事情是对数据包进行解码,然后将解码后的请求发送至分发器 Dispatcher,再由分发器将请求派发到指定的线程池上,最后由线程池调用具体的服务。这就是一个远程调用请求的发送与接收过程。
  1. 客户端(Client)以本地调用的方式调用远程服务。
  2. 客户端代理对象(Client Stub)将本次请求的相关信息(要调用的类名、方法名、方法参数等)封装成 Request,并且对其序列化,为网络通信做准备。
  3. 客户端代理对象(Client Stub)找到服务端(Server)的地址,通过网络(Socket 通信)将 Request 发送到服务端。
  4. 服务端代理对象(Server Stub)接收到客户端(Client)的请求后,将二进制数据反序列化为 Request。
  5. 服务端代理对象(Server Stub)根据调用信息向本地的方法发起调用。
  6. 服务端代理对象(Server Stub)将调用后的结果封装到 Response 中,并且对其序列化,通过网络发送给客户端。
  7. 客户端代理对象(Client Stub)收到响应后,将其反序列化为 Response,远程调用结束。

服务治理​

  • 服务治理主要作用是改变运行时服务的行为和选址逻辑,达到限流,权重配置等目的

标签路由​

  • 以应用作为维度,给不同的服务器打上不同名字的标签,调用的时候,客户端可以通过 setAttachment 的方式,来设置不同的标签名称,可以通过这种方式来实现流量隔离,灰度发布等功能

条件路由​

  • 可随时在服务治理控制台写入路由规则,在发起一次RPC调用前起到过滤目标服务器地址的作用,过滤后的地址列表,将作为消费端最终发起RPC调用的备选地址

黑白名单​

  • 条件路由的一部分,规则存储和条件路由放在一起,为了方便配置所以单独拿出来,同样可以通过服务和应用两个维度,指定黑名单和白名单

动态配置​

  • 和路由规则平行的另一类服务治理治理功能,主要作用是在不重启服务的情况下,动态改变调用行为,支持服务和应用两个维度的配置

权重调节​

  • 是动态配置的子功能,主要作用是改变服务端的权重,更大的权重会有更大的几率被客户端选中作为服务提供者,从而达到流量分配的目的

负载均衡​

  • 动态配置的子功能,主要作用是调整客户端的选址逻辑,目前可选的负载均衡策略有随机,轮询和最小活跃。