摘自Dubbo入门介绍及学习笔记总结
分布式概述
分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。
老式系统(单一应用架构)就是把一个系统,统一放到一个服务器当中然后每一个服务器上放一个系统,如果说要更新代码的话,每一个服务器上的系统都要重新去部署十分的麻烦。
而分布式系统就是将一个完整的系统拆分成多个不同的服务,然后在将每一个服务单独的放到一个服务器当中。
PRC
RPC:Remote Procedure Call ——远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
RPC基本原理
- Client像调用本地服务似的调用远程服务;
- Client stub接收到调用后,将方法、参数序列化
- 客户端通过sockets将消息发送到服务端
- Server stub 收到消息后进行解码(将消息对象反序列化)
- Server stub 根据解码结果调用本地的服务
- 本地服务执行(对于服务端来说是本地执行)并将结果返回给Server stub
- Server stub将返回结果打包成消息(将结果消息对象序列化)
- 服务端通过sockets将消息发送到客户端
- Client stub接收到结果消息,并进行解码(将结果消息发序列化)
- 客户端得到最终结果。
RPC 调用分以下两种:
同步调用:客户方等待调用执行完成并返回结果。
异步调用:客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。若客户方不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。
SOA(Service-Oriented Architecture)
流动计算架构:在分布式应用架构的基础上增加了一个调度、治理中心基于访问压力实时管理集群容量、提高集群的利用率,用于提高机器利用率的资源调度和治理中心(SOA) 是关键 (不浪费计算机资源)
Dubbo
官网https://dubbo.apache.org/zh/
Apache Dubbo 提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。
Dubbo设计架构
服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者(Consumer): 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。推荐使用zookeeper。
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Docker搭建Zookeeper + Dubbo-admin
拉取zookeeper:3.4.11,并运行
docker pull zookeeper:3.4.11
docker run --name zookeeper -p 2181:2181 -v /home/zookeeper/data:/data -d zookeeper:3.4.11
拉取chenchuxin/dubbo-admin,并运行
docker pull chenchuxin/dubbo-admin
docker run -d --name dubbo-admin -p 8081:8080 -e dubbo.registry.address=zookeeper://172.17.0.2:2181 -e dubbo.admin.root.password=root -e dubbo.admin.guest.password=123 chenchuxin/dubbo-admin
#(dubbo.registry.address=zookeeper://172.17.0.2:2181)这个地址为你zookeeper容器的地址,如何获取zookeeper容器地址:输入命令:docker inspect 容器名/容器id,“IPAddress”:就是zookeeper的ip地址
完成并访问http://120.79.28.120:8081/
服务提供者配置
官方教程https://dubbo.apache.org/zh/docs/v2.7/user/quick-start/
- 定义服务接口
package com.zhang.service;
public interface DemoService {
String sayHello(String name);
}
- 在服务提供方实现接口
package com.zhang.serviceImpl;
import com.zhang.service.DemoService;
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
return "Hello" + name;
}
}
- 用 Spring 配置声明暴露服务
- 加载 Spring 配置
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class Provider {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
context.start();
System.in.read();
}
}
服务消费者配置
- 通过 Spring 配置引用远程服务
- 加载Spring配置,并调用远程服务
import com.zhang.service.DemoService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Consumer {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
context.start();
DemoService demoService = (DemoService)context.getBean("demoService"); // 获取远程服务代理
String hello = demoService.sayHello("world"); // 执行远程方法
System.out.println( hello ); // 显示调用结果
System.in.read();
}
}
docker搭建Dubbo-monitor
此处搭建失败,留坑
dubbo配置文件的映射规则、覆盖策略
官方文档 https://dubbo.apache.org/zh/docs/v2.7/user/configuration/properties/
如下,是一个典型的 dubbo.properties 配置样例。
dubbo.application.name=foo
dubbo.application.owner=bar
dubbo.registry.address=10.20.153.10:9090
覆盖策略
优先级从高到低:
- JVM -D 参数:当你部署或者启动应用时,它可以轻易地重写配置,比如,改变 dubbo 协议端口;
- XML:XML 中的当前配置会重写 dubbo.properties 中的;
- Properties:默认配置,仅仅作用于以上两者没有配置时。
如果在 classpath 下有超过一个 dubbo.properties 文件,比如,两个 jar 包都各自包含了 dubbo.properties,dubbo 将随机选择一个加载,并且打印错误日志。
启动时检查
Dubbo 会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"。
可以通过 check="false" 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。
另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false",总是会返回引用,当服务恢复时,能自动连上。
application.properties添加如下配置
#dubbo.consumer表示所有的 消费者服务 启动时不检查服务依赖
dubbo.consumer.check=false
不同粒度配置的覆盖关系
以 timeout 为例,下图显示了配置的查找顺序,其它 retries, loadbalance, actives 等类似:
- 方法级优先,接口级次之,全局配置再次之。
- 如果级别一样,则消费方优先,提供方次之。
其中,服务提供方配置,通过 URL 经由注册中心传递给消费方。
(建议由服务提供方设置超时,因为一个方法需要执行多长时间,服务提供方更清楚,如果一个消费方同时引用多个服务,就不需要关心每个服务的超时设置)。
理论上 ReferenceConfig 中除了interface这一项,其他所有配置项都可以缺省不配置,框架会自动使用ConsumerConfig,ServiceConfig, ProviderConfig等提供的缺省配置。
重试次数配置
配置dubbo服务重试次数.
Dubbo 服务在尝试调用一次之后,如出现非业务异常(服务突然不可用、超时等),Dubbo 默认会进行额外的最多2次重试.
重试次数支持两种自定义配置:
- 通过注解/xml进行固定配置;
- 通过上下文进行运行时动态配置。
-
通过注解/xml进行固定配置
通过RpcContext进行运行时动态配置,优先级高于注解/xml 进行的固定配置(两者都配置的情况下,以RpcContext配置为准).
// dubbo服务调用前,通过RpcContext动态设置本次调用的重试次数
RpcContext rpcContext = RpcContext.getContext();
rpcContext.setAttachment("retries", 5);
重试的配置应该保证 幂等性,即操作多次的结果都一样。
幂等:查询、删除、修改
非幂等:新增(retries="0")
多版本
在 Dubbo 中为同一个服务配置多个版本,实现灰度发布。
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
#消费者使用服务1.0.0版本
# 不指定版本,消费者随机使用服务版本
本地存根
在 Dubbo 中利用本地存根在 客户端 执行部分逻辑
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等。
Consumer调用远程服务之前,先调用本地的Stub ,再由Stub决定要不要去调Dubbo的 Proxy,进而调用 Provider 提供的服务。
在消费者的 consumer.xml 中配置如下
并在consumer端,提供Stub的实现
public class DemoServiceStub implements DemoService {
private final DemoService demoService;
//传入的是DemoService远程的代理对象
public DemoServiceStub(DemoService demoService) {
super();
this.demoService = demoService;
}
@Override
public String sayHello(String name) {
System.out.println("DemoServiceStub ..." + name);
if(!name.isEmpty()){
return demoService.sayHello(name);
} else {
System.out.println("name is empty .....");
}
return null;
}
}
Stub 必须有可传入 Proxy 的构造函数。
一般在DemoService interface 旁边放一个 Stub 实现,它实现 DemoService 接口,并有一个传入远程 DemoService 实例的构造函数
SpringBoot整合dubbo的三种方式
- 导入dubbo-starter 依赖,在application.properties中配置属性,用@Service暴露服务,@Reference 引用服务
- 保留dubbo.xml配置文件,导入dubbo-starter,使用@ImportResource导入配置文件。
- 使用注解API的方式(https://dubbo.apache.org/zh/docs/v2.7/user/configuration/api/)
第一种:
-
新建两个spring Initializer 的 Module
引入依赖
org.apache.dubbo
dubbo-spring-boot-starter
2.7.1
引入此依赖会自动引入如下依赖:
- application.properties配置
server.port=8081
dubbo.application.name=springboot-provider
dubbo.registry.protocol=zookeeper #注册中心
dubbo.registry.address=120.79.28.120:2181 #地址
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
- 注解开发
代码框架:
服务提供者
/**
* @org.apache.dubbo.config.annotation.Service
* 这个注解即暴露UserServiceImpl服务!!!!!
*/
@org.apache.dubbo.config.annotation.Service
@Component
public class UserServiceImpl implements UserService {
@Override
public List getUserAddressList(String userId) {
UserAddress address1 = new UserAddress();
UserAddress address2 = new UserAddress();
return Arrays.asList(address1, address2);
}
}
服务消费者
@Service
public class OrderServiceImpl implements OrderService {
/*@Reference注解引用服务!!!!*/
@Reference
UserService userService;
@Override
public List initOrder(String userId) {
List addressList = userService.getUserAddressList(userId);
return addressList;
}
}
- 结果
高可用
- zookeeper宕机与dubbo直连
zookeeper 注册中心宕机,还可以使用dubbo暴露的服务
健壮性
- 监控中心宕机不影响使用,只是丢失部分采样数据
- 数据库宕机后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
- 注册中心对等集群,任意一台注册中心宕机后,将自动切换到另外一台
- 假设注册中心全部宕机,服务提供者和服务消费者仍能通过本地缓存通信
- 即使没有注册中心,可以通过dubbo直连提供服务,服务消费者直接指定提供者的URL进行直连
- 集群下dubbo负载均衡配置
在集群负载均衡时,dubbo提供了多种均衡策略,缺省为random随机调用
负载均衡策略
此处查看 官方文档
random LoadBalance 随机负载均衡,是加权随机算法的具体实现
假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上,此时返回服务器 A 即可。RoundRobinLoadBalance 加权轮询负载均衡
所谓轮询是指将请求轮流分配给每台服务器。举个例子,我们有三台服务器 A、B、C。我们将第一个请求分配给服务器 A,第二个请求分配给服务器 B,第三个请求分配给服务器 C,第四个请求再次分配给服务器 A。这个过程就叫做轮询。
但现实情况下,我们并不能保证每台服务器性能均相近。如果我们将等量的请求分配给性能较差的服务器,这显然是不合理的。因此,这个时候我们需要对轮询过程进行加权,以调控每台服务器的负载。经过加权后,每台服务器能够得到的请求数比例,接近或等于他们的权重比。比如服务器 A、B、C 权重比为 5:2:1。那么在8次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的2次请求,服务器 C 则收到其中的1次请求。LeastActiveLoadBalance 最小活跃数负载均衡
活跃调用数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求。此时应优先将请求分配给该服务提供者。在具体实现中,每个服务提供者对应一个活跃数 active。初始情况下,所有服务提供者活跃数均为0。每收到一个请求,活跃数加1,完成请求后则将活跃数减1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求、这就是最小活跃数负载均衡算法的基本思想。ConsistentHashLoadBalance 一致性 hash 负载均衡
当有查询或写入请求时,则为缓存项的 key 生成一个 hash 值。然后查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中查询或写入缓存项。如果当前节点挂了,则在下一次查询或写入缓存时,为缓存项查找另一个大于其 hash 值的缓存节点即可。
大致效果如下图所示,每个缓存节点在圆环上占据一个位置。如果缓存项的 key 的 hash 值小于缓存节点 hash 值,则到该缓存节点中存储或读取缓存项。比如下面绿色点对应的缓存项将会被存储到 cache-2 节点中。由于 cache-3 挂了,原本应该存到该节点中的缓存项最终会存储到 cache-4 节点中。
一致性 hash 在 Dubbo 中的应用。我们把上图的缓存节点替换成 Dubbo 的服务提供者,于是得到了下图:
这里相同颜色的节点均属于同一个服务提供者,比如 Invoker1-1,Invoker1-2,……, Invoker1-160。这样做的目的是通过引入虚拟节点,让 Invoker 在圆环上分散开来,避免数据倾斜问题。所谓数据倾斜是指,由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。
比如:下图中Invoker-1 和 Invoker-2 在圆环上分布不均,导致系统中75%的请求都会落到 Invoker-1 上,只有 25% 的请求会落到 Invoker-2 上。解决这个问题办法是引入虚拟节点,通过虚拟节点均衡各个节点的请求量。
服务降级 官方文档
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
- mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
- 还可以改为 mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
集群容错 官方文档
在集群调用失败时,Dubbo提供了多种容错方案,默认是 failover 重试
Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过retries="2"
来设置重试次数(不含第一次)。Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于 非幂等性 的写操作,比如新增记录。Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于 实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks="2"
来设置最大并行数。Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。整合 Hystrix
Hystrix 通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
SpringBoot提供了Hystrix 的集成,直接添加如下依赖:
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
1.4.4.RELEASE
Dubbo原理
RPC原理
一次完整的RPC调用流程(同步调用,异步另说)如下:
- 服务消费方(client)调用以本地调用方式调用服务;
- client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
- client stub找到服务地址,并将消息发送到服务端;
- server stub收到消息后进行解码;
- server stub根据解码结果调用本地的服务;
- 本地服务执行并将结果返回给server stub;
- server stub将返回结果打包成消息并发送至消费方;
- client stub接收到消息,并进行解码;
- 服务消费方得到最终结果。
RPC框架的目标就是要2~8这些步骤都封装起来,这些细节对用户来说是透明的,不可见的。dubbo作为RPC框架也是如此,将步骤2-8封装了,对于用户来说只需要操作1、9步骤
Dubbo原理
整体框架设计 官方文档:框架设计
略...官方已做详细说明。