【Dubbo】Dubbo
分布式简介
分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。老式系统(单一应用架构)就是把一个系统,统一放到一个服务器当中。如果说要更新代码的话,每一个服务器上的系统都要重新去部署十分的麻烦。
而分布式系统就是将一个完整的系统拆分成多个不同的服务,然后在将每一个服务单独的放到一个服务器当中。
应用架构及发展演变
ORM:单一应用架构
单一应用架构:一个项目装到一个服务器当中。也可以运行多个服务器,每一个服务器当中都装一个项目。缺点:
- 如果要添加某一个功能的话就要把一个项目重新打包,在分别部署到每一个服务器当中去。
- 如果后期项目越来越大的话单台服务器跑一个项目压力会很大的。会不利于维护,开发和程序的性能。
MVC:垂直应用架构
垂直应用架构:将应用切割成几个互不相干的小应用,在将每个小应用独立放到一个服务器上,如果哪一个应用的访问数量多就多加几台服务器。
RPC:分布式应用架构
分布式应用架构(远程过程调用):当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。
RPC(Remote Procedure Call) 是指远程过程调用,是一种进程间通信方式,它是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
RPC的本质就是向远程目标服务器发出 HTTP 请求,令其解析该请求后执行相应的服务方法
Dubbo一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
RPC工作原理
- Client像调用本地服务似的调用远程服务;
- Client stub(存根)接收到调用后,将方法、参数序列化
- 客户端通过sockets将消息发送到服务端
- Server stub 收到消息后进行解码(将消息对象反序列化)
- Server stub 根据解码结果调用本地的服务
- 本地服务执行(对于服务端来说是本地执行)并将结果返回给Server stub
- Server stub将返回结果打包成消息(将结果消息对象序列化)
- 服务端通过sockets将消息发送到客户端
- Client stub接收到结果消息,并进行解码(将结果消息发序列化)
- 客户端得到最终结果。
RPC 调用分以下两种:
- 同步调用:客户方等待调用执行完成并返回结果。
- 异步调用:客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。若客户方不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。
RPC步骤解析
SOA:流动计算架构
流动计算架构:在分布式应用架构的基础上增加了一个调度、治理中心基于访问压力实时管理集群容量、提高集群的利用率,用于提高机器利用率的资源调度和治理中心(SOA) 是关键 (不浪费计算机资源)
Dubbo 介绍
Dubbo官网: https://dubbo.apache.org/zh/
Dubbo是一款高性能、轻量级的开源Java RPC框架。它是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,服务自动注册和发现。
Dubbo 特性一览
Dubbo 设计架构
该图描述了服务注册中心、服务提供方、服务消费方、服务监控中心之间的调用关系:
- 服务提供者(Provider):暴露服务的服务提供方。服务提供者在启动时完成以下步骤进行服务暴露:
- 本地暴露:创建Netty服务端NettyServer,启动并监听配置文件中指定的Dubbo服务端口20880,等待消费者客户端发送远程调用当前服务的请求;
- 注册服务信息到本地缓存:将服务信息注册到提供者注册表(本地缓存
Map
)中; - 注册服务信息到注册中心:向注册中心注册自己提供的服务信息,例如在ZooKeeper的Dubbo节点下创建提供的服务节点,该节点包含该服务的接口名、Provider服务端(通常为NettyServer)的URL等信息;服务信息将被保存成
Map
结构:服务名 : List<URL>
。
- 服务消费者(Consumer): 调用远程服务的服务消费方。服务消费者在启动时,完成以下步骤进行服务引用:
- 获取注册中心地址并据此创建ZooKeeper注册中心类
registry
; - 根据服务名称从注册中心
registry
订阅服务的URL列表List<URL>
(只订阅当前工程引用的服务,其他服务不订阅); - 获取到服务URL信息后,创建
NettyClient
客户端与其进行通讯,并创建invoker
(包含了远程服务的信息,后续使用其进行远程服务调用); - 将
invoker
保存到消费者的本地缓存Map
中,这样即使ZooKeeper宕机本地工程也能使用缓存中的invoker
调用远程服务。
- 获取注册中心地址并据此创建ZooKeeper注册中心类
- 服务消费者在进行服务调用时将进行以下步骤:
- 使用服务的代理对象调用方法时将逐层调用其内嵌套的多个不同功能的
invoker
,基于软负载均衡算法,从服务URL列表中选择其中的一台提供者进行远程调用; - 多层
invoker
调用后将创建出Netty客户端NettyClient
与服务端NettyServer
进行通讯; - 将要调用的接口名、方法名和方法参数等信息经过编码序列化后发送给
NettyServer
,服务端收到该请求后创建目标服务对应的代理对象执行服务方法,待其执行完毕后再将方法的返回结果发送给客户端; - 远程调用原理:选择其中的某一个URL作为目标服务端
NettyServer
,创建NettyClient
与其进行通讯,将要执行的服务接口名、方法名、方法参数类型列表与方法值列表发送给NettyServer,令其创建代理对象调用该方法,从而实现远程调用目标方法。如果选中的服务器调用失败,再选另一台调用。
- 使用服务的代理对象调用方法时将逐层调用其内嵌套的多个不同功能的
- 注册中心(Registry):
- 注册中心负责保存服务提供者发来的服务信息,同时在消费者发来订阅请求时返回服务提供者地址列表(包含ip/port等信息)给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 当注册中心检测到有Dubbo提供者宕机时,将从节点中删除该提供者信息(超时检测机制),同时通知所有消费者节点信息发生改变,修改消费者本地缓存中的服务数据(使用ZooKeeper里的监听机制,在消费者从注册中心订阅时就绑定监听了注册中心服务节点信息改变事件)
- 监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
- 框架容器(Container):Dubbo框架容器。
图中线条代表含义:
- 紫色虚线代表Dubbo容器启动时执行的步骤,先后为:
0.start
:启动Dubbo容器1.register
:服务提供者在注册中心内注册信息(服务端的ip地址和端口号等信息)2.subscribe
:服务消费者向注册中心订阅所有服务提供者的信息
- 蓝色虚线代表异步执行,当注册中心发现服务提供者发生改变时,会通知服务消费者该变化。服务提供者和服务消费者会定期向监控中心发送数据。
- 红色实线代表服务消费者同步执行服务提供者的方法。
Dubbo详细源码分析见【Dubbo】Dubbo 源码分析。
Dubbo 特性
(1)服务注册中心
相比Hessian类RPC框架,Dubbo有自己的服务中心,写好的服务可以注册到服务中心,客户端从服务中心寻找服务,然后再到相应的服务提供者机器获取服务。通过服务中心可以实现集群、负载均衡、高可用(容错) 等重要功能。
服务中心一般使用ZooKeeper实现,也有Redis和其他一些方式。以使用ZooKeeper作为服务中心为例,服务提供者启动后会在ZooKeeper的Dubbo节点下创建提供的服务节点,包含服务提供者ip、port等信息。服务提供者关闭时会从ZooKeeper中移除对应的服务。
服务使用者会从注册中心ZooKeeper中寻找服务,同一个服务可能会有多个提供者,Dubbo会帮我们找到合适的服务提供者,也就是针对服务提供者的负载均衡。
(2)负载均衡
当同一个服务有多个提供者在提供服务时,客户端如何正确的选择提供者实现负载均衡呢?Dubbo也给我们提供了几种方案:
- random:随机选提供者,并可以给提供者设置权重
- roundrobin:轮询选择提供者
- leastactive:最少活跃调用数,相同活跃数的随机,活跃数:指调用前后计数差。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
- consistenthash:一致性hash,相同参数的请求发到同一台机器上。
(3)简化测试,允许直连提供者
在开发阶段为了方便测试,通常系统客户端能指定调用某个服务提供者,那么可以在引用服务时加一个url参数去指定服务提供者。 配置如下:
1 | <dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890"/> |
(4)服务版本,服务分组(灰度发布)
在Dubbo配置文件中可以通过制定版本实现连接制定提供者,也就是通过服务版本可以控制服务的不兼容升级;当同一个服务有多种实现时,可以使用服务分组进行区分。
Dubbo 和 Spring Cloud 对比
Dubbo 监控配置
配置 ZooKeeper
配置流程见Linux 开发环境配置文档
管理控制台 dubbo-admin 配置
1、下载dubbo-admin
dubbo-admin下载地址 :https://github.com/apache/dubbo-admin/tree/master
2、解压后进入目录修改指定ZooKeeper地址
进入如下地址:dubbo-admin-master/dubbo-admin/src/main/resources/application.properties
将ZooKeeper的监控中心的地址配置为对应端口
1 | # 注册中心的地址 |
配置完毕后,在dubo-zookeeper/dubbo-admin-master/dubbo-admin
文件夹下打包测试下。
1 | mvn clean package |
使用 java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
运行打包好的jar包。启动成功后,在本地的7001端口即可访问到注册中心,账号密码默认均为"root":
此时,ZooKeeper的管理控制台配置完成。
简易监控中心 dubbo-monitor-simple 配置
进入dubbo-monitor-simple
文件,执行mvn package
命令将当前项目打包成jar包。
将 dubbo-monitor-simple-2.0.0-assembly.tar.gz
压缩包解压至当前文件夹,解压后config文件查看properties的配置是否是对应的zookeeper。之后打开解压后的 assembly.bin
文件,start.bat
启动dubbo-monitor-simple
监控中心。
配置完成后,在localhost:8080
,可以看到一个监控中心。
之后在服务提供者和消费者的xml中配置以下内容,再次启动服务提供和消费者启动类。
1 | <!--dubbo-monitor-simple监控中心发现的配置--> |
配置后即可在监控中心观察到服务提供者和消费者信息:
Dubbo 用法
服务提供者模块
创建服务提供者模块,导入如下依赖(非Spring Boot配置):
1 | <!--dubbo--> |
在配置文件中添加Dubbo相关配置provider.xml
:
1 |
|
编写一个ProviderApplication
启动类程序,运行测试配置:
1 | public class MailApplication { |
首先启动zookeeper注册中心的zkServer和zkCli服务。运行 java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
。
之后启动项目,我们可以看到在ZooKeeper中已经发现服务提供者:
服务消费者模块
创建服务消费者模块,同样引入依赖(与上文相同)。创建配置文件consumer.xml
:
1 |
|
把消费者模块中创建的OrderServiceImpl
类中加上注解@Component
以加入到容器中:
1 |
|
编写一个ConsumerApplication
启动类程序,运行测试配置:
1 | public class ConsumerApplication { |
注意:消费者的运行测试需要先启动提供者。启动服务提供者、消费者。及zookeeper的和dubbo-admin,查看监控信息:
Dubbo 和 Spring Boot 整合
Dubbo和Spring Boot整合的三种方式:
- 导入
dubbo-starter
,在application.properties
配置属性,使用 @Service暴露服务,使用 @Reference 引用服务 - 导入
dubbo-starter
,保留Dubbo相关的xml配置文件,使用 @ImportResource 导入Dubbo的xml配置文件 - 导入
dubbo-starter
,创建 @Configuration 配置类,使用 @Bean 将每一个组件手动配置到容器中,让Dubbo来扫描其他的组件
下面介绍第一种方式:
服务提供者模块
导入Dubbo的场景依赖dubbo-spring-boot-starter
:
1 | <dependencies> |
配置 application.properties
:
1 | boot-user-service-provider = |
使用 @DubboService 注解将 user-service-provider
服务提供者模块中的UserServiceImpl暴露
1 | // 将当前服务暴露 |
BootProviderApplication
启动类需要写上 @EnableDubbo 注解以开启基于注解的Dubbo功能:
1 | //开启基于注解的dubbo功能 |
服务消费者模块
导入Dubbo的场景依赖(同上文)。
创建application.properties
配置:
1 | server.port=8081 |
改写配置版中的OrderServiceImpl
,在其内的UserService
上添加 @DubboReference 注解以引用远程提供者服务:
1 |
|
创建OrderController
控制器:
1 |
|
创建BootConsumerApplication
启动类,使用**@EnableDubbo**注解开启Dubbo功能:
1 | //开启基于注解的dubbo功能 |
至此,Duboo的Spring Boot整合配置完成。
Dubbo 配置
配置优先级原则
- JVM 启动
-D 参数
优先,这样可以使用户在部署和启动时进行参数重写,比如在启动时需改变协议的端口。 - XML次之,如果在 XML中有配置,则
dubbo.properties
中的相应配置项无效。 - Properties 最后,相当于缺省值,只有 XML 没有配置时,
dubbo.properties
的相应配置项才会生效,通常用于共享公共配置,比如应用名。
Dubbo推荐在Provider上尽量多配置Consumer端属性:
- 作为服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数,等等
- 在Provider配置后,Consumer不配置则会使用Provider的配置值,即Provider配置可以作为Consumer的缺省值。否则,Consumer会使用Consumer端的全局设置,这对于Provider是不可控的,并且往往是不合理的
配置的覆盖规则:
- 方法级别配置别优于接口级别,即小Scope优先
- Consumer端配置优于Provider配置优于全局配置
- 最后是Dubbo Hard Code的配置值(见配置文档)
启动时检查
Dubbo缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"
。
可以通过 check="false"
关闭检查,比如,测试时有些服务不关心,或者出现了循环依赖,必须有一方先启动。另外,如果 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check
,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false"
,总是会返回引用,当服务恢复时,能自动连上。
以order-service-consumer
消费者为例,在consumer.xml
中添加配置
1 | <!--配置当前消费者的统一规则,当前所有的服务都不启动时检查--> |
添加后,即使服务提供者不启动,启动当前的消费者,也不会出现错误,否则必须先开启服务提供者,才能正常开启服务消费者。
超时和重试次数配置
1 | <!-- 全局超时配置,默认是1000ms --> |
本地存根
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 代理实例,会把 Proxy 通过构造函数传给 Stub,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
过程:
- 本地引用XxxService接口,该接口在本地没有实现类,只在远程Provider模块内有实现类。本地并无法直接调用该接口的实现类方法,只能远程调用。
- 在本地创建XxxService接口的本地存根XxxServiceStub,该类实现了XxxService接口,并在配置文件中添加了相应配置。
- Dubbo将在其构造方法中自动传入XxxServiceProxy代理对象,此时本地存根XxxServiceStub即可使用该代理对象远程调用Provider模块的XxxServiceImpl里的方法。
使用本地存根(stub)技术可以在本模块中获取到远程模块的某个实现类对象,从而在本地调用该远程模块实现类的对象,进行一定的操作。
添加哪个类为存根Stub:
1 | <dubbo:reference interface="com.zhao.gmail.service.UserService" id="userService" stub="com.zhao.gmall.service.impl.UserServiceStub"></dubbo:reference> |
该对象的构造函数需要带上UserService远程接口的Proxy代理对象,由Dubbo自动传入
1 | class UserServiceStub implements UserService { |
多版本控制
服务提供者若有多个不同版本的服务类,可以指定消费端使用不同的版本:
服务消费者调用时,可自由配置版本
Dubbo 高可用
ZooKeeper 宕机与 Dubbo 直连
当ZooKeeper注册中心宕机时,仍然可以使用Dubbo暴露的服务。原因:
- 监控中心宕掉不影响使用,只是丢失部分采样数据
- 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
- 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯(本地缓存了远程服务的ip端口号等信息,就算没有了注册中心,也可以直接使用ip和端口号与远程服务进行通讯)
- 服务提供者无状态,任意一台宕掉后,不影响使用
- 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
高可用:通过设计,减少系统不能提供服务的时间。
集群下 Dubbo 负载均衡配置
Dubbo提供了多种集群负载均衡策略,缺省为 random
: 随机调用。共有如下四种策略:
- Random LoadBalance:基于权重的随机负载均衡机制
- RoundRobin LoadBalance: 基于权重的轮询负载均衡机制
- LeastActive LoadBalance: 最少活跃数负载均衡机制
- ConsistentHash LoadBalance: 一致性Hash负载均衡机制
负载均衡配置方法:
1 | <dubbo:service interface="..." loadbalance="roundrobin" /> |
详细介绍:
Random LoadBalance 基于权重的随机负载均衡机制
随机,按权重设置随机概率。 调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance 基于权重的轮询负载均衡机制
轮循,按公约后的权重设置轮循比率。 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive LoadBalance 最少活跃数负载均衡机制
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
ConsistentHash LoadBalance 一致性Hash负载均衡机制
一致性 Hash,相同参数的请求总是发到同一提供者。 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
算法参见:http://en.wikipedia.org/wiki/Consistent_hashing 。缺省只对第一个参数 Hash,如果要修改,请配置:
1 | <dubbo:parameter key="hash.arguments" value="0,1" /> |
缺省用 160 份虚拟节点,如果要修改,请配置:
1 | <dubbo:parameter key="hash.nodes" value="320" /> |
服务降级
服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的当不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。向注册中心写入动态配置覆盖规则:
1 | RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension(); |
其中两种服务降级方式:
mock=force:return+null
(强制返回null))表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。mock=fail:return+null
(失败才返回null)表示消费方对该服务的方法调用在失败后再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
集群服务容错
在服务调用失败时,Dubbo提供了多种容错方案,缺省为failover
:失败重试。
集群服务容错模式:
- Failover Cluster:失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过
retries="2"
来设置重试次数(不含第一次)。 - Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
- Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
- Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过
forks="2"
来设置最大并行数。 - Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
集群服务模式配置:
1 | <dubbo:service cluster="failsafe" /> |
整合Hystrix
服务熔断错处理配置参考=> https://www.cnblogs.com/xc-xinxue/p/12459861.html
Hystrix旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。
配置spring-cloud-starter-netflix-hystrix
。Spring Boot官方提供了对Hystrix的集成,直接在pom.xml
里加入依赖:
1 | <dependency> |
然后在ProviderApplication
类上增加 @EnableHystrix 来启用hystrix starter:
1 |
|
配置Provider端
在Dubbo的Provider上增加 @HystrixCommand 配置,这样子调用就会经过Hystrix代理。
1 |
|
配置Consumer端
对于Consumer端,则可以增加一层method调用,并在method上配置 @HystrixCommand 。当调用出错时,会走到 fallbackMethod = "reliable"
的调用里。
1 |
|