RPC框架之Dubbo

问题1:为什么要把系统拆分成分布式的?为啥要用dubbo?

1.为什么要将系统进行拆分?

  1. 要是不拆分系统,一个大系统几十万行代码,很多人共同维护一份代码,简直是悲剧;

  2. 拆分了以后,一个大系统拆分成很多小服务,平均每个系统也就几万行代码,每个服务部署到单独的机器

2.如何进行服务拆分?

​ 大部分系统,是要进行多轮拆分的,第一次拆分就可能将原来的多个模块拆分开来。

​ 但是后来可能每个系统都变得很复杂了,每个模块拆分出来的服务又要继续拆分。

3.拆分后可以不用dubbo吗?

​ 当然可以,大不了各个系统之间,直接基于springmvc,就通过纯httpj接口互相通信。但是这里肯定是由问题的,因为HTTP接口通信维护起来成本很高,要考虑超时重试,负载均衡等各种问题。

所以dubbo说白了,就是一个rpc框架,就是本地就是进行接口调用,但是dubbo会代理这个调用请求,跟远程机器网络通信,处理负载均衡,服务上下线自动感知,超时重试等问题,就不用我们自己做,交给dubbo。

问题2:说一下dubbo的工作原理,注册中心挂了可以继续通信吗?说一下一次rpc请求的流程?

1.dubbo工作原理 

2.dubbo分层

  • 第一层:service层,需要服务提供方和消费方来实现

  • 第二层:config层,配置层,主要是对dubbo的各种配置

  • 第三层:proxy层,服务代理层,透明生成客户端的stub和服务端的skeleton

  • 第四层:registry层,服务端的注册与发现

  • 第五层:cluster层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务

  • 第六层:monitor层,监控层,对rpc接口的调用次数和调用时间进行监控

  • 第七层:protocol层,远程调用层,封装rpc调用

  • 第八层:exchange层,信息交换层,封装请求响应模式,同步转异步

  • 第九层:transport层,网络传输层,抽象mina和netty为统一接口

  • 第十层:serilize层,数据序列化层

 

2.注册中心挂了可以继续通信吗?

是可以的,因为客户端第一次从注册中心获取服务端的服务后,会缓存在自己本地,下一次调用服务端不用去请求注册中心,因此注册中心挂了是可以继续通信的

 

问题3:dubbo都支持哪些通信协议和序列化协议

1.dubbo支持不同的协议

  1. dubbo协议

    默认就是走dubbo协议,单一长链接,NIO异步通信

    (NIO通信原理:NIO采用了Reactor模式(类似于观察者模式,不同之处在于Reactor模式可以监听多个主题),通过一个多路复用器来监听多个客户端的网络句柄,一旦监听到客户端的请求消息,将对应的请求消息转发给对应的Handler(业务处理类))

    适合场景:传输数据量较小,但是并发很高

  2. rmi协议

    走java二进制序列化,多个短连接,适合消费者和提供者数量差不多,适用于文件的传输,一般较少

  3. hessian协议

    走hessian序列化协议,多个短连接,适用于提供者数量和消费者数量还多,适用于文件传输,一般较少使用

  4. http协议

    走json序列化

  5. webservice

    走soap文本序列化

 

问题4:dobbo负载均衡策略和集群容错策略?动态代理策略?

dobbo负载均衡策略

1.random loadbalance

默认情况下,dubbo是random load balcance 随机调用实现负载均衡,可以对provider不同的实例设置不同的权限,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了。

2.roundrobin loadbalance

这个默认就是均匀地将流量打到各个机器上去,但是如果各个机器的性能都不一样,容易导致性能差的机器负载过高而宕机。此时需要调整权重,让性能差一点的机器承载权重少一些

3.leastactive loadbalance

这个就是自动感知一下,如果某个机器性能差,那么接收的请求越少,约不活跃,此时就会给不活跃的性能差的机器更少的请求

 

4.consistanthash loadbalance

一致性hash算法,相同的参数的请求一定分配到一个provider上去,provider挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。如果需要的不是随机负载均衡,是要一类请求都到这个节点上,那就走这个一致性hash策略

 

dobbo集群容错策略

1.failover cluster模式

​ 失败自动切换,自动重试其他机器,dubbo默认使用这种策略

 

2.failfast cluster模式

一次失败就立即失败,常见于写操作

 

3.failsafe cluster模式

出现异常就忽略,常用于不重要的接口调用,比如日志记录

 

4.failbackc cluster模式

失败了后台自动记录请求,然后定时发送,比较适合消息队列这种情况

 

5.focking cluster模式

并行的调用多个provider,只要一个成功就立即返回

 

6.broadcast cluster模式

逐个调用所有的provider

 

dubbo的动态代理策略

默认使用 javassist动态字节码生成,创建代理类

 

问题5:你了不了解spi机制呢?如何基于spi机制对dubbo进行扩展?

SPI机制

  • 简单来说,就是service provider interface;比如有一个接口A,现在这个接口A有三个实现类,那么在运行的时候对接口到底用哪个实现类呢?这就需要SPI,需要根据指定的配置和默认的配置,去找到对应的实现类加载进来,然后用这个实现类的对象

    例如:接口A --> 实现类A1 实现类A2 实现类A3

    配置一下,接口A= 实现类A2

    在系统运行的时候,会加载这个配置,用实现类A2实例化出对象来提供服务

     

    • 比如说你有一个工程Demo ,里面有一个接口A,接口A在工程里是没有实现的--> 系统在运行的时候,怎么给接口A选择一个实现类呢?

    • 你就可以自己搞一个jar包,META-INF/services/,上放一个文件,文件名就是接口名,接口A,接口A的实现类=com.ultrapower.service.实现类A2.让工程A来依赖这个jar包,然后在系统运行的时候,工程跑起来,对接口A,就会扫描自己依赖的所有jar包,在每个jar包里找找,有没有 META-INF/service 文件夹?如果有,在里面找,有没有接口A这个名字的文件?如果有,在里面找找有没有接口A的实现类是你的jar包里的那个类

Dubbo的SPI机制

dubbo也用了spi的思想,不过没有用到jdk的spi机制,是自己实现的一套SPI机制

   1 Protocol protocol=ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension() 
  
  • Protocol接口,dubbo需要判断一下,在系统运行的时候,应该选用这个Protocol接口的哪个实现类来实例化对象来使用呢?

  • 他会去找一个你配置的Protocol,它就会将你配置的Protocol实现类,加载到 jvm中来,然后会实例化对象,就会用到你提供的哪个Protocol接口实现类

  • 微内核,可插拔,大量的组件,Protocol负责RPC调用,你可以实现自己的rpc调用组件,实现Protocol接口,给自己一个实现类即可

  • 这行代码就是Dubbo中大量使用的,就是对很多组件,都保留一个接口和多个实现,然后在运行的时候动态的根据配置去找对应的实现类,如果没有配置,那就走默认的实现类。

复制代码

 1  
 2   @SPI("dubbo")
 3   public interface Protocol{
 4        /**
 5        * 获取缺省端口,当用户没有配置端口时使用。
 6        *
 7        * @return 缺省端口
 8        */
 9       int getDefaultPort();
10   ​
11       /**
12        * 暴露远程服务:
13 * 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();
14 * 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。
15 * 3. export()传入的Invoker由框架实现并传入,协议不需要关心。
16 * 17 * @param 服务的类型 18 * @param invoker 服务的执行体 19 * @return exporter 暴露服务的引用,用于取消暴露 20 * @throws RpcException 当暴露服务出错时抛出,比如端口已占用 21 */ 22 @Adaptive 23 Exporter export(Invoker invoker) throws RpcException; 24 ​ 25 /** 26 * 引用远程服务:
27 * 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。
28 * 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。
29 * 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。
30 * 31 * @param 服务的类型 32 * @param type 服务的类型 33 * @param url 远程服务的URL地址 34 * @return invoker 服务的本地代理 35 * @throws RpcException 当连接服务提供方失败时抛出 36 */ 37 @Adaptive 38 Invoker refer(Class type, URL url) throws RpcException; 39 ​ 40 /** 41 * 释放协议:
42 * 1. 取消该协议所有已经暴露和引用的服务。
43 * 2. 释放协议所占用的所有资源,比如连接和端口。
44 * 3. 协议在释放后,依然能暴露和引用新的服务。
45 */ 46 void destroy(); 47 ​ 48 }

复制代码

 

在dubbo自己的jar里,在META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件中

dubbo=com.alibaba.dubbo.rpc.DubboProtocol

http=com.alibaba.dubbo.rpc.HttpProtocol

hessian=com.alibaba.dubbo.rpc.hessianProtocol

所有说这里就是dubbo默认的配置,其实就是一个Protocol接口,@SPI("dubbo")说的就是,通过SPI机制来提供实现类,实现类是通过dubbo作为默认key去配置文件里找的,配置文件的名称和接口名的全路径名时一样的,通过dubbo作为key 可以找到默认的实现类就是 com.alibaba.dubbo.rpc.DubboProtocol

dubbo默认的网络通信协议,就是dubbo协议,用的 DubboProtocol

如果想要动态替换默认的实现类,需要使用@Adapter接口,Protocol接口中,有两个方法加了@Adapter注解,就是说那俩接口会被代理实现。

比如:这个Protocol接口搞了俩@Adapter 注解标注了方法,在运行的时候,就会针对Protocol生成代理类,这个代理类的俩方法里面会代理代码,代理代码会在运行时候动态根据url中的protocol来获取那个key,默认是dubbo,你也可以自己指定别的key,那么就会获取别的实现类的实例了。

通过这个URL的参数不同,就可以控制动态的使用不同的组件实现类

 

 

问题6:如何基于dubbo做服务治理,服务降级,失败重试以及超时重试?

1.服务治理

1.调用链路生成

一个大型的分布式系统,由大量的服务组成。这些服务之间是如何调用的呢?调用链路是啥?

那就需要基于dubbo做的分布式系统中,对各个服务之间的调用自动记录下来,然后自动将各个服务的依赖关系和调用链路生成出来,做成一张图,大家可以看到的

 

2.服务访问压力以及时长统计

需要自动统计各个接口之间的访问次数和调用延迟,而且要分成两个级别。一个级别是接口粒度,就是每个服务额每个接口每天被调用的次数,TP50 ,TP90,TP99,三个档次的请求延迟分别是多少;第二个级别是从源头入口,一个完整的请求链路经过几十个服务之后,完成一次请求,每天全链路走多少次,全链路请求延迟的TP50,TP90,TP99

分别是多少?

这些东西都搞定以后,后面才看当前系统的压力到底在哪里?如何来扩容和优化?

 

3.其他的

服务分层(避免循环依赖),调用链路失败监控和报警,服务鉴权,每个服务 的可用性的监控

 

2.服务降级

比如服务A调用服务B,结果服务B挂掉了,服务A重试几次调用服务B,还是不行,直接降级,走一个备用逻辑,给用户返回响应

问题7:分布式服务接口的幂等性如何设计?

所谓幂等性,就是说一个接口,多次发起同一个请求,你这个接口得保证结果是准确的,比如不能多扣款,不能多插入一条数据,不能将统计值多加1。这就是幂等性。

 

保证幂等性的手段如下:

  • 对于每次请求必须有一个全局唯一的标识

  • 每次处理完请求后,必须有一个记录标识这个请求处理完了,比如常见的方案是在mysql 中记录个啥状态,比如支付之前记录一条这个订单的支付流水,而且支付流水采用orderid 作为 唯一键。只有成功插入这个支付流水,才可以执行实际的支付扣款。

  • 每次接收到了请求需要进行判断之前是否处理过的逻辑处理,比如说吗,如果有一个订单已经支付了,就已经有一个支付流水了,那么如果重复发送请求,则此时先插入支付流水,然而orderid已经存在了,唯一键约束生效,报错插入失败,然后你就不用扣款了

  • 一般生产上可以用redis中的 set来保证幂等

 

问题8:如何设计一个类似于Dubbo的rpc框架

简单梳理一下思路:

  1. 上来就要把服务到注册中心注册,就应该有一个注册中心,保留各个服务的地址信息,就可以用zookeeper来做。

  2. 然后消费者要去注册中心去拿服务信息,每个服务可能会存在于有多台机器

  3. 接着你就该发起一次请求,如何发呢? 基于动态代理,面向接口获取到一个动态代理,这个动态代理就是本地接口的一个代理,然后这个代理会找到服务对应的机器地址

  4. 然后找哪台机器发送请求? 这里会有一个负载均衡算法,比如可以使用随机轮训的算法

  5. 找到一台机器,如何发送给他? 可以用netty,nio的方式,第二个问题发送什么格式?可以是序列化格式或者json格式等

  6. 服务起那边一样,需要针对你的服务生成一个动态代理,监听某个网络端口,然后代理你本地的服务代码,接收到请求,就代理本地的服务方法。

你可能感兴趣的:(Java)