深入理解Dubbo与实战 - 1. 架构
1. Dubbo 高性能 RPC框架
1.1 架构
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
1.2 总体分层
Dubbo 总体分为业务层、RPC层、Remote 三层。
Service和Config两层可以认为是API层,主要提供给API使用者,使用者无须关心底层
的实现,只需要配置和完成业务代码即可;后面所有的层级合在一起,可以认为是SPI层,主要提供给扩展者使用,即用户可以基于Dubb。框架做定制性的二次开发,扩展其功能。
层次名 | 层次名 | 作用 |
---|---|---|
Service | 业务层 | 开发者实现的业务代码 |
Config | 配置层 | 要围绕ServiceConfig (暴露的服务配置)和ReferenceConfig (引用的服务配置)两个实现类展开,初始化配置信息。可以理解为该层管理了整个Dubbo的配置 |
Proxy | 服务代理层 | 在Dubbo中,无论生产者还是消费者,框架都会生成一个代理类,整个过程对上层是透明的。当调用一个远程接口时,看起来就像是调用了一个本地的接口一样,代理层会自动做远程调用并返回结果,即让业务层对远程调用完全无感 |
Registry | 注册层 | 负责服务的注册与发现 |
Cluster | 集群容错层 | 该层主要负责:远程调用失败时的容错策略(如失败重试、快速失败);选择具体调用节点时的负载均衡策略(如随机、一致性Hash等);特殊调用路径的路由策略(如某个消费者只会调用某个IP的生产者) |
Monitor | 监控层 | 监控统计调用次数和调用时间 |
Protocol | 远程调用层 | 封装RPC调用具体过程,Protocol是Invoker暴露(发布一个服务让别人可以调用)和引用(引用一个远程服务到本地)的主功能入口,它负责管理Invoker的整个生命周期。Invoker是Dubbo的核心模型,框架中所有其他模型都向它靠拢,或者转换成它,它代表一个可执行体。允许向它发起invoke调用,它可能是执行一个本地的接口实现,也可能是一个远程的实现,还可能一个集群实现 |
Exchange | 信息交换层 | 建立Request-Response模型,封装请求响应模式,如把同步请求转化为异步请求 |
Transport | 网络传输层 | 把网络传输抽象为统一的接口,如Mina和Netty虽然接口不一样,但是Dubbo在它们上面又封装了统一的接口。用户也可以根据其扩展接口添加更多的网络传输方式 |
Serialize | 序列化层 | 序列化层。如果数据要通过网络进行发送,则需要先做序列化,变成二进制流。序列化层负责管理整个框架网络传输时的序列化/反序列化工作 |
1.3 调用过程
服务暴露过程:
首先,服务器端(服务提供者)在框架启动时,会初始化服务实例,通过Proxy组件调用具体协议(Protocol ),把服务端要暴露的接口封装成Invoker (真实类型是
AbstractProxylnvoker),然后转换成Exporter,这个时候框架会打开服务端口等并记录服务实例到内存中,最后通过Registry把服务元数据注册到注册中心。
- Proxy组件:我们知道,Dubbo中只需要引用一个接口就可以调用远程的服务,并且只需要像调用本地方法一样调用即可。其实是Dubbo框架为我们生成了代理类,调用的方法其实是Proxy组件生成的代理方法,会自动发起远程/本地调用,并返回结果,整个过程对用户完全透明。
- Protocol:顾名思义,协议就是对数据格式的一种约定。它可以把我们对接口的配置,根据不同的协议转换成不同的Invoker对象。例如:用DubboProtocol可以把XML文件中一个远程接口的配置转换成一个Dubbolnvoker.
- Exporter:用于暴露到注册中心的对象,它的内部属性持有了 Invoker对象,我们可以认为它在Invoker上包了 一层。
- Registry:把Exporter注册到注册中心。
消费者调用过程
- 首先,调用过程也是从一个Proxy开始的,Proxy持有了一个Invoker对象。然后触发invoke调用。
- 在invoke调用过程中,需要使用Cluster处理容错。Cluster在调用之前会通过Directory获取所有可以调用的远程服务Invoker列表(一个接口可能有多个节点提供服务)。由于可以调用的远程服务有很多,此时如果用户配置了路由规则,那么还会根据路由规则将Invoker列表过滤一遍。继续通过LoadBalance方法做负载均衡,最终选出一个可以调用的Invoker。
- 这个Invoker在调用之前又会经过一个过滤器链,这个过滤器链通常是处理上下文、限流、计数等。
- 接着,会使用Client做数据传输,如我们常见的Netty Client等。传输之前肯定要做一些私有协议的构造,此时就会用到Codec接口。构造完成后,就对数据包做序列化(Serialization),然后传输到服务提供者端。服务提供者收到数据包,也会使用Codec处理协议头及一些半包、
粘包等。处理完成后再对完整的数据报文做反序列化处理。 - 随后,这个Request会被分配到线程池(ThreadPool)中进行处理Server会处理这些Request,根据请求查找对应的Exporter (它内部持有了 Invoker。Invoker是被用装饰器模式一层一层套了非常多Filter的,因此在调用最终的实现类之前,又会经过一个服务提供者端的过滤器链。
- 最终,我们得到了具体接口的真实实现并调用,再原路把结果返回。