Ref:
https://www.cnblogs.com/javazhiyin/p/11966271.html
https://segmentfault.com/a/1190000023085675
服务引入:
https://segmentfault.com/a/1190000024443857
https://dubbo.apache.org/zh/docs/v2.7/dev/source/refer-service/
- RPC 就是 Remote Procedure Call,远程过程调用,它相对应的是本地过程调用。
- SOA 是 Service Oriented Architecture,面向服务架构,可以将它理解为一个架构模型或者一种设计方法,而并不是服务解决方案。其中包含多个服务, 服务之间通过相互依赖或者通信机制来通信,最终提供一系列的功能。一个服务通常以独立的形式存在于系统中,各个服务之间通过网络调用 。
Dubbo 是什么
基于 Java 的高性能 RPC 分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,以及 SOA 服务治理方案。
远程服务调用的分布式框架(告别 Web Service 模式中的 WSdl,以服务者与消费者的方式在 dubbo 上注册)
其核心部分包含:
- 远程通讯:提供对多种基于长连接的 NIO 框架抽象封装,包括多种线程模型,序列化,以及 “请求 - 响应” 模式的信息交换方式。
- 集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
自动发现:基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
Dubbo 做了什么
透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何 API 侵入。
- 软负载均衡及容错机制,可在内网替代 F5 等硬件负载均衡器,降低成本,减少单点。
- 服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的 IP 地址,并且能够平滑添加或删除服务提供者。
Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需用 Spring 加载 Dubbo 的配置即可,Dubbo 基于 Spring 的 Schema 扩展进行加载。
内置服务容器
Spring Container
Jetty Container
Log4j Container
配置项
配置关系:
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,默认 check=”true”,可以通过 check=”false” 关闭检查。
注册中心
推荐使用 Zookeeper 作为注册中心,还有 Redis、Multicast、Simple 注册中心,但不推荐。
集群容错方案
读操作建议使用 Failover 失败自动切换,默认重试两次其他服务器。
写操作建议使用 Failfast 快速失败,发一次调用失败就立即报错。
负载均衡策略
通信框架
Dubbo 默认使用 Netty 框架,也是推荐的选择,另外内容还集成有 Mina、Grizzly。
服务调用阻塞吗
默认是同步等待结果阻塞的,支持异步调用。
Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。
异步调用流程如下:
Dubbo 优雅停机
Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的;只有通过 kill PID 时,才会执行。
与 SpringCloud
dubbo 的优势
- 单一应用架构:当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的 数据访问框架(ORM)是关键。
- 垂直应用架构:当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的 Web 框架(MVC)是关键。
- 分布式服务架构:当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的需求。此时,用于提高业务复用及整合的 分布式服务框架(RPC)是关键。
- 流动计算架构:当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的 资源调度和治理中心(SOA)是关键。
SpringCloud 优势
- 约定优于配置
- 开箱即用、快速启动
- 适用于各种环境
- 轻量级的组件
- 组件支持丰富,功能齐全
两者相比较
1、dubbo 由于是二进制的传输,占用带宽会更少
2、SpringCloud 是 http 协议传输,带宽会较多,同时 http 协议一般会使用 JSON 报文,消耗会更大
3、dubbo 的开发难度较大,原因是 dubbo 的 jar 包依赖问题很多大型工程无法解决
4、springcloud 的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级
5、dubbo 的注册中心可以选择 zk,redis 等多种,springcloud 的注册中心只能用 eureka 或者自研
根据具体的团队水平,业务情况等特点,dubbo 和 SpringCloud 各自可以发挥各自不同的优势
也可参考:https://segmentfault.com/a/1190000023907463
- 在 Spring Cloud 构建的微服务系统中,大多数的开发者使用都是官方提供的 Feign 组件来进行内部服务通信,这种声明式的 HTTP 客户端使用起来非常的简洁、方便、优雅,并且和开发平台、语言无关,但是通常情况下,HTTP 并不会开启 KeepAlive 功能,即当前连接为短连接,短连接的缺点是每次请求都需要建立 TCP 连接,这使得其效率变的相当低下。
- 对外部提供 REST API 服务是一件非常好的事情,但是如果内部调用也是使用 HTTP 调用方式,就会显得显得性能低下,Spring Cloud 默认使用的 Feign 组件进行内部服务调用就是使用的 HTTP 协议进行调用,这时,如果内部服务使用 RPC 调用,对外使用 REST API,将会是一个非常不错的选择。
Ref: https://segmentfault.com/a/1190000037434049
Dubbo 几个角色
dubbo 角色及其作用:
角色 | 角色说明 |
---|---|
Consumer | 需要调用远程服务的服务消费方 |
Registry | 注册中心 |
Provider | 服务提供方 |
Container | 服务运行的容器 |
Monitor | 监控中心 |
说明:
- 服务提供者 Provider 启动然后向注册中心注册自己所能提供的服务。
- 服务消费者 Consumer 启动向注册中心订阅自己所需的服务。然后注册中心将提供者元信息通知给 Consumer, 之后 Consumer 因为已经从注册中心获取提供者的 IP 地址,因此可以通过负载均衡选择一个 Provider 直接调用 。
- 服务提供者元数据变更,注册中心会把变更推送给服务消费者。
服务提供者和消费者都会在内存中记录着调用的次数和时间,然后定时发送统计数据到监控中心。
Dubbo 服务暴露与引入流程
服务暴露
服务的暴露起始于 Spring IOC 容器刷新完毕之后,在刷新容器最后一步发布 ContextRefreshEvent 事件的时候,通知实现了 ApplicationListener 的 ServiceBean 类进行回调 onApplicationEvent 事件方法,Dubbo 会在这个方法中调用 ServiceBean 父类 ServiceConfig 的 export 方法,该方法真正实现了服务的(异步或者非异步)发布。
Dubbo 会通过 proxyFactory.getInvoker,利用 javassist 来进行动态代理,封装真的实现类,然后再通过 URL 参数选择对应的协议来进行 protocol.export,默认是 Dubbo 协议。
在第一次暴露的时候会调用 createServer 来创建 Server,默认是 NettyServer。
然后将 export 得到的 exporter 存入一个 Map 中,供之后的远程调用查找,然后会向注册中心注册提供者的信息。服务引入
Dubbo 服务引用的时机有两个:
第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务;
- 第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用;
这两个引用服务的时机区别在于,第一个是饿汉式的,第二个是懒汉式的。
默认情况下,Dubbo 使用懒汉式引用服务。如果需要使用饿汉式,可通过配置
当服务被注入到其他类中时,Spring 会第一时间调用 getObject 方法,并由该方法执行服务引用逻辑。
在进行具体工作之前,需先进行配置检查与收集工作。
接着根据收集到的信息决定服务引入的方式,有三种:
- 引用本地 (JVM) 服务,
- 通过直连方式引用远程服务,
- 通过注册中心引用远程服务。
不管是哪种引用方式,最后都会得到一个 Invoker 实例。如果有多个注册中心,多个服务提供者,这个时候会得到一组 Invoker 实例,此时需要通过集群管理类 Cluster 将多个 Invoker 合并成一个实例。合并后的 Invoker 实例已经具备调用本地或远程服务的能力了,但并不能将此实例暴露给用户使用,这会对用户业务代码造成侵入。此时框架还需要通过代理工厂类 (ProxyFactory) 为服务接口生成代理类,并让代理类去调用 Invoker 逻辑。避免了 Dubbo 框架代码对业务代码的侵入,同时也让框架更容易使用。
服务的引入时机有两种(默认是懒汉式):
- 饿汉式就是加载完毕就会引入:通过实现 Spring 的 InitializingBean 接口中的 afterPropertiesSet 方法,容器通过调用 ReferenceBean 的 afterPropertiesSet 方法时引入服务。
- 懒汉式:只有当这个服务被注入到其他类中时启动引入流程。
- 先根据配置参数组装成 URL ,一般而言会配置到注册中心,所以会构建 RegistryDirectory 向注册中心注册消费者的信息,并且订阅提供者、配置、路由等节点。
- 得知提供者的信息之后会进入 Dubbo 协议的引入,会创建 Invoker ,期间会创建 NettyClient 来进行远程通信,并且通过 directory 和 cluster 进行底层多个服务提供者的屏蔽、容错和负载均衡等,最后通过 Cluster 来包装 Invoker,默认是 FailoverCluster,最终返回代理类。
服务调用
Ref: https://dubbo.apache.org/zh/docs/v2.7/dev/source/service-invoking-process/
通过一张图了解 Dubbo 服务调用过程。
- 首先服务消费者通过代理对象 Proxy 发起远程调用,接着通过网络客户端 Client 将编码后的请求发送给服务提供方的网络层上,也就是 Server。
- Server 在收到请求后,首先要做的事情是对数据包进行解码。然后将解码后的请求发送至分发器 Dispatcher,再由分发器将请求派发到指定的线程池上,最后由线程池调用具体的服务。
这就是一个远程调用请求的发送与接收过程。至于响应的发送与接收过程,这张图中没有表现出来。对于这两个过程:
- 调用某个接口的方法会调用之前生成的代理类,然后会从 cluster 中经过路由的过滤、负载均衡机制选择一个 invoker 发起远程调用,此时会记录此请求和请求的 ID 等待服务端的响应。
- 服务端接受请求之后会通过参数找到之前暴露存储的 map,得到相应的 exporter ,然后最终调用真正的实现类,再组装好结果返回,这个响应会带上之前请求的 ID。
- 消费者收到这个响应之后会通过 ID 去找之前记录的请求,然后找到请求之后将响应塞到对应的 Future 中,唤醒等待的线程,最后消费者得到响应,一个流程完毕。
关键的就是 cluster、路由、负载均衡,然后 Dubbo 默认是异步的,所以请求和响应是如何对应上的。
SPI
什么是 SPI
不论是从 Dubbo 协议,还是 cluster,export 方法等等均涉及 SPI,SPI 是 Dubbo 可扩展性的基石。
SPI 是 Service Provider Interface,主要用于框架中,框架定义好接口,不同的使用者有不同的需求,因此需要有不同的实现,而 SPI 就通过定义一个特定的位置,Java SPI 约定在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,然后文件里面记录的是此 jar 包提供的具体实现类的全限定名。
所以就可以通过接口找到对应的文件,获取具体的实现类然后加载即可,做到了灵活替换具体的实现类。
为什么 Dubbo 不用 JDK 的 SPI,而是要自己实现
因为 Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费。
因此 Dubbo 就自己实现了一个 SPI,给每个实现类配了个名字,通过名字去文件里面找到对应的实现类全限定名然后加载实例化,按需加载。