调用关系说明: 1. 服务容器负责启动,加载,运行服务提供者。 2. 服务提供者在启动时,向注册中心注册自己提供的服务。 3. 服务消费者在启动时,向注册中心订阅自己所需的服务。 4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
我们在讲 Dubbo 的时候提到了 Dubbo 实际上是一款 RPC 框架,那么RPC 究竟是什么呢?相信看了下面我对 RPC 的介绍你就明白了!
RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务A,B部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。
https://dubbo.apache.org/zh/
dubbo-admin 是dubbo的管理界面平台,且是一个前后端分离的项目,前端使用vue,后端使用springboot。
下载
dubbo-admin是在github上发布的项目源码,所以我只需要前往github下载即可
github地址:https://github.com/apache/dubbo-admin
运行项目
dubbo-admin-server/src/main/resources/application.properties
中指定注册中心地址(注册中心一般推荐使用zookeeper)npm run dev
即可启动以前我们的项目全部代码都是放到同一台服务器上运行的单机模式,现在系统模块拆分,并部署到各个服务器上运行,并使用dubbo来作为服务中介
如何使用zookeeper具体请看这篇文章:zookeeper的安装与使用
公共模块里面,存放接口,与实体类(需要实现序列化),采用maven项目实现,以便后续项目的使用
DemoService
public interface DemoService {
public String sayHello();
public User getUser();
}
User
因为dubbo是一个远程服务调用框架,在服务之前调用,要想传输类对象,这个实体类就必须实现序列化(Serializable ),这也是dubbo一大高级特性
public class User implements Serializable {
private Integer id;
private String name;
private Double score;
public User() {
}
public User(Integer id, String name, Double score) {
this.id = id;
this.name = name;
this.score = score;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getScore() {
return score;
}
public void setScore(Double score) {
this.score = score;
}
}
使用 mvn install
命令将这个项目安装到本地的maven仓库
<dependencies>
<dependency>
<groupId>org.examplegroupId>
<artifactId>api_simpleartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.5.2version>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>2.7.8version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>5.1.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>5.1.0version>
<exclusions>
<exclusion>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
exclusion>
exclusions>
dependency>
dependencies>
# 告诉注册中心,你的项目的名称,具有唯一性
dubbo.application.name=provider
# 告诉程序你的注册中心的地址,端口号,以及所使用的的协议,我们使用zookeeper作为注册中心,那么协议就是zookeeper
dubbo.registry.address=zookeeper://192.168.23.135:2181
# 下面这个配置使用为了防止,在第一次注册的时候,由于timeout时间过短导致的连接失败的情况
dubbo.config-center.timeout=10000
dubbo.registry.timeout=10000
# 因为是服务端,我们需要让程序像服务器一样运行起来,那么我们就必须要给程序一个端口号
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
# 元数据配置,注册中心地址 如果不配则无法看见元数据
# 但是个人发现,这个配置写上去,就会报zookeeper not connected异常
# dubbo-springboot版本2.7.8不配置这一项,元数据一样会显示
# dubbo.metadata-report.address=zookeeper://127.0.0.1:2181
// 1. 以前这里的注解是@Service
// 2. 作用是将UserServiceImpl生成bean对象并放入spring容器
// 3. @DubboService是将此服务信息(ip:port、服务名字),放在注册中心,提供给消费者。
@DubboService(interfaceClass = DemoService.class)
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello() {
return "hello world";
}
@Override
public User getUser() {
return new User(1,"zhangsan",99.9);
}
}
<dependencies>
<dependency>
<groupId>org.examplegroupId>
<artifactId>api_simpleartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.5.2version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.5.2version>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>2.7.8version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>5.1.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>5.1.0version>
<exclusions>
<exclusion>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
exclusion>
exclusions>
dependency>
dependencies>
# 告诉注册中心,你的项目的名称,具有唯一性
dubbo.application.name=provider
# 告诉程序你的注册中心的地址,端口号,以及所使用的的协议,我们使用zookeeper作为注册中心,那么协议就是zookeeper
dubbo.registry.address=zookeeper://192.168.23.135:2181
# 下面这个配置使用为了防止,在第一次注册的时候,由于timeout时间过短导致的连接失败的情况
dubbo.config-center.timeout=10000
dubbo.registry.timeout=10000
# web项目的端口号
server.port=8888
4.编写web层
@RestController
public class DemoController {
// DubboReference的作用是去注册中心寻找对应的服务,
// 并且生成代理对象,放入spring容器中,并赋值给demoService
@DubboReference
private DemoService demoService;
@RequestMapping("demo01")
public Object demo01(){
return demoService.sayHello();
}
@RequestMapping("demo02")
public Object demo02(){
return demoService.getUser();
}
}
到这里,我们的分布式小项目基本就完成了
https://gitee.com/cn2770345524/test_dubbo02/
dubbo框架是一个远程调用过程的框架,允许我们通过网络传输对象,但是传输的对象必须要实现Serializable
接口
有这么一种情况,我们所调用的服务都是正常的,但是服务中心(registry)挂掉了,这个时候我们还能不能正常访问服务呢?
是可以的。dubbo框架中,当服务都是正常运行的,但是注册中心(registry)挂掉了之后,依然允许我们正常的调用原有所使用的的服务
在consumer调用了某种服务后,dubbo框架会自动的将该服务的地址,协议等相关信息缓存到本地,虽然注册中心挂掉了,但是只要原有服务的地址没变,依然可以访问服务,这就是dubbo的地址缓存
在生产环境中,我们通常会遇到这种情况,调用某种服务时,因为服务提供方的系统资源不够,导致服务执行耗时过长,导致整个程序过程就卡在了调用服务哪里
dubbo提供了一种超时机制,给服务设置一个超时限制,当服务调用超过了最大时限,系统就认为不需要再等待,通知服务调用方服务超时
如果是针对消费端,那么当消费端发起一次请求后,如果在规定时间内未得到服务端的响应则直接返回超时异常,但服务端的代码依然在执行。
如果是针对服务端,那么当消费端发起一次请求后,一直等待服务端的响应,服务端在方法执行到指定时间后如果未执行完,此时返回一个超时异常给到消费端。
// 超时针对服务端,那么当消费端发起一次请求后,一直等待服务端的响应,服务端在方法执行到指定时间后如果未执行完,此时返回一个超时异常给到消费端
// dubbo默认超时设置为1000毫秒
@DubboService(interfaceClass = DemoService.class,timeout = 3000)
public class DemoServiceImpl implements DemoService {
,,,,,
}
@RestController
public class DemoController {
// 超时针对消费端,那么当消费端发起一次请求后,如果在规定时间内未得到服务端的响应则直接返回超时异常,但服务端的代码依然在执行
@DubboReference(timeout = 3000)
private DemoService demoService;
,,,,,
}
在调用远程服务时,可能会因为网络问题,或者服务调用超时问题导致服务调用失败,dubbo框架提供了一种重试机制
当服务调用超时/调用失败之后,会重新尝试再次调用服务,如果调用服务的次数超过设置的最大重试次数后,就会认为服务调用失败
// 通过retries 来设置服务重试的最大次数
// 例如 当服务调用超时后,dubbo会再次调用服务,最多会重试三次
@DubboService(interfaceClass = DemoService.class,timeout = 3000,retries = 3)
public class DemoServiceImpl implements DemoService {
,,,,,
}
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
在生产环境中,我们的软件需要经过多个版本的迭代更新,一般我们采用灰度更新的方式更新,即:先让一部分用户使用新版本服务,用户反馈没有问题,我们再面向全部用户推广
可以按照以下的步骤进行版本迁移:
服务端
// 通过version设置服务版本信息
// 一种服务可以有多个版本,一个服务可以有多个服务者提供
@DubboService(version = "1.0")
public class DemoServiceImpl implements DemoService {
,,,,,
}
// 通过version设置服务版本信息
// 一种服务可以有多个版本,一个服务可以有多个服务者提供
@DubboService(version = "2.0")
public class DemoServiceImpl implements DemoService {
,,,,,
}
消费端
@RestController
public class DemoController {
// 通过version指定调用服务的哪个版本
@DubboReference(version = "1.0")
private DemoService demoService;
}
在生产环境中,我们通常是由集群的方式来提供服务的,此时就涉及到负载均衡的问题了
以怎么的方式来访问集群里面的机器,dubbo给我们提供一下几种策略
策略 | 作用 |
---|---|
random(默认策略) | 随机,按权重设置随机概率;在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重 |
roundrobin | 轮询,按公约后的权重设置轮询比率。存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上 |
leastactive | 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差;每个服务维护一个活跃数计数器。当A机器开始处理请求,该计数器加1,此时A还未处理完成。若处理完毕则计数器减1。而B机器接受到请求后很快处理完毕。那么A,B的活跃数分别是1,0。当又产生了一个新的请求,则选择B机器去执行(B活跃数最小),这样使慢的机器A收到少的请求。 |
consistenthash | 一致性 Hash,相同参数的请求总是发到同一提供者.当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动 |
在负载均衡中,是根据服务者权重(weight)来实行上述的策略,我们可以手动给服务设置策略与权重
通过给服务来weight
设置服务的权重,通过设置loadbalance
来设置服务的负载均衡策略
// loadbalance既可以在服务端设定,也可以在消费方设置
@DubboService(weight = 100,loadbalance = "random")
public class DemoServiceImpl implements DemoService {
......
}
@DubboReference(loadbalance = "random")
private DemoService demoService;
负载均衡只有在集群模式下才会生效
在调用服务的时候,可能会出现调用服务失败的情况,在集群模式下,如何应对这种情况呢
dubbo为我们提供了一下几种集群容错的方案:
方案 | 作用 |
---|---|
Failover Cluster(默认方案) | 失败重试。当出现失败时,重试其他服务器,默认重试2次,使用retrires配置,一般用于读操作 |
Failfast Cluster | 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录 |
Failsafe Cluster | 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作 |
Failback Cluster | 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作 |
Forking Cluster | 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数 |
Broadcast Cluster | 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。broadcast.fail.percent=20 代表了当 20% 的节点调用失败就抛出异常,不再调用其他节点 |
// 既可以针对消费方,也可以针对服务方
// 消费方
@DubboReference(cluster = "failover",retries = 2)
// 服务方
@DubboService(cluster = "failover")
具体的设置参数,可以参照官网API
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
失败返回null值,但是不报错
@DubboReference(version = "1.0",mock = "fail:return null")
private DemoService demoService;
无论服务是否正常运行,直接不去访问,返回null
@DubboReference(version = "1.0",mock = "force:return null")
private DemoService demoService;