什么是RPC?

RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。简言之,RPC使得程序能够像访问本地系统资源一样,去访问远端系统资源

RPC其实是很宽泛的概念,Http协议其实就是RPC的一种实现方式。当然,也可以狭义地理解RPC,简单封装http协议的那些就不属于RPC了。

为什么要有RPC

  • http协议是在接口不多、系统与系统交互较少的情况下,解决信息孤岛初期常使用的一种通信手段。
    • 优点:简单、直接、开发方便,直接用现成的http协议进行传输。
    • 缺点:如果是一个大型的网站,内部子系统较多、接口非常多的情况下,RPC框架的好处就显示出来了,首先就是长连接,不必每次通信都要像http一样去3次握手什么的,减少了网络开销。
  • RPC框架一般都有注册中心,有丰富的监控管理
  • 服务的发布、下线、动态扩展等,对调用方来说是无感知、统一化的操作。
  • 最近流行的服务化架构、服务化治理、云原生,RPC框架是一个强力的支撑。
  • socket只是一个简单的网络通信方式,只是创建通信双方的通信通道,而要实现rpc的功能,还需要对其进行封装,以实现更多的功能。
  • RPC一般配合netty框架、spring自定义注解来编写轻量级框架,其实netty内部是封装了socket的,较新的jdk的IO一般是NIO,即非阻塞IO,在高并发网站中,RPC的优势会很明显

PRC架构组件及原理

一个基本的RPC架构里面应该至少包含以下4个组件:

  • Client:服务调用方(consumer)

  • Client Stub 客户端存根:存放服务端地址信息,将客户端的请求参数和数据信息进行打包,再通过网络传输发送给服务端

  • Server Stub 服务端存根:接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理

  • Serve:服务的真正提供者

具体调用过程:

  • client 通过调用本地服务的方式调用需要消费的服务;

  • client stub 接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体;

  • client stub 找到远程的服务地址,并且发送消息给服务端

  • server stub 收到消息后进行反序列化

  • server stub 根据解码结果调用本地的server进行相关处理;

  • server 执行具体业务逻辑并返回处理结果给 server stub

  • server stub 将处理结果序列化

  • server stub 发送消息至消费方

  • client stub 接收到消息并反序列化

  • client stub 返回结果给 client;

RPC框架的实现目标则是将上面这些步骤封装起来,特别是调用和序列化/反序列化的过程给封装起来,让用户感觉上像调用本地服务一样的调用远程服务。

在这里插入图片描述

RPC框架需要解决的问题?

  • 如何确定客户端和服务端之间的通信协议?
  • 如何更高效地进行网络通信?
  • 服务端提供的服务如何暴露给客户端?
  • 客户端如何发现这些暴露的服务?
  • 如何更高效地对请求对象和响应结果进行序列化和反序列化操作?

RPC的实现基础?

  • 需要有非常高效的网络通信,比如一般选择Netty作为网络通信框架;
  • 需要有比较高效的序列化框架,比如谷歌的Protobuf序列化框架;
  • 可靠的寻址方式(服务的注册与发现),比如可以使用Zookeeper来注册服务等等;
  • 如果是带会话(状态)的RPC调用,还需要有会话和状态保持的功能;

RPC使用了哪些关键技术?

  • 动态代理

    生成Client Stub(客户端存根)和Server Stub(服务端存根)的时候需要用到动态代理技术,可以使用JDK提供的原生的动态代理机制,也可以使用开源的:CGLib代理,Javassist字节码生成技术。

  • 序列化和反序列化

    在网络中,所有的数据都将会被转化为字节进行传送,所以为了能够使参数对象在网络中进行传输,需要对这些参数进行序列化和反序列化操作。

  • NIO通信

    出于并发性能的考虑,传统的阻塞式 IO 显然不太合适,因此我们需要异步的 IO,即 NIO。Java 提供了 NIO 的解决方案,Java 7 也提供了更优秀的 NIO.2 支持。可以选择Netty或者MINA来解决NIO数据传输的问题。

  • 服务注册中心

    可选:Redis、Zookeeper、Consul 、Etcd。一般使用ZooKeeper提供服务注册与发现功能,解决单点故障以及分布式部署的问题(注册中心)。

主流RPC框架有哪些?

  • Dubbo——阿里、Apache
  • gRPC——谷歌
  • Thrift——Facebook、Apache

gRPC 和 Thrift 虽然支持跨语言的 RPC 调用,但是它们只提供了最基本的 RPC 框架功能,缺乏一系列配套的服务化组件和服务治理功能的支撑。

Dubbo 不论是从功能完善程度、生态系统还是社区活跃度来说都是最优秀的。而且国内有非常多成功的案例,是一款经得起生产考验的成熟稳定的 RPC 框架。

Dubbo 主要是给 Java 语言使用。虽然Dubbo也能兼容部分语言,但还是不太推荐。如果需要跨多种语言调用的话,可以考虑使用 gRPC。

RPC的调用流程

A服务器想调用B服务器上的一个方法,需要的步骤:

  • 建立通信

    • 主要是通过在客户端和服务器之间建立TCP连接

    • 连接可以是短连接(调用时先建立连接,调用结束立刻断掉),也可以是长连接(客户端和服务器建立连接之后保持长期连接,不管此时有无数据包的发送,可以配合心跳机制定期检测连接是否存活),多个远程过程调用共享同一个连接

  • 服务的注册与发现:是RPC的实现基石,可以用zk等来作为注册中心。

    • 从服务提供者的角度看:

      • 当服务提供者启动时,需要将自己提供的服务注册到注册中心,以便服务消费者能够通过服务注册中心找到自己;

      • 当服务提供者由于各种原因致使提供的服务停止时,需要向注册中心注销停止的服务;

      • 服务的提供者需要定期向服务注册中心发送心跳检测,服务注册中心如果一段时间未收到来自服务提供者的心跳后,认为该服务提供者已经停止服务,则将该服务从注册中心上去掉。

    • 从调用者的角度看:

      • 服务调用者启动时,根据自己订阅的服务向注册中心查找服务提供者的地址等信息;

      • 当服务调用者消费的服务上线或者下线的时候,注册中心会通知该服务的调用者;

      • 服务调用者下线的时候,则取消订阅

  • 远程服务调用:调用方调用远程方法,通过代理对象来传输网络请求。

  • 网络传输:发送网络请求来传递目标类和方法的信息以及方法的参数等数据到服务提供端。

  • 序列化与反序列化

    • 序列化

      当A机器上的应用发起一个RPC调用时,调用方法和其入参等信息需要通过底层的网络协议如TCP传输到B机器,由于网络协议是基于二进制的,所有我们传输的参数数据都需要先进行序列化(Serialize)或者编组(marshal)成二进制的形式才能在网络中进行传输。然后通过寻址操作和网络传输将序列化或者编组之后的二进制数据发送给B机器。

    • 反序列化

      当B机器接收到A机器的应用发来的请求之后,又需要对接收到的参数等信息进行反序列化操作,即将二进制信息恢复为内存中的表达方式。

  • 本地服务调用

    服务端找到本地的方法进行本地调用(一般是通过动态代理生成代理Proxy反射调用),之后得到调用的返回值。此时还需要再把返回值发送回A机器,同样也需要经过序列化操作,然后再经过网络传输将二进制数据发送回A机器,A再次进行反序列化操作,得到响应对象。

通常,经过以上几个步骤之后,一次完整的RPC调用算是完成了,另外可能因为网络抖动等原因需要重试等。

在这里插入图片描述

如何设计一个RPC框架?

遇到这类问题,起码从你了解的类似框架的原理入手,自己说说参照 dubbo 的原理,你来设计一下,举个例子,dubbo 不是有那么多分层么?而且每个分层是干啥的,你大概是不是知道?那就按照这个思路大致说一下吧,起码你不能懵逼,要比那些上来就懵,啥也说不出来的人要好一些。

举个栗子,我给大家说个最简单的回答思路:

  • 上来你的服务就得去注册中心注册吧,保留各个服务的信息,可以用 zookeeper 来做。
  • 然后你的消费者需要去注册中心拿对应的服务信息吧,而且每个服务可能会存在于多台机器上。
  • 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。
  • 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的轮询。
  • 接着找到一台机器,就可以跟它发送请求了
    • 第一个问题用什么传输协议?可以说dubbo协议
    • 第二个问题咋发送?你可以说用 netty 了,nio 方式;
    • 第三个问题发送啥格式数据?你可以说用 hessian 序列化协议了,或者是别的。
    • 然后请求过去了。
  • 服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码。

image-20230201150953832

如何保证分布式系统中接口调用的顺序性?

  • 尽量避免引入顺序性:这个系统从业务逻辑上最好不要设计顺序性的保证,因为一旦引入顺序性保障,就需要引入其他技术或中间件,导致系统的复杂度上升,性能下降,吞吐量降低,热点数据压力过大等问题。

  • 一致性hash+内存队列

    • 一致性hash负载均衡策略,比如同一个订单id对应的请求都给分发到同一个机器上去。
    • 在这台机器上因为可能是多线程并发执行的,就得将这个订单id对应的请求放进一个内存队列里去,强制排队,确保顺序性。

img

  • 分布式锁

img

Dubbo

dubbo 是一款高性能、轻量级的开源 Java RPC 框架,提供了六大核心能力:

根据 Dubbo 官方文档open in new window的介绍,Dubbo 提供了六大核心能力

  1. 面向接口代理的高性能RPC调用。
  2. 智能容错和负载均衡。
  3. 服务自动注册和发现。
  4. 高度可扩展能力。
  5. 运行期流量调度。
  6. 可视化的服务治理与运维。

dubbo架构的核心组件有哪些?

  • Container: 服务运行的容器,负责加载、运行服务provider。

  • Provider: 暴露服务的服务提供方,在启动时向注册中心注册自己提供的服务

  • Consumer: 远程调用服务的服务消费方,再启动时向注册中心订阅自己所需的服务

  • Registry: 服务注册与发现注册中心。注册中心会返回provider的地址列表给消费者

  • Monitor: 统计服务的调用次数和调用时间的监控中心provider和consumer会定时异步发送统计数据到监控中心

image-20230130153945017

Dubbo 服务注册与发现的流程?

  • Container负责启动,加载,运行Provider。
  • Provider在启动时,向注册中心注册自己提供的服务。
  • Consumer在启动时,向注册中心订阅自己所需的服务。
  • Registry返回provider地址列表给Consumer,consumer缓存在本地。如有变更(provider宕机等),注册中心将基于长连接将变更数据推送给consumer
  • Consumer从provider地址列表中,基于负载均衡算法,选一台provider调用,如果调用失败,再选另一台调用
  • Consumer和Provider在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心Monitor。

dubbo服务调用是阻塞的吗?

默认是阻塞的,可以异步调用,没有返回值的可以这么做。异步调用会返回一个 Future 对象。

dubbo架构设计与工作原理?

从下至上分为十层,各层均为单向依赖。

左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。

dubbo-framework

  • 服务接口层(Service):与业务逻辑相关,根据 provider 和 consumer 的业务设计对应的接口和实现

  • 配置层(Config)对外配置接口,支持代码配置,同时也支持基于 Spring 来做配置,以 ServiceConfig 和 ReferenceConfig 为中心

  • 服务代理层(Proxy)服务接口透明代理,是rpc的关键,通过代理类进行网络通信,生成服务的客户端 Stub 和 服务端的 Skeleton,以 ServiceProxy 为中心,扩展接口为 ProxyFactory

  • 服务注册层(Registry)封装服务地址的注册和发现,以服务 URL 为中心,扩展接口为 RegistryFactory、Registry、RegistryService

  • 路由层(Cluster)封装多个provider的路由和负载均衡,将多个实例组合成一个服务,并桥接注册中心,以Invoker 为中心,扩展接口为 Cluster、Directory、Router 和 LoadBlancce

  • 监控层(Monitor):RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory、Monitor 和 MonitorService

  • 远程调用层(Protocol)封装 RPC 调用,以 Invocation 和 Result 为中心,扩展接口为 Protocal、Invoker 和 Exporter

  • 信息交换层(Exchange)封装请求响应模式,同步转异步。以 Request 和Response 为中心,扩展接口为 Exchanger、ExchangeChannel、ExchangeClient 和 ExchangeServer

  • 网络传输层(Transport)抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel、Transporter、Client、Server 和 Codec

  • 数据序列化层(Serialize)对需要传输的数据进行序列化,可复用的一些工具,扩展接口为 Serialization、ObjectInput、ObjectOutput 和 ThreadPool

Dubbo集群提供了哪些负载均衡策略?

  • RandomLoadBalance(默认):(加权)随机,可以对不同provider设置不同的权重,会按照权重来负载均衡,权重越大分配流量越高,但具有概率性具体的次数并不完全跟权重相对应,次数越多就越趋近于权重

  • RoundRobinLoadBalance:(加权)轮询,平均分布,也是可以设置权重具体次数一定与权重对应

  • LeastActiveLoadBalance:最小活跃数策略。

    • 初始状态下所有provider的活跃数均为 0,provider每收到一个请求,其活跃数 +1,请求处理完后,其活跃数 -1。谁的活跃数越小,谁的处理速度就越快,性能越好,所以优先分配给它。

    • 如果有多个provider活跃数相等就再走一遍RandomLoadBalance。

  • ConstantHashLoadBalance:一致性 Hash,使相同参数请求总是发到同一provider,一台机器宕机,可以基于虚拟节点,分摊至其他provider,避免引起provider的剧烈变动。

Dubbo的集群容错方案有哪些?

  • Failover Cluster(默认):失败自动切换,当出现失败或超时,重试其它服务器。常用于读操作,但重试会带来更长延迟。默认最多重试2次(不包括第一次)后报错,该次数可以配置。
  • Failfast Cluster:快速失败只发起一次调用,失败立即报错。常用于非幂等性的写操作,比如新增记录。
  • Failsafe Cluster:失败安全,出现异常直接忽略。常用于写入审计日志等操作。
  • Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。常用于消息通知操作。
  • Forking Cluster:并行调用多个服务器只要一个成功即返回。常用于实时性要求较高的读操作,但会浪费更多服务资源。可通过 forks=”2″ 来设置最大并行数。
  • Broadcast Cluster:广播调用所有provider逐个调用,任意一台报错则报错 。常用于通知所有provider更新缓存或日志等本地资源信息。

Dubbo 支持哪些序列化方式?

  • jdk原生的序列化:针对java,且性能太差

  • hessian2(默认):跨语言

  • JSON:跨语言,但性能差

  • Kryo:针对java,性能很好,比较成熟

  • FST:针对java,性能很好

  • Protostuff:跨语言

  • ProtoBuf:跨语言,性能好

  • 。。。

Dubbo 有哪些注册中心?

  • Zookeeper(推荐) :使用 watch 机制监听数据变更。

    • 在整个 Dubbo 服务的启动过程中,provider在启动时向 /dubbo/com.foo.BarService/providers创建数据节点,写入自己的URL。

    • consumer在启动时订阅(watch/provider节点) /dubbo/com.foo.BarService/providers 下所需的provider的URL,并向 /dubbo/com.foo.BarService/consumers 创建数据节点,写入自己的URL。

/user-guide/images/zookeeper.jpg

  • Nacos(推荐):功能强大,拓展性强,有好看的控制台,符合国人习惯。

  • Multicast(仅限开发阶段使用):不需要任何中心节点,只要广播地址,就能进行服务注册和发现,基于网络中组播传输实现。

  • Redis:采用 key/map 存储,key 存服务名和类型,map中key存服务url,value服务过期时间。基于发布/订阅模式通知数据变更。

  • 多注册中心:跨区域,跨机房,按需选择

  • Simple 注册中心(dubbo 2.7之后以移除)。

Dubbo 的注册中心集群挂掉,发布者和订阅者之间还能通信么?

可以。在刚启动 Dubbo 时,consumer会从注册中心拉取已注册的provider地址列表等数据,缓存在本地

但如果有变更,就可能会出错,所以还是要尽快恢复注册中心。

Dubbo 的微内核架构了解吗?

Dubbo 采用微内核(Microkernel)+ 插件(Plugin)的模式,也就是微内核架构微内核只负责组装插件。基于微内核架构的系统,非常易于扩展功能

何为微内核架构? 《软件架构模式》 这本书是这样介绍的:

微内核架构模式(有时被称为插件架构模式)是实现基于产品应用程序的一种自然模式。基于产品的应用程序是已经打包好并且拥有不同版本,可作为第三方插件下载的。然后,很多公司也在开发、发布自己内部商业应用像有版本号、说明及可加载插件式的应用软件(这也是这种模式的特征)。微内核系统可让用户添加额外的应用如插件,到核心应用,继而提供了可扩展性和功能分离的用法。

微内核架构包含两类组件:核心系统(core system)插件模块(plug-in modules)

img

  • 核心系统提供系统所需核心能力,插件模块可以扩展系统的功能

  • 一般,微核心都会采用 Factory、IoC、OSGi 等方式管理插件生命周期。但Dubbo 不想强依赖 Spring 等 IoC 容器,也不想自己造一个小的 IoC 容器(过度设计),因此dubbo采用了最简单的 Factory 方式管理插件 ,具体实现方式是对jdk标准的SPI 扩展机制进行增强来更好地满足dubbo的需求 。

Dubbo SPI 对 Jdk SPI 增强了啥?

  • JDK SPI 存在的问题:

    • JDK 标准的 SPI 会一次性加载所有的扩展实现,如果有的扩展很耗时,但也没用上,很浪费资源
    • 如果扩展点加载失败,连扩展点名称都拿不到了,会间接导致报错信息错误。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
  • DUBBO SPI:

    • 按需加载。用哪个扩展类则实例化哪个扩展类,减少资源浪费。

    • 增加扩展点 IOC 和 AOP 的能力,dubbo可以自动注入该依赖对象,自动发现扩展类的包装类,完成包装类的构造,增强扩展类的功能。

    • 具备动态选择扩展类的能力。Dubbo 扩展会基于参数,在运行时动态选择对应的扩展类,提高了 Dubbo 的扩展能力。

    • 可以对扩展实现进行排序。能够基于用户需求,指定扩展实现的执行顺序。

Dubbo spi 扩展加载流程

  • 读取并解析配置文件
  • 缓存所有扩展实现
  • 基于用户执行的扩展名,实例化对应的扩展实现
  • 进行扩展实例属性的 IOC 注入以及 AOP 实例化扩展的包装类,实现 AOP 特性

//imgs/v3/concepts/extension-load.png

如何使用dubbo的spi拓展?

下面以扩展协议为例进行说明如何利用 Dubbo 提供的扩展能力扩展 Triple 协议。

  • 在协议的实现 jar 包内放置文本文件:META-INF/dubbo/org.apache.dubbo.remoting.api.WireProtocol
tri=org.apache.dubbo.rpc.protocol.tri.TripleHttp2Protocol
  • 实现类内容
@Activate
//说明下:Http2WireProtocol 实现了 WireProtocol 接口
public class TripleHttp2Protocol extends Http2WireProtocol {
    // ...
}
  • Dubbo 配置模块中,扩展点均有对应配置属性或标签,通过配置指定使用哪个扩展实现。比如:
<dubbo:protocol name="tri" />

Dubbo 支持哪些RPC协议?

  • Dubbo协议(推荐):单个TCP长连接NIO 异步通讯,适合高并发小数据量的常规服务调用,以及consumer多于provider。属于私有协议,只能用在dubbo上,互通性差。

    • 连接个数:单连接
    • 连接方式:长连接
    • 传输协议:TCP
    • 传输方式:NIO 异步传输
    • 序列化:Hessian2 二进制序列化
    • 适用范围:小数据包(建议小于100K),consumer比provider多,单一consumer无法压满provider,尽量不要用 dubbo 协议传输大文件或超大字符串。
    • 适用场景:常规远程服务方法调用

    dubbo-protocol.jpg

  • Triple协议(dubbo3之后推荐):基于http2,跨环境、跨语言、跨生态互通。

    • 兼容grpc
    • 序列化:Protobuff
    • 顺应云原生发展趋势
  • REST协议:用来开发restful风格的服务比较简单。其实也就是http协议的直接应用,默认基于json传输。

  • gRPC协议:顺应云原生趋势

  • HTTP协议:基于 HTTP 表单,采用 Spring 的 HttpInvoker 实现。

    • 连接个数:多连接
    • 连接方式:短连接
    • 传输协议:HTTP
    • 传输方式:同步传输
    • 序列化:表单序列化
    • 适用范围:传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件。
    • 适用场景:需同时给应用程序和浏览器 JS 使用的服务。
  • Thrift协议:对 thrift(一个RPC框架) 原生协议的扩展。

  • Rmi协议:采用 JDK 标准的 java.rmi.* 实现,采用阻塞式短连接和 JDK 标准序列化方式。

    • 连接个数:多连接
    • 连接方式:短连接
    • 传输协议:TCP
    • 传输方式:同步传输
    • 序列化:Java 标准二进制序列化
    • 适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。
    • 适用场景:常规远程服务方法调用,与原生RMI服务互操作
  • Redis协议:基于redis实现。

  • Hessian协议:集成 Hessian(一个RPC框架) 的服务,Hessian 底层采用 Http 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。

    • 连接个数:多连接
    • 连接方式:短连接
    • 传输协议:HTTP
    • 传输方式:同步传输
    • 序列化:Hessian二进制序列化
    • 适用范围:传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件。
    • 适用场景:页面传输,文件传输,或与原生hessian服务互操作。
  • Webservice协议

    • 连接个数:多连接
    • 连接方式:短连接
    • 传输协议:HTTP
    • 传输方式:同步传输
    • 序列化:SOAP 文本序列化
    • 适用场景:系统集成,跨语言调用
  • Memcached协议:基于memcached实现。

Monitor 实现原理?

  • Consumer 在发起调用之前会先走filter 链,provider 在接收到请求时也是先走filter 链,再进行真正的业务逻辑处理。默认情况下,在 consumer 和 provider 的 filter 链中都会有 Monitorfilter。

  • MonitorFilter 向 DubboMonitor 发送数据

  • DubboMonitor 将数据聚合后(默认聚合 1min 中的统计数据)暂存到ConcurrentMap<Statistics, AtomicReference> statisticsMap,然后使用一个含有 3 个线程(DubboMonitorSendTimer)的线程池每隔1min,调用 SimpleMonitorService 遍历发送 statisticsMap 中的统计数据,每发送完一个,就重置当前的 Statistics 的 AtomicReference

  • SimpleMonitorService 将这些聚合数据塞入阻塞队列 queue 中(队列大小为 100000)。

  • SimpleMonitorService 使用一个后台线程(DubboMonitorAsyncWriteLogThread)将 queue 中的数据写入文件

  • SimpleMonitorService 还会使用一个含有 1 个线程(DubboMonitorTimer)的线程池每隔 5min,将文件中的统计数据画成图表

如何基于 dubbo 进行服务治理、服务降级、失败重试和超时重试?

服务治理

  • 调用链路自动生成

大型分布式系统的服务调用关系是非常复杂的,需要对各个服务之间的调用自动记录下来,然后自动将各个服务之间的依赖关系和调用链路生成出来,做成一张图。

dubbo-service-invoke-road

  • 服务访问压力以及时长统计(Monitor做的):需要自动统计各个接口和服务之间的调用次数以及访问延时,而且要分成两个级别。基于这些信息就能思考如何来扩容和优化。

    • 接口粒度:每个服务的每个接口每天被调用多少次,TP50/TP90/TP99,三个档次的请求延时分别是多少;

    • 从源头入口开始,一个完整的请求链路经过所有服务后,全链路请求延时的 TP50/TP90/TP99,分别是多少;每天全链路走多少次。

  • 其它

    • 服务分层(避免循环依赖)

    • 调用链路失败监控和报警

    • 服务鉴权

    • 每个服务的可用性的监控(接口调用成功率?能有几个 9?)

服务降级

通过在某个服务的dubbo:reference 中设置 mock=“return null”,调用这个接口失败的时候,统一返回 null。

public interface HelloService {
   void sayHello();
}

public class HelloServiceImpl implements HelloService {
    public void sayHello() {
        System.out.println("hello world......");
    }
}
<?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://code.alibabatech.com/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <dubbo:application name="dubbo-provider" />
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    <dubbo:protocol name="dubbo" port="20880" />
    <dubbo:service interface="com.zhss.service.HelloService" ref="helloServiceImpl" timeout="10000" />
    <bean id="helloServiceImpl" class="com.zhss.service.HelloServiceImpl" />
</beans>

<?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://code.alibabatech.com/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <dubbo:application name="dubbo-consumer"  />
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    <dubbo:reference id="fooService" interface="com.test.service.FooService"  timeout="10000" check="false" mock="return null">
    </dubbo:reference>
</beans>

mock 的值也可以设置为 true,再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+Mock” 。然后在 Mock 类里实现自己的降级逻辑。

public class HelloServiceMock implements HelloService {
    public void sayHello() {
        // 降级逻辑
    }
}

失败、超时重试

<dubbo:reference id="xxxx" interface="xx" check="true" async="false" retries="3" timeout="2000"/>

Dubbo 在安全方面有哪些措施?

  • 通过 Token 防止用户绕过注册中心直连,然后在注册中心上管理授权。
  • 还提供服务黑白名单,来控制服务所允许的调用方。

Dubbo 可以对结果进行缓存吗?

可以。设置<dubbo:reference cache=“true” />即可

dubbo可以实现事务吗?

dubbo本身不可以,作者也不建议rpc框架层面去实现,而是把事务控制当做拓展点来实现。可以使用seata等其他中间件。

Dubbo 用到哪些设计模式?

  • 工厂模式

    Provider 在 export 服务时,会调用 ServiceConfig 的 export 方法。ServiceConfig中有个字段:

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

Dubbo 里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了 JDKSPI 的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在 classpath下增加个文件就可以了,代码零侵入。另外,像上面的 Adaptive 实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。

  • 装饰器模式

    Dubbo 在启动和调用阶段都大量使用了装饰器模式。以 Provider 提供的调用链为例,具体的调用链代码是在 ProtocolFilterWrapper 的 buildInvokerChain 完成的,具体是将注解中含有 group=provider 的 Filter 实现,按照 order 排序,最后的调用顺序是:

EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter ->
ExecuteLimitFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter ->
ExceptionFilter

更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter 的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter 则只是在主功能上添加了功能,更改当前线程的 ClassLoader,这是典型的装饰器模式。

  • 观察者模式

    Dubbo 的 Provider 启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个 listener。注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息,provider 接受到 notify 消息后,运行 NotifyListener 的 notify 方法,执行监听器方法。

  • 动态代理模式

    Dubbo 扩展 JDK SPI 的类 ExtensionLoader 的 Adaptive 实现是典型的动态代理实现。Dubbo 需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是 ExtensionLoader 的 createAdaptiveExtensionClassCode 方法。代理类主要逻辑是,获取 URL 参数中指定参数的值作为获取实现类的 key。

上一篇 下一篇