分布式业务系统就是把原来的Java 开发的一个大块系统拆分成做个子系统,多个子系统支之间互相调用,形成一个整体。
Dubbo 是一款高性能Java RPC 框架。它实现了面向接口代理的RPC 调用、服务注册和发现,负载均衡,容错,扩展性等功能;
Dubbo 大致分为三层:
业务层
RPC 层
Remoting 层
dubbo 工作原理(核心组件):
第一层:service 层,接口层,给服务提供者和消费者来实现的。
第二层:config 层,配置层,对dubbo 进行各种配置。
第三层:proxy 层,服务代理层,生成代理,代理之间进行通信。
第四层:registry 层,服务注册层,负责服务注册与发现。
第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务。
第六层:monitor 层,监控层,对RPC 接口的调用次数和时间进行监控。
第七层:protocal 层,远程调用层,封装RPC 接口调用。
第八层:exchange 层,信息交换层,封装请求响应模式,同步转异步。
第九层:transport 层,网络传输层,抽象main 和netty 为统一接口。
第十层:serialize 层,数据序列化层。
工作流程:
- provider(提供者)向注册中心去注册。
- consumer(消费者)从注册中心订阅服务,注册中心通知消费者注册好了的服务。
- 消费者调用提供者。
- 消费者和提供者异步通知监控中心。
刚开始初始化的时候,消费者会将提供者的地址等的信息拉取到本地缓存,所以即使注册中心挂了也可以继续通信。
注册中心:
主要作用:
动态载入服务,服务提供者通过注册中心把自己暴露给消费者,不需要消费者逐个更新配置文件。
动态发现服务,消费者动态感知新的配置、路由规则和新的服务提供者。
参数动态调整,支持参数的动态调整,新参数会自动更新到所有的服务节点。
服务统一配置,统一连接到注册中心的服务配置。
注册中心工作原理:
Dubbo 有四种注册中心的实现,分别是ZooKeeper,Redis,Simple,Multicast
Zookeper 是通过树形文件存储的ZNode 在/dubbo/service 下建立四个目录:
Providers 目录:存放服务提供者URL 和元数据
Consumers 目录:存放消费者URL 和元数据
Routers 目录:存放消费者的理由策略
Configurators 目录:存放多个用于服务提供者的动态配置URL 和元数据
客户端第一次链接注册中心时会获取全量的服务元数据;根据Zookeeper 客户端的特性,会在对应的ZNode 的目录上注册一个Watcher ,同时让客户端和注册中心保持长连接。如果服务端的元数据发生改变,客户端就会接收到变更通知,然后去注册中心更新元数据信息。
Dubbo 通信协议:
dubbo 协议:
默认走的协议,单一长连接,长连接:就是建立连接过后可以持续发送请求,不需要在建立连接。dubbo 协议进行的是NIO 异步通信,基于hassian 协议作为序列化的协议,一般用于传输数据量小,但是并发量很高的场景。NIO 异步通信可以支撑高并发请求,长连接是最适合高并发场景的。
rmi 协议:
走Java 二进制序列化,多个短连接,适合消费者和提供者差不多的情况,适用于文件传输,较少用。
hessian 协议:
hessian 序列化协议,多个短连接,是用户提供者比消费者多的情况,适用于文件传输,较少用。
数据结构:
原始二进制数据
boolean
64-bit date(64 位毫秒值的日期)
64-bit double
32-bit int
64-bit long
null
UTF-8 编码的 string
http 协议:
json 序列化
webservice :
SOAP 文本序列化。
PB(Protocol Buffer):
PB 是Google 出品的轻量且高效的结构化数据存储格式,性能比JSON,xml 要高很多。第一:PB 使用proto 编译器,自动进行序列化和反序列化,速度非常快;第二:PB 的数据压缩效果好,它序列化后的数据量体积小,传输起来带宽和速度上会有优化。
Dubbo 负载均衡策略:
random loadbalance :
默认情况下的均衡策略,就是随机调用实现负载均衡,可以对提供者不同实例设置不同的权重,会按照权重来负载均衡,权重越大分配的流量越高。
roundrobin loadbalance:
它默认均匀地将流量分配到各个机器上,机器的性能不一样,容易导致性能差的机器负载过高,所以需要调整权重,让性能差的机器承载权重小一点,流量小一点。
leastactive loadbalance :
会自动感知,如果机器的性能越差,接收的请求越少,越不活跃,此时就会给不活跃的性能差的机器更少的请求。
consistanthash loadbalance :
一致性hash 算法,相同参数的请求一定分发到一个提供者上,提供者挂掉时,会基于虚拟节点自动分配剩余流量,抖动不会太大。如果需要的不是随机负载均衡,是要一类请求都到一个节点那就走这个一致性hash 策略。
Dubbo 集群容错策略:
failover cluster 模式:
失败自动切换,会自动重试其他机器,默认的模式,常见于读操作。
failfast cluster 模式:
一次调用失败就立即失败,常见与非幂等性的写操作,比如新增一条记录。
failsafe cluster 模式:
出现异常时忽略掉,常用于不重要的借口调用,比如记录日志。
failback cluster 模式:
失败后会自动记录请求,然后定时重发,比较适用于写消息队列这种情况。
forking cluster 模式:
并行调用多个提供者,只要一个成功就立即返回,常用于实时性要求比较高的操作,但是会浪费很多的服务资源,可设置最大并行数。
broadcacst cluster 模式:
逐个调用所有的提供者,任何一个提供者出错立即报错,通常用于通知提供者更新缓存或日志等本地资源信息。
Dubbo 动态代理策略:
默认使用javassist 动态字节码生成,创建代理类。但是可以通过spi 扩展机制配制自己的动态代理策略。
spi 思想:
spi(service provider interface ),根据指定的配置或默认配置,取找到对应的实现类加载进来。然后用这个实现类的实例对象。一般用在插件扩展的场景。
Dubbo 用的是Protocol 接口,在系统运行的时候,dubbo 会判断应该选用protocol 接口的那个实现类来实例化对象,它会找到配置的protocol ,将配置的实现类加载到jvm 中然后实例化对象。
Dubbo 的服务治理,服务降级,失败重试,超时重试:
服务治理:
1.调用链路自动生成
一个大型的分布式系统或者现在流行的为服务框架,分布式系统都是由大量的服务组成。服务器之间的调用就需要基于dubbo 的分布式系统中对各个服务之间的调用自动记录下来,然后将各个服务之间的以来关系和调用链路生成出来。
2.服务访问压力以及时长统计
这就需要自动统计各个接口和服务之间的调用次数以及访问延时,分两个级别:
- 接口粒度,就是每个服务的每个接口每天被调用多少次,请求延时分别是多少
- 从源头入口开始,一个完整的请求链路经过几十个服务之后完成一次请求,每天链路走多少次,全链路请求延时分别是多少。
知道这些东西之后才取考虑如何扩容、如何优化。
3.其他
服务分层(避免循环依赖)
调用链路失败监控和报警
服务鉴权
每个服务的可用性的监控
服务降级:
服务降级是当服务器压力剧增的情况下,根据当前业务情况以及流量对一些服务和页面进行由策略的降级,以此释放服务器资源以保证核心任务的正常运行。
降级的方式:
服务接口拒绝服务:页面能访问,但是添加删除会提示服务器繁忙。
页面拒绝访问:页面提示服务器繁忙,跳转到其他静态页面。
延迟持久化:页面照常访问,记录变更会稍晚看到结果,将数据记录到异步队列或log ,服务恢复后执行。
随机拒绝服务:服务接口随机拒绝服务,让用户重试,会导致用户体验不佳。
调用接口失败的时候可以通过mock 同意返回null。mock 的值也可以改为true,然后跟接口同一个路径下实现Mock 类,然后在mock 类里实现自己的降级逻辑。
失败重试和超时重试:
失败重试就是消费者调用提供者失败,比如抛出异常,此时就可以重试,或者调用超时也可以重试
参数设置:
timeout:超时时间,超过设置时间则调用超时
retries:一般是在读请求的时候设置,如果没有读到会报错,重试指定的次数。
分布式服务接口的幂等性:
所谓幂等性就是说一个接口多次发起同一个请求,要确保这个请求是准确的,保证幂等性应该结合实际的业务去考虑。
主要考虑以下三点:
1、对于每个请求必须有一个唯一的标识,
2、每次处理完请求之后必须有一个记录标识表示已经处理了这个请求,常见的方法就是在mysql 中记录状态
3、每次接收请求之前要进行判断之前是否有处理过这个请求,
也就是:全局唯一ID、去重表,多版本并发控制
但是我们通常只需要对新增请求和更新请求做幂等性保证。
分布式服务接口请求的顺序性:
在使用之前应该从业务逻辑上考虑是否需要顺序性保证;比如,使用分布式锁会导致系统复杂性提高,可能会带来效率低下,热数据压力过大等的问题。
首先得用dubbo 的一致性hash 负载均衡策略,比如将某个请求分发个某个机器,然后机器可能因为多线程并发执行,就需要把对应的请求扔到一个内存队列中去,强制排队,以此来保证请求的顺序性。最好能将多个操作合并成一个操作。
为什么要将系统进行拆分?
1、不拆分的话系统的代码太多,多个人维护一份代码容易引起代码冲突,还要解决代码合并问题,耗时耗力。不容易进行再次开发和维护
2、拆分后的系统每个服务器上的代码量就少很多,每个服务器部署到单独的机器上,不会代码冲突,容易维护。
如何拆分?
大部分的系统是会随着系统的复杂度的提升进行多次拆分的。第一次拆分可能就是根据情况将多个模块拆分出来。随着复杂度的提升,后面会将每个模块又进行拆分。
如何设计一个类似Dubbo 的RPC 框架?
考虑以下问题:
- 首先是注册中心,保留各个服务的信息。
- 当消费者去注册中心拿对应的服务时可能服务会存在多台机器中
- 基于动态代理发送请求,面向接口获取动态代理,动态代理找到本地代理,然后找到服务器对应的机器地址。
- 负载均衡算法。
- 找到机器发送的请求
- 服务器需要生成一个动态代理,监听网络端口代理本地服务代码进行调用。