首先,整理笔记之前,特此感谢一下Dubbo的官方文档,因为里面好多内容都是和官方文档离不开的。
PART1:Dubbo概念篇
- Dubbo是什么?RPC又是什么?~RPC基础一览
- Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案(
说白了Dubbo就是人家大公司自己出品的一款高性能、轻量级的开源 Java RPC 框架。 Dubbo 不光可以帮助我们调用远程服务,还提供了一些其他开箱即用的功能比如智能负载均衡
,咱自己写的那个功能都,可能都谈谈不上功能,都是打印个啥出来就完了),以及SOA服务治理方案。- 或者说,Dubbo是一个开源项目Maven&SpringBoot项目,把这个jar包执行起来以及要把zookeeper【zkServer】运行起来,相当于咱们用起来了Dubbo,其中有个配置文件中需要你把register.address改为自己安装的注册中心的地址,在自己项目pom.xml中用的话导入dubbo-spring-boot-starter、zkclient四个依赖。然后在配置文件中把服务名字、哪些服务要被注册等配置好
- 启动好zookeeper和dubbo两个项目【linux中肯定是开启后,放到后台运行咯】,然后咱们每启动一个项目或者叫服务,就会被dubbo的服务注册中心注册,从而进行管理了
- 常见的RPC框架都有哪些?这一篇RPC概念们,一网打尽,ctrl+F搜关键词就好
- 或者说,Dubbo是一个开源项目Maven&SpringBoot项目,把这个jar包执行起来以及要把zookeeper【zkServer】运行起来,相当于咱们用起来了Dubbo,其中有个配置文件中需要你把register.address改为自己安装的注册中心的地址,在自己项目pom.xml中用的话导入dubbo-spring-boot-starter、zkclient四个依赖。然后在配置文件中把服务名字、哪些服务要被注册等配置好
- Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案(
- Dubbo的整体架构:
- Dubbo 的微内核架构:Dubbo 采用 微内核(Microkernel) + 插件(Plugin) 模式,简单来说就是微内核架构。微内核只负责组装插件。
- 微内核架构模式(有时被称为插件架构模式)是
实现基于产品应用程序
的一种自然模式。基于产品的应用程序是已经打包好并且拥有不同版本,可作为第三方插件下载
的。然后,很多公司也在开发、发布自己内部商业应用像有版本号、说明及可加载插件式的应用软件(这也是这种模式的特征)
。微内核系统可让用户添加额外的应用如插件到核心应用,继而提供了可扩展性和功能分离的用法。【常见的一些IDE,都可以看作是基于微内核架构设计的。绝大多数 IDE比如IDEA、VSCode都提供了插件来丰富自己的功能。】 - 微内核架构包含两类组件:核心系统(core system) 和 插件模块(plug-in modules)。
- 核心系统:核心系统提供系统所需核心能力
- 通常情况下,
微核心都会采用 Factory、IoC、OSGi 等方式管理插件生命周期
。
- 通常情况下,
- 插件模块:插件模块可以扩展系统的功能,基于微内核架构的系统,非常易于扩展功能。
- 核心系统:核心系统提供系统所需核心能力
Dubbo基于微内核架构,才使得我们可以随心所欲替换Dubbo的功能点。比如你觉得Dubbo 的序列化模块实现的不满足自己要求,没关系啊!你自己实现一个序列化模块就好了啊
!Dubbo 不想依赖 Spring 等 IoC 容器,也不想自己造一个小的 IoC 容器(过度设计),因此采用了一种最简单的 Factory 方式管理插件 :JDK 标准的 SPI 扩展机制 (java.util.ServiceLoader)【ServiceLoader是一种加载服务实现的工具】
- 想要使用 Java 的 SPI 机制是需要依赖 ServiceLoader 来实现的,ServiceLoader 是 JDK 提供的一个工具类, 位于package java.util;包下。
- 这个类是一个 final 类型的,所以是不可被继承修改,同时它实现了 Iterable 接口。之所以实现了迭代器,是为了方便后续我们能够通过迭代的方式得到对应的服务实现:public final class ServiceLoader
implements Iterable{ xxx…}
- 微内核架构模式(有时被称为插件架构模式)是
- Dubbo架构中角色:
- Registry:服务注册与发现的注册中心。 负责服务地址的注册与查找,相当于目录服务【注册中心会返回服务提供者地址列表给消费者。非必须。】,
服务的 Provider 和 Consumer 只在启动时与注册中心交互
。注册中心通过长连接【长连接与短连接】感知 Provider 的存在【也就是防止你服务提供者突然挂了】,在 Provider 出现宕机的时候,注册中心会立即推送相关事件通知 Consumer
。- Dubbo服务器注册与发现的流程?
- 服务容器Container负责启动,加载,运行服务提供者–>服务提供者Provider在启动时,向注册中心注册自己提供的服务–>服务消费者Consumer在启动时,向注册中心订阅自己所需的服务–>注册中心Registry返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者–>服务消费者Consumer,
从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用
,如果调用失败,再选另一台调用–>服务消费者Consumer和提供者Provider,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心Monitor。- Dubbo内置了三种服务容器:
Dubbo的服务容器只是一个简单的 Main 方法,并加载一个简单的 Spring 容器,用于暴露服务
。- Spring Container
- Jetty Container
- Log4j Container
- Dubbo内置了三种服务容器:
- 服务容器Container负责启动,加载,运行服务提供者–>服务提供者Provider在启动时,向注册中心注册自己提供的服务–>服务消费者Consumer在启动时,向注册中心订阅自己所需的服务–>注册中心Registry返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者–>服务消费者Consumer,
- Dubbo的注册中心有哪些:Zookeeper、Redis、Multicast、Simple 等都可以作为Dubbo的注册中心。
官方推荐用Zookeeper作为注册中心
(也可以用Redis、Nacos等作为注册中心,得先安装并配置,然后启动、停止、查看状态等)。
- Multicast 注册中心:Multicast 注册中心不需要任何中心节点,只要广播地址,就能进行服务注册和发现,基于网络中组播传输实现。
- Zookeeper 注册中心:
基于分布式协调系统 Zookeeper 实现,采用 Zookeeper 的 watch 机制实现数据变更
。推荐使用 Zookeeper 作为注册中心 - Redis 注册中心:基于 Redis 实现,
采用 key/map 存储,key 存储服务名和类型,map 中 key 存储服务 url,value 服务过期时间
。基于 Redis 的发布/订阅模式通知数据变更 - Simple 注册中心。
- 和SSM、SpringBoot框架这些都大差不差,经常会听到,
基于 XML 配置以及基于注解配置
- 基于XML配置:指定注册中心地址(就是前面 ZooKeeper 的地址),这样
Dubbo 才能把暴露的 DemoService 服务注册到 ZooKeeper 中
:
- 基于注解配置:
- 基于XML配置:指定注册中心地址(就是前面 ZooKeeper 的地址),这样
- 注册中心挂了,服务是否可以正常访问?
- 可以,因为dubbo服务消费者在第一次调用时,会将服务提供方地址缓存到本地,以后在调用则不会访问注册中心。当服务提供者地址发生变化时,注册中心会通知服务消费者
- 注册中心和监控中心都宕机的话,服务都会挂掉吗?
- 不会。两者都宕机也不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表。
注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
- 不会。两者都宕机也不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表。
- 服务提供者宕机后,注册中心会做什么?
- 注册中心会立即推送事件通知消费者。
- 服务提供者能实现失效踢出是什么原理:
服务失效踢出基于Zookeeper的临时节点原理
- Zookeeper中节点是有生命周期的,具体的生命周期取决于节点的类型,节点主要分为持久节点(Persistent)和临时节点(Ephemeral)
- Dubbo服务器注册与发现的流程?
- Provider:暴露服务的服务提供者。
在Provider启动的时候
,会向 Registry 进行注册操作,将自己服务的地址和相关配置信息封装成 URL 注册到或者叫添加到 ZooKeeper 中,从而暴露自己的服务, Consumer 也是通过 URL 来确定自己订阅了哪些 Provider 的。
。然后服务消费者通过负载均衡策略选择唯一的一个服务提供者的URL
,从而进行远程服务的调用。
- 用来解决
如何提供服务
、提供的服务名称是什么
、需要接收客户端或者说远程服务调用者发来的什么参数
、需要返回给客户端或者说远程服务调用者什么响应
; - Invoker 是 Dubbo 领域模型中非常重要的一个概念【假如我们需要调用一个远程方法,我们需要动态代理来屏蔽远程调用的细节吧!我们屏蔽掉的这些细节就依赖对应的 Invoker 实现, Invoker 实现了真正的远程服务调用。】,
Invoker 就是 Dubbo 对远程调用的抽象
。
- 按照 Dubbo 官方的话来说,Invoker 分为
- 服务提供 Invoker
- 服务消费 Invoker
- 按照 Dubbo 官方的话来说,Invoker 分为
- 和SSM、SpringBoot框架这些都大差不差,经常会听到,
基于 XML 配置以及基于注解配置
- 基于XML配置:当然,废话就不说了,pom.xml以及 dubbo-provider.xml 配置文件这些你肯定得有,别学了半天这都忘了
- 基于注解配置:
- 基于XML配置:当然,废话就不说了,pom.xml以及 dubbo-provider.xml 配置文件这些你肯定得有,别学了半天这都忘了
- 用来解决
- Consumer:调用远程服务的服务消费者。
在Consumer启动的时候,会向 Registry 注册中心进行订阅操作并监听自己关注的Provider
。订阅操作会从 ZooKeeper 中获取
Provider 注册的URL
,并在 ZooKeeper 中添加相应的监听器
。获取到 Provider URL 之后,Consumer 会根据负载均衡算法从多个 Provider 中选择一个 Provider 并与其建立连接
,最后发起对 Provider 的 RPC 调用。 如果 Provider URL 发生变更,Consumer 将会通过
之前订阅过程中在注册中心添加的监听器
,获取到最新的 Provider URL 信息
,进行相应的调整,比如断开与宕机 Provider 的连接,并与新的 Provider 建立连接。
- 和SSM、SpringBoot框架这些都大差不差,经常会听到,
基于 XML 配置以及基于注解配置
- 基于XML配置:当然,废话就不说了,pom.xml以及dubbo-consumer.xml 配置文件这些你肯定得有,别学了半天这都忘了
- 基于注解配置:
- 基于XML配置:当然,废话就不说了,pom.xml以及dubbo-consumer.xml 配置文件这些你肯定得有,别学了半天这都忘了
- 如何使用服务、使用的服务名称是什么、需要传入什么参数、会得到什么响应。
- Consumer 与 Provider 建立的是长连接,且 Consumer 会缓存 Provider 信息,
所以一旦连接建立【建立连接后人家双方可以互相找到对方,那要你个中介有啥用】,即使注册中心宕机
,也不会影响已运行的 Provider 和 Consumer。
- 和SSM、SpringBoot框架这些都大差不差,经常会听到,
- Monitor:监控中心。
用于统计服务的调用次数和调用时间
。Provider 和 Consumer 在运行过程中,会在内存中统计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
。监控中心在上面的架构图中并不是必要角色
,监控中心宕机不会影响 Provider、Consumer 以及 Registry 的功能,只会丢失监控数据而已
。- DubboMonitor 实现原理?
- Consumer 端在发起调用之前会先走 filter 链;provider 端在接收到请求时也是先走 filter 链,然后才进行真正的业务逻辑处理。默认情况下,
在 consumer 和 provider 的 filter 链中都会有 Monitorfilter
。
- Consumer 端在发起调用之前会先走 filter 链;provider 端在接收到请求时也是先走 filter 链,然后才进行真正的业务逻辑处理。默认情况下,
- DubboMonitor 实现原理?
- Container: 服务运行容器。
负责加载、运行服务提供者
。必须有。
- Registry:服务注册与发现的注册中心。 负责服务地址的注册与查找,相当于目录服务【注册中心会返回服务提供者地址列表给消费者。非必须。】,
- 运行过程补充:
- 0.Provider要运行在一个容器里面(比如Tomcat),然后启动这个提供者
- 1.提供者启动起来之后,这个提供者就会将自己的信息(服务调用的IP、端口、服务发布的URL路径等,总的来说就是
被消费者调用的服务的地址注册到注册中心
)注册到Register(也就是告诉注册中心,我有一个服务,别人可以来调用我了
) - 2.消费者去向注册中心要服务提供者的信息,进而去调用服务
- Dubbo 的微内核架构:Dubbo 采用 微内核(Microkernel) + 插件(Plugin) 模式,简单来说就是微内核架构。微内核只负责组装插件。
Dubbo的总体的调用过程
:Dubbo 的工作原理
- config 配置层:Dubbo相关的配置。支持代码配置,同时也支持基于 Spring 来做配置,以 ServiceConfig, ReferenceConfig 为中心
- proxy 服务代理层:
调用远程方法像调用本地的方法一样简单的一个关键
,真实调用过程依赖代理类,以 ServiceProxy 为中心。 - registry 注册中心层:封装服务地址的注册与发现。
- cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心
- monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心。
- protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心。
- exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心。
- transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心。
- serialize 数据序列化层 :对需要在网络传输的数据进行序列化
- 服务暴露的流程是怎么样的:
- 服务引用的流程是怎么样的:
- Dubbo的分层:
- Dubbo的
三大核心功能
(Dubbo的三大核心能力
)主要包含:或者说Dubbo的作用:- 远程通讯(
面向接口的远程方法调用
):dubbo-remoting模块, 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。 - 集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
- 集群容错:
Dubbo中提供了很多集群容错模式或者集群容错方式,或者说可靠、智能的容错和负载均衡【以下几种集群容错措施可以根据实际的业务场景进行配置选择,而且 Dubbo 给我们提供了 Cluster 扩展接口,我们可以自己定制集群的容错模式。】
。集群容错是指服务消费者调用服务提供者集群时发生异常时的处理方案。以 Dubbo 框架为例,提供了六种内置的集群容错措施。- Failover Cluster:失败重试,失败自动切换。
Failover 是 Dubbo 默认的集群容错措施
。当出现调用失败时,会重新尝试调用其他服务节点,重试其它服务器,默认重试2次,使用retries配置。一般用于读操作,但重试会带来更长延迟。- 对于幂等性操作我们可以选择 Failover 策略,但是重试的副作用是如果服务提供者出现问题可能会产生大量的重试请求。
- dubbo的默认容错方案,当调用失败时自动切换到其他可用的节点,具体的重试次数和间隔时间可用通过引用服务的时候配置,默认重试次数为1是只调用一次。
- Failfast Cluster :快速失败,服务消费者只发起一次调用,如果出现失败的情况则立刻报错,不进行任何重试。。通常用于非幂等性的写操作,比如新增记录。
- 只会调用一次,失败后立刻抛出异常
- Failfast 非常适合非幂等性操作,但是Failfast 的缺点就是需要服务消费者自己控制重试逻辑。
- Failsafe Cluster :失败安全,Failsafe 策略在出现异常时,直接忽略。通常用于写入审计日志等操作。
- 调用出现异常,记录日志不抛出,返回空结果【Failsafe 策略适合执行非核心的操作,如监控日志记录。】
- Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。服务消费者调用失败后,Dubbo 会记录此次失败请求到队列中,然后定时重新发送该请求。
- 在调用失败,记录日志和调用信息,然后返回空结果给consumer,并且通过定时任务每隔5秒对失败的调用进行重试【Failback 策略适用于实时性不高的场景,如消息推送。】
- Forking Cluster :服务调用者并行调用多个服务提供者节点,只要有一个调用成功就返回结果。
- 通常用于实时性要求较高的操作,但需要浪费更多服务资源。可通过 forks=”2″ 来设置最大并行数。而且可以降低 TP999 指标,但是需要牺牲一定的服务器资源。
- 通过线程池创建多个线程,并发调用多个provider,结果保存到阻塞队列,只要有一个provider成功返回了结果,就会立刻返回结果
- Broadcast Cluster :广播调用所有提供者,广播模式,Broadcast 策略会广播所有的服务提供者,逐个调用,任意一台失败则等待广播最后完成之后抛出。
- 通常用于更新服务提供方的本地资源状态或者说通常用于通知所有提供者更新缓存或日志等本地资源信息。逐个调用每个provider,如果其中一台报错,在循环调用结束后,抛出异常。
- Failover Cluster:失败重试,失败自动切换。
- 集群容错:
- 自动发现:
基于注册中心目录服务,使服务消费方能动态的查找服务提供方
(服务自动注册和发现能力),使地址透明,使服务提供方可以平滑增加或减少机器。- Dubbo服务之间的调用是阻塞的吗?
- 默认是同步等待结果阻塞的,支持
异步调用
。Dubbo是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。
- 默认是同步等待结果阻塞的,支持
- Dubbo服务之间的调用是阻塞的吗?
- 远程通讯(
- Dubbo 支持哪些协议,每种协议的应用场景和优缺点是啥:
- dubbo支持的协议:
- Dubbo协议:Dubbo默认使用Dubbo协议。
- dubbo 单一长连接和 NIO 异步通讯,适合大并发小数据量的服务调用,以及服务消费者远大于提供者的情况,以及消费者远大于提供者。传输协议 TCP,异步,Hessian二进制序列化
- 缺点是不适合传送大数据包的服务
- rmi:采用 JDK 标准的 rmi 协议实现,传输参数和返回参数对象需要实现Serializable 接口,使用 java 标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议 TCP。
- 多个短连接,TCP 协议传输,同步传输,适用常规的远程服务调用和 rmi 互 操作。
- 缺点:在依赖低版本的 Common-Collections 包,java 序列化存在安全漏洞
- webservice:基于 WebService 的远程调用协议(Apache CXF的frontend-simple和transports-http)实现,提供和原生 WebService 的互操作。多个短连接,基于 HTTP 传输,同步传输,适用系统集成和跨语言调用;
http
:基于 Http 表单提交的远程调用协议,使用 Spring 的 HttpInvoke 实现
。多个短连接,传输协议 HTTP,对传输数据包不限,传入参数大小混合,提供者个数多于消 费者,- 缺点:不支持传文件,需要给应用程序和浏览器 JS 调用
- hessian:集成 Hessian 服务,基于 HTTP 通讯,采用 Servlet 暴露服务,Dubbo 内嵌 Jetty 作为服务器时默认实现,提供与 Hession 服务互操作,通讯效率高于WebService和Java自带的序列化。
- 适用于传输大数据包(可传文件),提供者比消费者个数多,提供者压力较大
- 缺点是参数及返回值需实现Serializable接口,自定义实现List、Map、Number、Date、Calendar等接口
- thrift协议:对thrift原生协议的扩展添加了额外的头信息。使用较少,不支持传null值
- memcache:基于 memcached 实现的 RPC 协议
- redis:基于 redis 实现的 RPC 协议
- Dubbo协议:Dubbo默认使用Dubbo协议。
- dubbo支持的协议:
- Dubbo SPI机制:SPI(Service Provider Interface),是一种服务发现机制【
Dubbo SPI可以帮助我们动态寻找服务/功能(比如负载均衡策略)的实现
。】- SPI 的具体原理:
我们将接口的实现类放在配置文件中,我们在程序运行过程中或者在服务加载的时候读取配置文件,通过反射加载实现类。这样,我们可以在运行的时候,动态替换接口的实现类
。和 IoC 的解耦思想是类似的。- 【说白了就是当服务的提供者提供了一种接口的实现之后,需要在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,此文件记录了该 jar 包提供的服务接口的具体实现类。
当某个应用引入了该 jar 包且需要使用该服务时,JDK SPI 机制就可以通过查找这个 jar 包的 META-INF/services/ 中的配置文件来获得具体的实现类名【SPI 机制的具体实现本质上还是通过反射完成的。即:我们按照规定将要暴露对外使用的具体实现类在 META-INF/services/ 文件下声明。】,进行实现类的加载和实例化,最终使用该实现类完成业务功能。
】 - Java的SPI 即 Service Provider Interface:
专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口
。
- SPI 将
服务接口和具体的服务实现分离
开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。 - 很多框架都使用了 Java 的 SPI 机制,比如:Spring 框架、数据库加载驱动、日志接口、以及 Dubbo 的扩展实现等等。
- SPI 将
- 【说白了就是当服务的提供者提供了一种接口的实现之后,需要在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,此文件记录了该 jar 包提供的服务接口的具体实现类。
- Dubbo 中某个接口被 @SPI注解修饰时,就表示该接口是扩展接口,@SPI 注解的 value 值指定了默认的扩展名称。在通过 Dubbo SPI 加载 Protocol 接口实现时,如果没有明确指定扩展名,则默认会将 @SPI 注解的 value 值作为扩展名,即加载 dubbo 这个扩展名对应的 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类
我们如何扩展 Dubbo 中的默认实现呢
?比如说我们想要实现自己的负载均衡策略(咱们自己的负载均衡策略就是DemoByHuLoadBalance类中的逻辑呗),我们创建对应的实现类 DemoByHuLoadBalance 实现 LoadBalance 接口或者 AbstractLoadBalance 类
。package com.xxx; import org.apache.dubbo.rpc.cluster.LoadBalance; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.RpcException; public class DemoByHuLoadBalance implements LoadBalance { public <T> Invoker<T> select(List<Invoker<T>> invokers, Invocation invocation) throws RpcException { // ... } }
- 然后,我们将这个实现类的路径写入到resources 目录下的 META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance文件中即可。
src |-main |-java |-com |-xxx |-XxxLoadBalance.java (实现LoadBalance接口) |-resources |-META-INF |-dubbo |-org.apache.dubbo.rpc.cluster.LoadBalance (纯文本文件,内容为:xxx=com.xxx.DemoByHuLoadBalance)
Dubbo的SPI
其实是对java的SPI进行了一种增强
,可以按需加载实现类之外,增加了 IOC 和 AOP 的特性,还有自适应扩展机制
。Dubbo的SPi和JAVA的SPI有什么区别
?- Java SPI:
- Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,那
我要是只用得上其中一个,是不是实例化全部就显得有点浪费
。- Java 中的 SPI 机制就是**
在每次类加载的时候会先去找到 class 相对目录下的 META-INF 文件夹下的 services 文件夹下的文件,将这个文件夹下面的所有文件先加载到内存中,然后根据这些文件的文件名和里面的文件内容找到相应接口的具体实现类
**,找到实现类后就可以通过反射去生成对应的对象,保存在一个 list 列表里面,所以可以通过迭代或者遍历的方式拿到对应的实例对象,生成不同的实现。- 文件名一定要是接口的全类名,然后里面的内容一定要是实现类的全类名,实现类可以有过个,直接换行就好了,多个实现类的时候,会一个一个的迭代加载。
- Java 中的 SPI 机制就是**
- JDK SPI 在 JDBC 中的应用: JDBC 是如何使用 JDK SPI 机制加载不同数据库厂商的实现类?在 mysql-connector-java-.jar 包中的 META-INF/services 目录下,有一个 java.sql.Driver 文件中只有一行内容:com.mysql.cj.jdbc.Driver 。在使用 mysql-connector-java-.jar 包连接 MySQL 数据库的时候,我们会用到如下语句创建数据库连接:【String url = “jdbc:xxx://xxx:xxx/xxx”; Connection conn = DriverManager.getConnection(url, username, pwd); 】DriverManager 是 JDK 提供的数据库驱动管理器,在调用 getConnection() 方法的时候,DriverManager 类会被 Java 虚拟机加载、解析并触发 static 代码块的执行;在DriverManager的内部的一个静态代码块中调用的 loadInitialDrivers() 方法中通过 JDK SPI 扫描 Classpath 下 java.sql.Driver 接口实现类并实例化。在 MySQL 提供的 com.mysql.cj.jdbc.Driver 实现类中,同样有一段 static 静态代码块,这段代码会创建一个 com.mysql.cj.jdbc.Driver 对象并注册到 DriverManager.registeredDrivers 集合中(CopyOnWriteArrayList 类型)在 getConnection() 方法中,DriverManager 从该 registeredDrivers 集合中获取对应的 Driver 对象创建 Connection
- Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,那
- Dubbo Spi:JDK SPI 在查找扩展实现类的过程中,
需要遍历 SPI 配置文件中定义的所有实现类
,该过程中会将这些实现类全部实例化
。如果 SPI 配置文件中定义了多个实现类,而我们只需要使用其中一个实现类时,就会生成不必要的对象,不就导致资源的浪费了嘛
。Dubbo SPI 不仅解决了上述资源浪费的问题,还对 SPI 配置文件扩展和修改
。- 1.对 Dubbo 进行扩展,不需要改动 Dubbo 的源码
- 2.延迟加载,可以一次只加载自己想要加载的扩展实现。
- 3.增加了对扩展点 IOC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
- 4.Dubbo 的扩展机制能很好的支持第三方 IoC 容器,默认支持 Spring Bean。
- 5.
Dubbo 将 SPI 配置文件改成了 KV 格式
- 其中 key 被称为扩展名(也就是 ExtensionName),当我们在为一个接口查找具体实现类时,可以指定扩展名来选择相应的扩展实现。然后找到之后只实例化这一个扩展实现即可,无须实例化 SPI 配置文件中的其他扩展实现类。
- 使用 KV 格式的 SPI 配置文件的另一个好处是:让我们更容易定位到问题。假设我们使用的一个扩展实现类所在的 jar 包没有引入到项目中,那么 Dubbo SPI 在抛出异常的时候,会携带该扩展名信息,而不是简单地提示扩展实现类无法加载。这些更加准确的异常信息降低了排查问题的难度,提高了排查问题的效率。
- Java SPI:
- 扩展点与扩展点实现:
- 扩展点:通过 SPI 机制查找并加载实现的接口(又称“扩展接口”)
- 扩展点实现:实现了扩展接口的实现类。
- SPI在dubbo应用很多,包括协议扩展、集群扩展、路由扩展、序列化扩展等等
- Dubbo对于SPI相关的文件目录的配置
按照 SPI 配置文件的用途
分为了三类目录:- 1.META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来
兼容 Java SPI
。 - 2.META-INF/dubbo/ 目录:该目录存放
用户自定义
的 SPI 配置文件。 - 3.META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部
使用的 SPI 配置文件
。
- 1.META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来
- SPI 的具体原理:
- 序列化,dubbo把序列化已经封装好了,
只需要所有要传输的实体类或者说所有的POJO都要implements Serializable接口
就行了- Dubbo 支持哪些序列化方式:
- Dubbo 支持多种序列化方式:JDK自带的序列化、hessian2、JSON、Kryo、FST、Protostuff,ProtoBuf等等。
Dubbo 默认使用的序列化方式是 hessian2
。- 一般我们不会直接使用 JDK 自带的序列化方式。主要原因有两个:
- 不支持跨语言调用 :
如果调用的是其他语言开发的服务的时候就不支持了
。 - 性能差 :相比于其他序列化框架性能更低,
主要原因是 JDK 自带的序列化方式序列化之后的字节数组体积较大,导致传输成本加大
。
- 不支持跨语言调用 :
- JSON 序列化由于性能问题,我们一般也不会考虑使用。
- 像 Protostuff,ProtoBuf、hessian2这些都是跨语言的序列化方式,如果
有跨语言需求
的话可以考虑使用。- Kryo和FST这两种序列化方式是 Dubbo 后来才引入的,性能非常好。不过,这两者都是专门针对 Java 语言的。Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式
- 一般我们不会直接使用 JDK 自带的序列化方式。主要原因有两个:
- Dubbo 支持哪些序列化方式:
- 超时与重试:
- 正常情况下,用户调用RPC远程方法调用框架,过程如下:
- 超时:服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会直等待下去。
dubbo利用超时机制(使用timeout属性配置超时时间,默认值1000, 单位毫秒。)来解决这个问题,设置一个超时时间, 在这个时间段内,无法完成服务访问,则自动断开连接
。
- Dubbo超时设置有两种方式:
- 服务提供者端设置超时时间,在Dubbo的用户文档中,推荐如果能在服务端多配置就尽量多配置,因为服务提供者比消费者更清楚自己提供的服务特性。
- 服务消费者端设置超时时间,如果在消费者端设置了超时时间,以消费者端为主,即优先级更高。因为服务调用方设置超时时间控制性更灵活。如果消费方超时,服务端线程不会定制,会产生警告。
- 重试:设置了超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。如果出现网络抖动,则这一次请求就会失败。设置了超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。如果出现网络抖动,则这一次请求就会失败。
- 比如,Dubbo的配置方式经常用的就两种:
- xml配置方式:<dubbo:reference id=“testService” interface=“xxx.xx.service.x.interfice.TestService” retries=“0”/> 【如果不需要重试次数,我们会加上重试次数为0,因为他默认是有多次的。】
- 使用注解的方式:@Reference(retries =0)
- 比如,Dubbo的配置方式经常用的就两种:
- 正常情况下,用户调用RPC远程方法调用框架,过程如下:
- Dubbo多版本
- 如果Dubbo中provider提供的服务由多个版本怎么办:
可以直接通过Dubbo配置中的version版本来控制多个版本即可
。//老版本 version=1.0.0, 新版本version=1.0.1 <dubbo:service interface="com.xxxx.rent.service.IDemoService" ref="iDemoServiceFirst" version="1.0.0"/> <dubbo:service interface="com.xxxx.rent.service.IDemoService" ref="iDemoServiceSecond" version="1.0.1"/>
- 如果Dubbo中provider提供的服务由多个版本怎么办:
- 负载均衡:
- Dubbo这种RPC框架为咱们提供了四种负载均衡策略实现:
- @Reference(loadbalance = “random”)//远程注入(
有哪些负载均衡策略,也可以回答这四种负载均衡策略
)【负载均衡算法可以是多种多样的,客户端可以记录例如健康状态、连接数、内存、CPU、Load 等更加丰富的信息,根据综合因素进行更好地决策。】 Random
:按权重随机,默认值,按权重设置随机概率- 每个机器都可以设置一个weight权重值:@Service(weight = 100)
- 并且可以按照实际情况调整每个机器的weight权重值
- 每个机器都可以设置一个weight权重值:@Service(weight = 100)
Round-Robin
:按权重轮询【Round-Robin 是最简单有效的负载均衡策略,并没有考虑服务端节点的实际负载水平,而是依次轮询服务端节点
】
- Weighted Round-Robin 权重轮询。对不同负载水平的服务端节点增加权重系数,这样可以通过权重系数降低性能较差或者配置较低的节点流量。权重系数可以根据服务端负载水平实时进行调整,使集群达到相对均衡的状态
LeastActive
:最少活跃调用数(最后一次处理请求所花的时间。客户端根据服务端节点当前的连接数进行负载均衡,客户端会选择连接数最少的一台服务器进行调用
),相同活跃数的随机
- Least Connections 策略只是服务端其中一种维度,我们可以演化出最少请求数、CPU 利用率最低等其他维度的负载均衡方案
ConsistentHash
:一致性Hash,相同参数的请求总是发到同一提供者
。目前主流推荐的负载均衡策略,在服务端节点扩容或者下线时,尽可能保证客户端请求还是固定分配到同一台服务器节点。Consistent Hash 算法是采用哈希环来实现的,通过 Hash 函数将对象和服务器节点放置在哈希环上,一般来说服务器可以选择 IP + Port 进行 Hash,然后为对象选择对应的服务器节点,在哈希环中顺时针查找距离对象 Hash 值最近的服务器节点。
- @Reference(loadbalance = “random”)//远程注入(
- 【另外,官方文档对负载均衡这部分的介绍非常详细】
- Dubbo负载均衡的作用?
- 将负载均衡功能实现在rpc客户端侧,以便能够随时适应外部的环境变化,更好地发挥硬件作用。而且客户端的负载均衡天然地就避免了单点问题。定制化的自有定制化的优势和劣势。
负载均衡可以从配置文件中指定,也可以在管理后台进行配置修改
。事实上,它支持 服务端服务/方法级别、客户端服务/方法级别 的负载均衡配置。
- 将负载均衡功能实现在rpc客户端侧,以便能够随时适应外部的环境变化,更好地发挥硬件作用。而且客户端的负载均衡天然地就避免了单点问题。定制化的自有定制化的优势和劣势。
- 负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动)的工作负载分布。
负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载【其实就是说我们的系统中的某个服务的访问量特别大,我们将这个服务部署在了多台服务器上,当客户端发起请求的时候,多台服务器都可以处理这个请求。负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题】
。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件。 - Dubbo 提供的负载均衡策略:在集群负载均衡时,
Dubbo 提供了多种均衡策略【在 Dubbo 中,所有负载均衡实现类均继承自 AbstractLoadBalance,该类实现了 LoadBalance 接口,并封装了一些公共的逻辑。】,默认为 random 随机调用
。我们还可以自行扩展负载均衡策略(参考Dubbo SPI机制)
。public abstract class AbstractLoadBalance implements LoadBalance { static int calculateWarmupWeight(int uptime, int warmup, int weight) { } @Override public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) { } protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation); int getWeight(Invoker<?> invoker, Invocation invocation) { } }
- RandomLoadBalance:
随机负载均衡(对加权随机算法的实现)。这是Dubbo默认采用的一种负载均衡策略
。- RandomLoadBalance 的源码:
public class RandomLoadBalance extends AbstractLoadBalance { public static final String NAME = "random"; @Override protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) { int length = invokers.size(); boolean sameWeight = true; int[] weights = new int[length]; int totalWeight = 0; // 下面这个for循环的主要作用就是计算所有该服务的提供者的权重之和 totalWeight(), // 除此之外,还会检测每个服务提供者的权重是否相同 for (int i = 0; i < length; i++) { int weight = getWeight(invokers.get(i), invocation); totalWeight += weight; weights[i] = totalWeight; if (sameWeight && totalWeight != weight * (i + 1)) { sameWeight = false; } } if (totalWeight > 0 && !sameWeight) { // 随机生成一个 [0, totalWeight) 区间内的数字 int offset = ThreadLocalRandom.current().nextInt(totalWeight); // 判断会落在哪个服务提供者的区间 for (int i = 0; i < length; i++) { if (offset < weights[i]) { return invokers.get(i); } } return invokers.get(ThreadLocalRandom.current().nextInt(length)); } }
- RandomLoadBalance 的源码:
- LeastActiveLoadBalance:
最小活跃数负载均衡,相同活跃数的随机。活跃数指调用前后计数差
【使慢的 Provider 收到更少请求,因为越慢的 Provider 的调用前后计数差会越大。】。初始状态下所有服务提供者的活跃数均为 0(每个服务提供者的中特定方法或者叫服务都对应一个活跃数【活跃数是通过 RpcStatus 中的一个 ConcurrentMap 保存的,根据 URL 以及服务提供者被调用的方法的名称,我们便可以获取到对应的活跃数。也就是说服务提供者中的每一个方法的活跃数都是互相独立的。】,每收到一个请求后,对应的服务提供者的活跃数 +1,当这个请求处理完之后,活跃数 -1
。)- 【
因此,Dubbo 就认为谁的活跃数越少,谁的处理速度就越快,性能也越好,这样的话,我就优先把请求给活跃数少的服务提供者处理
。】public class LeastActiveLoadBalance extends AbstractLoadBalance { public static final String NAME = "leastactive"; @Override protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) { int length = invokers.size(); int leastActive = -1; int leastCount = 0; int[] leastIndexes = new int[length]; int[] weights = new int[length]; int totalWeight = 0; int firstWeight = 0; boolean sameWeight = true; // 这个 for 循环的主要作用是遍历 invokers 列表,找出活跃数最小的 Invoker // 如果有多个 Invoker 具有相同的最小活跃数,还会记录下这些 Invoker 在 invokers 集合中的下标,并累加它们的权重,比较它们的权重值是否相等 for (int i = 0; i < length; i++) { Invoker<T> invoker = invokers.get(i); // 获取 invoker 对应的活跃(active)数 int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); int afterWarmup = getWeight(invoker, invocation); weights[i] = afterWarmup; if (leastActive == -1 || active < leastActive) { leastActive = active; leastCount = 1; leastIndexes[0] = i; totalWeight = afterWarmup; firstWeight = afterWarmup; sameWeight = true; } else if (active == leastActive) { leastIndexes[leastCount++] = i; totalWeight += afterWarmup; if (sameWeight && afterWarmup != firstWeight) { sameWeight = false; } } } // 如果只有一个 Invoker 具有最小的活跃数,此时直接返回该 Invoker 即可 if (leastCount == 1) { return invokers.get(leastIndexes[0]); } // 如果有多个 Invoker 具有相同的最小活跃数,但它们之间的权重不同 // 这里的处理方式就和 RandomLoadBalance 一致了 if (!sameWeight && totalWeight > 0) { int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight); for (int i = 0; i < leastCount; i++) { int leastIndex = leastIndexes[i]; offsetWeight -= weights[leastIndex]; if (offsetWeight < 0) { return invokers.get(leastIndex); } } } return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]); } } public class RpcStatus { private static final ConcurrentMap<String, ConcurrentMap<String, RpcStatus>> METHOD_STATISTICS = new ConcurrentHashMap<String, ConcurrentMap<String, RpcStatus>>(); public static RpcStatus getStatus(URL url, String methodName) { String uri = url.toIdentityString(); ConcurrentMap<String, RpcStatus> map = METHOD_STATISTICS.computeIfAbsent(uri, k -> new ConcurrentHashMap<>()); return map.computeIfAbsent(methodName, k -> new RpcStatus()); } public int getActive() { return active.get(); } }
- 如果有多个服务提供者的活跃数相等怎么办?那就再走一遍 RandomLoadBalance 。
- 【
- ConsistentHashLoadBalance【一致性Hash负载均衡策略】:在分库分表、各种集群中就经常使用这个负载均衡策略。
ConsistentHashLoadBalance 中没有权重的概念,具体是哪个服务提供者处理请求是由你的请求的参数决定的,也就是说相同参数的请求总是发到同一个服务提供者
。官方详细的源码分析- 前情提要:
哈希算法最简单的做法就是进行取模运算,比如分布式系统中有 3 个节点,基于 hash(key) % 3 公式对数据进行了映射
。如果客户端要获取指定 key 的数据,通过下面的公式可以定位节点:hash(key) % 3,如果经过上面这个公式计算后得到的值是 0,就说明该 key 需要去第一个节点获取
。
但是哈希算法有一个很致命的问题,如果节点数量发生了变化,这样会导致大部分映射关系改变,也就是在对系统做扩容或者缩容时,必须迁移改变了映射关系的数据,否则会出现查询不到数据的问题
。【如果我们对分布式系统进行缩容,比如移除一个节点,也会因为取模哈希函数中基数的变化,可能出现查询不到数据的问题。】- 要解决这个问题的办法,
就需要我们进行迁移数据,比如节点的数量从 3 变化为 4 时,要基于新的计算公式 hash(key) % 4 ,重新对数据和节点做映射
。假设总数据条数为 M,哈希算法在面对节点数量变化时,最坏情况下所有数据都需要迁移,所以它的数据迁移规模是 O(M),这样数据的迁移成本太高了。所以,我们应该要重新想一个新的算法,来避免分布式系统在扩容或者缩容时,发生过多的数据迁移。所以一致性哈希算法应运而生,一致性哈希算法就很好地解决了分布式系统在扩容或者缩容时,发生过多的数据迁移的问题
。
- 要解决这个问题的办法,
- 一致哈希算法也用了取模运算,但与哈希算法不同的是,哈希算法是对节点的数量进行取模运算,而
一致哈希算法是对 2^32 进行取模运算,是一个固定的值。可以把一致哈希算法是对 2^32 进行取模运算的结果值组织成一个圆环,就像钟表一样,钟表的圆可以理解成由 60 个点组成的圆,而此处我们把这个圆想象成由 2^32 个点组成的圆,这个圆环被称为哈希环
。【在一致哈希算法中,如果增加或者移除一个节点,仅影响该节点在哈希环上顺时针相邻的后继节点,其它数据也不会受到影响
。】
- 但是一致性哈希算法并不保证节点能够在哈希环上分布均匀【
一致性哈希算法虽然减少了数据迁移量,但是存在节点分布不均匀的问题
。】,这样就会带来一个问题,会有大量的请求集中在一个节点上。在这种节点分布不均匀的情况下,进行容灾与扩容时,哈希环上的相邻节点容易受到过大影响,容易发生雪崩式的连锁反应。所以,往下看
- 但是一致性哈希算法并不保证节点能够在哈希环上分布均匀【
- Dubbo 为了避免数据倾斜问题(节点不够分散,大量请求落到同一节点),还引入了虚拟节点的概念。通过虚拟节点可以让节点更加分散,有效均衡各个节点的请求量。【
因为实际中我们没有那么多节点。所以这个时候我们就加入虚拟节点,也就是对一个真实节点做多个副本
。】
- 虚拟节点
除了会提高节点的均衡度,还会提高系统的稳定性
。当节点变化时,会有不同的节点共同分担系统的变化,因此稳定性更高。比如,当某个节点被移除时,对应该节点的多个虚拟节点均会移除,而这些虚拟节点按顺时针方向的下一个虚拟节点,可能会对应不同的真实节点,即这些不同的真实节点共同分担了节点变化导致的压力。而且,有了虚拟节点后,还可以为硬件配置更好的节点增加权重,比如对权重更高的节点增加更多的虚拟机节点即可。因此,带虚拟节点的一致性哈希方法不仅适合硬件配置不同的节点的场景,而且适合节点规模会发生变化的场景
- 虚拟节点
- 前情提要:
- RoundRobinLoadBalance:
加权轮询负载均衡。轮询就是把请求依次分配给每个服务提供者。加权轮询就是在轮询的基础上,让更多的请求落到权重更大的服务提供者上
。比如假如有两个提供相同服务的服务器 S1,S2,S1的权重为7,S2的权重为3。如果我们有 10 次请求,那么 7 次会被 S1处理,3次被 S2处理
。但是,如果是 RandomLoadBalance 的话,很可能存在10次请求有9次都被 S1 处理的情况(概率性问题)。Dubbo 中的 RoundRobinLoadBalance 的代码实现被修改重建了好几次,Dubbo-2.6.5 版本的 RoundRobinLoadBalance 为平滑加权轮询算法- RoundRobinLoadBalance具体的实现原理:假如有两个提供相同服务的服务器 S1,S2,S1的权重为7,S2的权重为3。我们把这些权重值分布在坐标区间会得到:S1->[0, 7) ,S2->[7, 10)。
我们生成[0, 10) 之间的随机数,不同请求对应不同的随机数,随机数落到对应的区间,我们就选择对应的服务器来处理请求
。
- RoundRobinLoadBalance具体的实现原理:假如有两个提供相同服务的服务器 S1,S2,S1的权重为7,S2的权重为3。我们把这些权重值分布在坐标区间会得到:S1->[0, 7) ,S2->[7, 10)。
- RandomLoadBalance:
- Dubbo这种RPC框架为咱们提供了四种负载均衡策略实现:
- 服务降级:服务的性能已达到最大,比如CPU和内存利用率已经到百分之九十多,机器就快崩了,那么现在咱们就得把一些不太重要的服务关掉或者说释放掉,以保证核心的业务能正常运行(钱都付不了了,你日志服务有啥用)
- Dubbo中有两种服务降级方式://远程注入:@Reference(mock = “fail:return null”)//不再调用userService的服务
- Dubbo 的配置总线: Dubbo 中的 URL,很多人称之为“配置总线”,也有人称之为“统一配置模型”。URL 在 Dubbo 中被当作是“公共的契约”
- URL:说白了服务器上的每个资源都有
统一的且在网上唯一的地址
,该地址就叫 URL(Uniform Resource Locator,统一资源定位符),它是互联网的统一资源定位标志,也就是指网络地址。
- 一个标准的 URL 格式可以包含如下的几个部分:protocol://username:password@host:port/path?key=value&key=value
- protocol:URL 的协议。我们常见的就是 HTTP 协议和 HTTPS 协议,当然,还有其他协议,如 FTP 协议、SMTP 协议等。
- username/password:用户名/密码。 HTTP Basic Authentication 中多会使用在 URL 的协议之后直接携带用户名和密码的方式。
- host/port:主机/端口。
在实践中一般会使用域名,而不是使用具体的 host 和 port
。 - path:请求的路径。
- parameters:参数键值对。
一般在 GET 请求中会将参数放到 URL 中,POST 请求会将参数放到请求体中
。
- 一个标准的 URL 格式可以包含如下的几个部分:protocol://username:password@host:port/path?key=value&key=value
Dubbo 中任意的一个实现都可以抽象为一个 URL
,Dubbo 使用 URL 来统一描述了所有对象和配置信息
,并贯穿在整个 Dubbo 框架之中:
dubbo://172.17.32.91:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&…&…,将Demo Provider注册到Zookeeper上的这条URL信息解析如下:- protocol:dubbo 协议。
- username/password:没有用户名和密码。
- host/port:172.17.32.91:20880。
- path:org.apache.dubbo.demo.DemoService。
- parameters:参数键值对,这里是问号后面的或者说问号和&一块组成的参数串。
- URL:说白了服务器上的每个资源都有
PART2:Dubbo实战篇
这边自己学习这个Dubbo时,也就是先用Spring和SpringMVC搭了个小框架(过程解释的不详细,可以看看blog的spring全家桶中关于Spring和SpringMVC的文章,里面有相关概念以及相关配置的解释),然后写两个类,一个用来模拟服务消费者,一个用来模拟服务提供者【服务提供者Provicer模块和服务消费者Consumer模块(版本二中或者说实际项目中这两个其实是两个项目,分别要部署在两台机器上)
】,然后就是两个版本,从本地类之间的调用这个版本(通过MAVEN依赖、Spring注入等实现两个类的调用)升级到
远程两个类的调用版本(通过Dubbo相关的配置)。
- 其实不管是那种生态或者中间件,涉及到具体的服务端和消费端的编写思路:
- 服务提供者暴露服务或者说提供服务
- 1.在pom.xml中导入依赖
- 2.配置注册中心的地址,以及服务发现名,和要注册的要被扫描的包中的暴露的服务
- 3.在想要被注册的服务上面,增加Dubbo下面的注解:@Service
- 消费者如何消费
- 1.导入依赖
- 2.在配置文件中配置注册中心的地址,可以在任何地方,并配置自己的服务名
- 3.从远程注入服务,用@Reference,这个货跟咱们之前的@Autowired是一个道理
- 服务提供者暴露服务或者说提供服务
- Version01:创建服务提供者Provicer模块和服务消费者Consumer模块(版本二中或者说实际项目中这两个其实是两个项目,分别要部署在两台机器上)。先开发一个不上线的简单的测试版:咱们最开始用Spring+SpringMVC模拟一下这俩货的本地调用,也就是服务提供者和服务消费者,然后也就是用一个service接口和serviceImpl来模拟服务提供者,然后用XxxController来模拟服务消费者,然后不同于咱们之前的是现在这俩不在一个项目中,相当于咱们要在一个项目中调用另一个项目怎么实现呢,不就是把要依赖的那个项目打成一个jar放到欲依赖的项目里面吗,当然啦,咱们web项目吗,所以打jar包这个过程就变成了写maven依赖坐标了。------->在服务提供者模块那里编写一个子实现类来提供服务(提供服务就是子实现类自己肚子里面的方法被调用了呗)-------->在服务消费者中写一个XxxController来远程调用服务提供者的子实现类提供的服务(方法)【之前比如说咱们做项目时,都是通过@Autowired将要被调用的类注入,然后这个被注入的类中的方法(方法指的就是服务)才能被肆意调用,但是这种是本地方法或者服务调用】-------->分别启动上面两个模块,进行测试
- 那这样了你不搞个pom.xml能行?
- 当然你这样肯定会用到spring和springmvc,那那个service子实现类,你不给人家类上写个@Service让spring把这个用来模拟服务提供者的子实现类当作一个bean扫描进spring容器中能行?你写了@Service后你不搞个spring的配置文件,里面头文件下面写上组件扫面把这个子实现类所在包扫描进去能行?另外写XxxController模拟服务消费者时,你用了springmvc,那这里面的监听器、DispatcherServlet你不来个web.xml然后将监听器和DispatcherServler(用来指定加载spring-mvc.xml配置文件,spring-mvc.xml中就是来写一个前后缀,注解驱动以及组件扫描,但是其实配置文件都是互通的,spring-mvc.xml里面有些东西也可以直接写到spring的配置文件中)写到里面能行?然后web.xml中肯定也得把spring的配置文件也得引进来呀
- Version02: 咱们就得把这个单体项目改为分布式或者SOA项目了,
因为之前这个版本两个模拟项目中的service是不能单独启动的(咱们只是用了maven分模块的办法,让这两个模块产生了依赖,从而一个调用另一个),而人家分布式项目中每个单独的项目都是可以单独启动的
- 单独的项目一般是在不同的服务器上启动的。他们之间的互相调用肯定是得通过dubbo这种RPC远程方法调用框架了,本质上就是得通过RPC呀
- 改造版本为分布式的:服务消费者和服务提供者都得改造一下
- 先改造模拟服务提供者的类:
- pom.xml得改一改:
<!--Dubbo的起步依赖,版本2.7之后统-为rg. apache. dubb --> <dependency> <groupId>org.apache.dubbo</ groupId> <artifactId>dubbo</artifactId> <version>${dubbo.version}</version> </ dependency> <!-- ZooKeeper客户端实现--> <dependency> <groupId>org.apache.curator</ groupId> <artifactId>curator-framework</artifactId> <version>${ zookeeper.version}</version> </ dependency> <!--ZooKeeper客户端实现--> <dependency> <groupId>org.apache.curator</ groupId> <artifactId>curator-recipes</artifactId> <version>${zookeeper.version}</vers ion> </dependency>
- 此外,为了单独能启动,
得把之前是jar项目的项目改成war项目,war项目才能单独启动
。并且那你想单独启动你内部肯定得集成tomcat,咱们用springboot时是因为人家springboot帮咱们内部已经集成好了tomcat,所以咱们在pom.xml中没有手动集成tomcat而已
。
- 改代码,
其实就是@Service
(标志这个类是服务层的一个bean,spring的IOC容器你赶紧来,赶紧把我加载进去到你的IOC容器中,你会别人会来调用我的)改一下
,因为我现在这个模拟服务提供者的service类不需要放到spring的IOC容器中了,我们需要把这个服务提供者的IP、端口、访问路径告诉Dubbo,dubbo记录到zookeeper中,然后等待服务消费者来进行调用,还是通过@Service,只不过是另一个包下的
(@Service//将这个类提供的方法(服务)对外发布。将访问的地址,也就是ip,端口,路径注册到dubbo中的注册中心中,由dubbo中的zookeeper记录下来;只不过不能用spring的包扫描了,得用dubbo的包扫描了
)
- 模拟服务提供者的类既然要将服务的访问信息(包括ip、端口、访问url)等记录到dubbo的zookeeper中,
那你怎么找到咱们电脑中安装的zookeeper呢
,这是在spring的配置文件中配置的
- 也得来一个web.xml来加载dubbo的核心配置文件呀,只有加载了配置文件后才能扫描到咱们的dubbo的这些配置呀
- pom.xml得改一改:
- 再改造模拟服务消费者的类:
- 1.咱们是通过Dubbo这个RPC框架进程远程服务(方法)的调用的,所以肯定不能再用maven进行依赖了
- 用@Reference代替@Autowired
- 写配置:
- 1.咱们是通过Dubbo这个RPC框架进程远程服务(方法)的调用的,所以肯定不能再用maven进行依赖了
- 先改造模拟服务提供者的类:
- Version03: Dubbo+Nacos+SpringBoot
- 环境准备:
- 那首先肯定你得有个SpringBoot项目呀,实验室或者手头没有现成的,那就到spring官网下载一个最小例程;
- 然后跟Zookeeper这种中间件一样,咱们得安装启动Nacos【Nacos可以点这里看看官方文档】,本文使用的是nacos1.4.3版本,打开nacos的控制面板
-
Nacos与dubbo整合:
- 开发步骤:
- 为了后续方便使用SpringCloud Alibaba进行开发,首先创建一个pom类型的父项目,
主要用于项目技术栈版本管理
。【总的来说就是:创建一个maven项目,名称为spring-cloud-alibaba-example,去除src文件,修改pom文件】
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache/POM/4.0.0" xmlns:xsi="http://www.w3/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache/POM/4.0.0 http://maven.apache/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>ah.wideth</groupId> <artifactId>spring-cloud-alibaba-example</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <packaging>pom</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR12</spring-cloud.version> <com-alibaba-cloud.version>2.2.7.RELEASE</com-alibaba-cloud.version> </properties> <!--对项目版本进行管理--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${com-alibaba-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
后续创建的项目都放到此目录下,只需要声明groupId和artifactId,会自动引用父项目spring-cloud-alibaba-example的版本
。与其说是父项目,不如说是根项目
: 因为下面每学习一个新的技术,就会新建一个真正的父项目,而在对应的父项目下面又会创建许多的子项目。
- Nacos与Dubbo整合
- 模块:
- 父工程pom文件、
- 创建公共接口模块
- pom文件
- 公共接口模块里面只有一个接口,没有配置文件,打jar包
- 模块:
- 创建服务提供者模块
- pom文件,里面的除了常规的
- application.yml配置文件
- 接口实现类,该类实现了上面我们在公共接口模块创建的接口
- 服务提供者启动类
- pom文件,里面的除了常规的
- 创建服务消费者模块
- pom文件
- application.yml配置文件
- controller,调用公共接口模块创建的接口
- 服务消费者启动类
- pom文件
- 服务调用测试:打开Nacos控制面板查看注册中心中的服务,
启动Nacos,启动服务提供者和服务消费者,调用服务消费者的getInfo方法,服务提供者会返回结果
- 为了后续方便使用SpringCloud Alibaba进行开发,首先创建一个pom类型的父项目,
- 环境准备:
PART3:dubbo-admin:管理平台,图形化的服务管理页面
- 从注册中心中获取到所有的提供者/消费者进行配置管理
- 改dubbo-admin-server的resources底下的application.properties中的注册中心的配置信息
- 路由规则、动态配置、服务降级、访问控制、权重调整、负载均衡等管理功能
- dubbo-admin是一个前后端分离的项目。前端使用yue,后端使用springboot
- 安装dubbo-admin其实就是部署该项目
- dubbo-admin安装完成后的页面
- dubbo-admin安装完成后的页面
- @Reference:【这个注解对应的xml配置形式就是:<dubbo:reference id=“xxxXXX” interface=“…” retries=“0”/>】
- 在采用@Reference注解条件下,比如说咱们采用@Reference注解
配置重试次数
,不配置retries或者配置为0,都会重试两次,只有配置为 -1 或更小,才会不执行重试。采用标签形式:不配置retries会重试两次,配置为0或更小都不会重试。
- 在采用@Reference注解条件下,比如说咱们采用@Reference注解
- Dubbo和 Spring Cloud 有什么区别?
- 最大的区别:
- Dubbo底层是使用Netty这样的NIO框架,是基于TCP协议传输的,配合以Hession序列化完成RPC通信;
- 而SpringCloud是基于Http协议+rest接口调用远程过程的通信,相对来说,Http请求会有更大的报文,占的带宽也会更多。但是REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适,至于注重通信速度还是方便灵活性,具体情况具体考虑。
- 模块区别:
- Dubbo主要分为服务注册中心,服务提供者,服务消费者,还有管控中心;
相比起Dubbo简单的四个模块,SpringCloud则是一个完整的分布式一站式框架,他有着一样的服务注册中心,服务提供者,服务消费者,管控台,断路器,分布式配置服务,消息总线,以及服务追踪等
;- 最大区别就是SpringCloud抛弃了Dubbo的RPC通信【存在代码级别的强依赖,不适合微服务环境下的快速演化特征】,采用基于HTTP的REST方式【牺牲了服务调用的性能,但是不存在代码级别的强依赖,服务提供方跟服务调用方只依靠一纸契约】
- 最大的区别:
巨人的肩膀:
moon聊技术
B站黑马视频
Dubbo官方文档
https://mp.weixin.qq/s/_5YMfQK1tmYbmRMldBPlaQ
javaGuide老师关于RPC的好文章
Github :https://github/apache/incubator-dubbo
官网:https://dubbo.apache/zh/
从 Motan 看 RPC 框架设计:http://kriszhang/motan-rpc-impl/
Motan 中文文档:https://github/weibocom/motan/wiki/zh_overview
Github:https://github/grpc/grpc
官网:https://grpc.io/
官网:https://thrift.apache/
Thrift 简单介绍:https://www.jianshu/p/8f25d057a5a9
更多推荐
java基础巩固-宇宙第一AiYWM:为了维持生计,架构知识+分+微序幕就此拉开之Dubbo(常见的RPC框架们,包含Dubbo内部一些东东+实战,Spring
发布评论