分布式系统,即是将一个项目中的各个服务分散部署在不同的机器上的,一个服务可能负责几个功能,是一种面向服务的架构(SOA)。分布式系统可以看做是若干个独立计算机的集合,这些服务在不同的计算机上部署,但还是属于同一个项目。
分布式系统最初由Google提出,目的是为了利用上更多廉价的和普通的机器,以辅佐其他单个计算机无法完成的计算或存储任务,进而实现充分利用更多的物理机器和实现处理更多的数据。
但是原本在本地即可直接调用的服务,被部署到了其他机器上后,那么此时该如何调用远程的服务完成业务逻辑的处理呢?这正是分布式系统需要面临的问题,但也容易想到,通过网络实现 服务调用者 和 服务提供者 的通信即可。所以分布式系统其实是建立在网络之上的软件系统。
更多关于分布式的详情可参考我的另一篇博文:分布式和微服务的区别 。
上述提到分布式系统中存在各个服务间之间的远程调用问题,而Dubbo这个分布式服务框架则专门致力于解决这些问题。
Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC(一种远程调用) 分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。并且由于Dubbo在阿里内部经过多年的电商平台考验,在开源后很短的时间内国内各大互联网公司便相继开始使用。在2018年阿里宣布将Dubbo捐献给Apache,进入Apache孵化器,由Apache做维护。
简单来说,Dubbo的主要功能是通过RPC的方式实现服务间的调用,即 客户端和服务端共用一个接口(可以将接口打成一个jar包,在客户端和服务端引入这个jar包),客户端通过Spring容器面向接口写调用(Dubbo负责注入Spring容器),服务端面向接口写实现类,客户端调用服务端过程中的网络通信交给Dubbo框架去实现。
上述的客户端和服务端即分别可以对服务应消费者和服务生产者,Dubbo正是以经典的消费者生产者问题实现对服务的注册和调用。
上述Dubbo简介中实际上已经对Dubbo的基本使用做了描述。即服务生产者注册服务,服务消费者调用服务,并且彼此之间统一服务代码接口;也就是生产者实现接口然后注册,消费者通过Spring容器拿到接口实现类,面向接口调用。
下述基于SpringBoot分别编写服务消费者和服务生产者示例,并实现服务间的调用。两个示例项目均只选择了web模块和导入了dubbo依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>2.7.7version>
dependency>
服务提供者:
服务提供者,即生产者示例SpringBoot项目的目录结构如下:
第一步,在SpringBoot的application.yml
全局配置文件中对Dubbo的相关信息进行配置:
server:
port: 8001
dubbo:
application:
#服务提供者应用唯一名称
name: dubbo-provider
registry:
#服务注册中心地址,N/A表示不使用注册中心
address: N/A
scan:
#扫描要注册到Dubbo中的服务包名
base-packages: top.korbin.service
#当前服务注册发布时的通讯协议和端口号
protocol:
name: dubbo
port: 20880
第二步,服务提供者实现接口TestService
中仅有的public String test();
方法;并添加@DubboService
注解实现将该接口的实现类注入到Dubbo:
@DubboService //表示这是需要注册到Dubbo中的服务,项目启动时进行注册
@Service //注入Spring容器,表示属于三层架构的服务层
public class TestServiceImpl implements TestService {
@Override
public String test() {
return "这是Dubbo测试项目中的服务提供者提供的TestService中的test方法";
}
}
以上两步即简单的实现将一个测试接口的实现类注入到Dubbo中~
服务调用者:
服务调用者,即消费者示例SpringBoot项目的目录结构如下,仅引入需要调用服务的接口:
第一步,依旧首先在SpringBoot的application.yml
全局配置文件中对Dubbo进行配置,但相较于服务提供方,只需要配置注册中心和应用名称即可:
server:
port: 8002
dubbo:
application:
#服务调用者应用唯一名称
name: dubbo-comsumer
registry:
#服务注册中心地址,N/A表示不使用注册中心
address: N/A
第二步,编写测试控制器TestController
,使用@DubboReference
注解面向接口调用Dubbo中注册的实现类,在注解参数中需要指明该实现类的来源,即服务提供者注册到Dubbo后的地址:
@RestController
public class TetstController {
@DubboReference(url = "dubbo://127.0.0.1:20880/top.korbin.service.TestService")
private TestService testService;
@GetMapping("test")
public String test(){
return testService.test();
}
}
第三步,在浏览器访问测试地址,成功返回服务提供者实现类中的字符串,即实现了服务的远程调用。
上述示例是在本地演示,实际开发中将消费者中的地址替换为生产者真正的ip地址即可实现点对点的直连服务远程调用。但是通常情况下,既然都采用分布式系统了,为了服务的高可用性,会同时注册多个相同的服务提供者;Dubbo中对此的实现也很简单,只需要在服务调用端配置多个服务提供者的地址就可以了,即在@DubboReference
注解中的url
参数通过逗号,
分隔多个地址即可。
负载均衡:
同时注册多个相同的服务提供者,相当于单独对该服务做了 集群,所以还需要配置响应的负载均衡。同样Dubbo提供了对负载均衡的处理,也只需要在@DubboReference
注解中配置loadbalance
参数即可,其属性值有如下4种类型:
RandomLoadBalance //随机,按权重设置随机概率。
RoundRobinLoadBalance //轮询,按公约后的权重设置轮询比率。
LeastActiveLoadBalance //最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
ConsistentHashLoadBalance //一致性 Hash,相同参数的请求总是发到同一提供者。
//使用示例:
@DubboReference(loadbalance = RandomLoadBalance.NAME, url = "dubbo://127.0.0.1:20880/top.korbin.dubboprovider.service.TestService")
private TestService testService;
在上述通过Dubbo实现分布式的远程服务调用后,并进行了集群和负载均衡方案的介绍,所以测试示例目前的整体架构如下图所示:
虽然这个架构确实解决了一些问题,但是也带来了一些问题:
1.当服务提供者增加节点时,需要修改配置文件;
2.当其中一个服务提供者宕机时,服务消费者不能及时感知到,还会往宕机的服务发送请求。
3.上述消费者对生产者是一对多,当消费者也搭建集群时,就是多对多的错杂关系了,很难统一管理和维护。
另外在上述Dubbo的使用配置过程的介绍中,可以频繁看到 “注册中心” 这个关键词,其实只是在示例中故意不去使用而已。因为实际上 注册中心 正是解决上述问题的法宝,并且这也很好理解,就像经典的生产者消费者问题中需要一个中间容器一样,这个注册中心就像是存放服务的容器。
有了注册中心,之前的远程服务调用过程就可以得到优化:服务提供者首先将服务注册到注册中心,然后服务调用者在需要调用服务时统一去注册中心取即可,这样服务提供者只需要关心生产注册,服务调用者只需要关心调用消费。而服务分发、负载均衡和容灾处理机制等其他分布式的问题,都可以交由Dubbo通过注册中心来统一处理。
Zookeeper是一个分布式的,开源的分布式应用程序协调服务,是Google的Chubby一个开源的实现,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
Dubbo目前支持4种注册中心,分别是multicast、zookeeper、redis和simple,并且Dubbo推荐使用Zookeeper注册中心。Zookeeper也是一个分布式的服务框架,是树型的目录服务的数据存储,能做到集群管理数据 ,可以很好的作为Dubbo服务的注册中心,Dubbo能与Zookeeper做到集群部署,当提供者出现断电等异常停机时,Zookeeper注册中心能自动删除提供者信息,当提供者重启时,能自动恢复注册数据,以及订阅请求等。
Zookeeper注册中心是作为一个独立的服务存在的,所以在使用前需要先安装并启动服务。官网下载地址为:https://zookeeper.apache.org/releases.html#download :
点击 Download 后选择任意下载地址下载即可,注意该安装包同时适用于 Windows 系统和 Linux 系统:
下载完成后解压,并进入到zookeeper解压目录下的conf
目录下,将该目录下的zoo_sample.cfg
默认配置文件模板复制一份,然后改名为zoo.cfg
,改名后才能作为实际应用的配置文件而生效。
配置文件部分参数说明:
- tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。
- initLimit:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 5*2000=10 秒
- syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 2*2000=4 秒
- dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
- clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
完成配置后即可对Zookeeper服务进行启动,进入到Zookeeper根目录下的bin
目录,在 Windows 环境下,直接双击zkServer.cmd
即可启动Zookeeper服务器,并且双击zkCli.cmd
即可使用Zookeeper客户端连接到服务器验证服务器是否启动成功~
在 Linux 环境下,启动服务器也非常简单,同样在bin
目录下,操作命令如下:
- 启动ZK服务:
./zkServer.sh start
- 查看ZK服务状态:
./zkServer.sh status
- 停止ZK服务:
./zkServer.sh stop
- 重启ZK服务:
./zkServer.sh restart
至此即完成对Zookeeper的安装和服务启动~
首先添加SpringBoot中整合Dubbo时使用Zookeeper注册中心所需要的依赖:
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>5.1.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>5.1.0version>
dependency>
在Dubbo使用Zookeeper注册中心后,需要修改原先单独使用Dubbo的配置文件。
服务提供者:
server:
port: 8001
dubbo:
application:
#服务应用唯一名称
name: dubbo-provider
registry:
#服务Zookeeper注册中心地址
address: zookeeper://127.0.0.1:2181
scan:
#扫描要注册到注册中心中的服务包名
base-packages: top.korbin.service
#当前服务注册发布时的通讯协议和端口号
protocol:
name: dubbo
port: 20880
服务调用者:
server:
port: 8002
dubbo:
application:
#服务调用者应用唯一名称
name: dubbo-comsumer
registry:
#服务Zookeeper注册中心地址
address: zookeeper://127.0.0.1:2181
在服务调用客户端中,之前没有使用注册中心时需要使用@DubboReference
注解通过接口远程直连拿到Dubbo中注册的实现类。而现在使用了Zookeeper注册中心,同样只使用该注解即可,但相较于之前不需要再为该注解配置url
参数了,只要接口是在相同的包名下即可直接调用,因为服务调用时的分配任务已经交给了Zookeeper注册中心:
@RestController
public class TetstController {
@DubboReference
private TestService testService;
@GetMapping("test")
public String test(){
return testService.test();
}
}
上述配置文件中,如果Zookeeper注册中心也是一个集群,则多个地址之间用逗号分隔即可。
现在使用了Dubbo+Zookeeper注册中心后,示例项目的架构图变为如下图所示了:
聪明的你一定可以发现,该图刚好对上上文介绍Dubbo是官方提供的服务注册与发现架构图:
节点角色说明:
0.服务容器负责启动(上面例子为Spring容器),加载,运行服务提供者。
1.服务提供者在启动时,向注册中心注册自己提供的服务。
2.服务消费者在启动时,向注册中心订阅自己所需的服务。
3.注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
4.服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
5.服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Dubbo提供了一个dubbo-admin项目,即管理控制台,实现对服务的可视化监控和管理。
官方开源地址下载:https://github.com/apache/dubbo-admin/tree/master ,下载完成后将dubbo-admin
项目解压,然后修改配置文件,其实也是一个SpringBoot项目:
完成配置后即可直接启动项目,或者将项目打包后再部署到应用环境中启动。项目启动后即可通过ip和端口号访问控制台UI,如我本地测试为:http://localhost:7001,进入到登录页面:
输入用户名root
和之前配置好的密码即可登录进入Dubbo管理控制台,然后查看注册了服务的服务提供这些诶和订阅了服务的服务调用者:
该开源的Dubbo的服务管理控制台项目是阿里巴巴内部裁剪版本,主要包含的功能:
路由规则
动态配置
服务降级
访问控制
权重调整
负载均衡
该部分内容参考自:Dubbo ,包含了Dubbo的一些特性和一些关于Dubbo的常见面试问题 :
Dubbo内置了哪几种服务容器?
三种服务容器:
1、Spring Container
2、Jetty Container
3、Log4j Container
Dubbo都支持什么协议,推荐用哪种?
1、dubbo://(推荐)
2、http://
3、rest://
4、redis://
5、memcached://
Dubbo里面有哪几种节点角色?
1、provide:暴露服务的服务提供方
2、consumer:调用远程服务的服务消费方
3、registry:服务注册于发现的注册中心
4、monitor:统计服务调用次数和调用时间的监控中心
5、container:服务运行容器
Dubbo默认使用什么注册中心,还有别的选择吗?
推荐使用zookeeper作为注册中心,还有redis、multicast、simple注册中心。
在 Provider 上可以配置的 Consumer 端的属性有哪些?
1、timeout:方法调用超时
2、retries:失败重试次数,默认重试 2 次
3、loadbalance:负载均衡算法,默认随机
4、actives 消费者端,最大并发调用限制
Dubbo有哪几种负载均衡策略,默认是哪种?
1、random loadbalance:安权重设置随机概率(默认);
2、roundrobin loadbalance:轮寻,按照公约后权重设置轮训比例;
3、lastactive loadbalance:最少活跃调用数,若相同则随机;
4、consistenthash loadbalance:一致性hash,相同参数的请求总是发送到同一提供者。
Dubbo启动时如果依赖的服务不可用会怎样?
Dubbo缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,默认check=“true”,可以通过 check=“false” 关闭检查。
Dubbo推荐使用什么序列化框架,你知道的还有哪些?
推荐使用Hessian序列化,还有Duddo、FastJson、Java自带序列化;
Dubbo默认使用的是什么通信框架,还有别的选择吗?
Dubbo 默认使用 Netty 框架,也是推荐的选择,另外内容还集成有Mina、Grizzly。
Dubbo有哪几种集群容错方案,默认是哪种?
服务提供者能实现失效踢出是什么原理?
服务失效踢出基于zookeeper的临时节点原理。
Dubbo服务之间的调用是阻塞的吗?
默认是同步等待结果阻塞的,支持异步调用。
Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。
Dubbo暂时不支持分布式事务。
Dubbo的管理控制台能做什么?
管理控制台主要包含:路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡,等管理功能。
注:dubbo源码中的dubbo-admin模块打成war包,发布运行即可得到dubbo控制管理界面。
Dubbo 服务暴露的过程?
Dubbo 会在 Spring 实例化完 bean 之后,在刷新容器最后一步发布 ContextRefreshEvent 事件的时候,通知实现了 ApplicationListener 的 ServiceBean 类进行回调 onApplicationEvent 事件方法,Dubbo 会在这个方法中调用 ServiceBean 父类 ServiceConfig 的 export 方法,而该方法真正实现了服务的(异步或者非异步)发布。
当一个服务接口有多种实现时怎么做?
当一个接口有多种实现时,可以用 group 属性来分组,服务提供方和消费方都指定同一个 group 即可。
服务上线怎么兼容旧版本?
可以用版本号(version)过渡,多个不同版本的服务注册到注册中心,版本号不同的服务相互间不引用。这个和服务分组的概念有一点类似。
Dubbo 和 Dubbox 有什么区别?
Dubbox 是继 Dubbo 停止维护后,当当网基于 Dubbo 做的一个扩展项目,如加了服务可 Restful 调用,更新了开源组件等。
你觉得用 Dubbo 好还是 Spring Cloud 好?
扩展性的问题,没有好坏,只有适合不适合,我更倾向于使用 Dubbo, Spring Cloud 版本升级太快,组件更新替换太频繁,配置太繁琐。
出现调用超时com.alibaba.dubbo.remoting.TimeoutException
异常怎么办?
通常是业务处理太慢,可在服务提供方执行:jstack PID > jstack.log 分析线程都卡在哪个方法调用上,这里就是慢的原因。如果不能调优性能,请将timeout设大。
出现java.util.concurrent.RejectedExecutionException
或者Thread pool exhausted
怎么办?
1、RejectedExecutionException表示线程池已经达到最大值,并且没有空闲连,拒绝执行了一些任务。
2、Thread pool exhausted通常是min和max不一样大时,表示当前已创建的连接用完,进行了一次扩充,创建了新线程,但不影响运行。
原因可能是连接池不够用,请调整dubbo.properites中的:
// 设成一样大,减少线程池收缩开销
dubbo.service.min.thread.pool.size=200
dubbo.service.max.thread.pool.size=200