dubbo基础看这里,本文参考敖丙的dubbo-扬帆起航.pdf,以及Dubbo面试28题 – mikechen的互联网架构
目录
Dubbo SPI
为什么要有 RPC,HTTP 不好么?
HTTP 只是传输协议,协议只是规范了一定的交流格式。RPC 对比的是本地过程调用,是用来作为分布式系统之间的通信,它可以用 HTTP 来传输,也可以基于 TCP 自定义协议传输。(一般都是TCP )。
dubbo中的角色
节点 | 角色说明 |
---|---|
Consumer | 需要调用远程服务的服务消费方 |
Registry | 注册中心 |
Provider | 服务提供方 |
Container | 服务运行的容器 |
Monitor | 监控中心 |
- 首先服务提供者 Provider 启动然后向注册中心注册自己所能提供的服务。
- 服务消费者 Consumer 启动向注册中心订阅自己所需的服务。然后注册中心将提供者元信息通知给 Consumer, 之后 Consumer 因为已经从注册中心获取提供者的地址,因此可以通过负载均衡选择一个 Provider 直接调用 。
- 之后服务提供方元数据变更的话注册中心会把变更推送给服务消费者。
- 服务提供者和消费者都会在内存中记录着调用的次数和时间,然后定时的发送统计数据到监控中心。
dubbo架构设计:
- service:业务层,主要是我们开发的业务逻辑;
- config:配置信息;
- proxy:服务提供者/消费者都会生成一个代理类,由代理进行远程调用和返回结果;
- register:注册层,封装了服务注册与发现;
- cluster:路由层,负责具体调用的节点以及调用失败的容错;
- monitor:监控层,监控调用次数;
- protocol:远程调用层,封装rpc调用;
- exchange:封装响应模型;
- transport:网络传输层;
- serialize:序列化与反序列化。
服务暴露与服务引入的流程(背一下):
- Provider(提供者)绑定指定端口并启动服务;
- 指供者连接注册中心,并发本机IP、端口、应用信息和提供服务信息发送至注册中心存储;
- Consumer(消费者),连接注册中心,并发送应用信息、所求服务信息至注册中心;
- 注册中心根据消费者所求服务信息匹配对应的提供者列表发送至Consumer应用缓存;
- Consumer在发起远程调用时基于缓存的消费者列表择其一发起调用;
- Provider状态变更会实时通知注册中心、在由注册中心实时推送至Consumer。
dubbo的服务暴露、引入、消费具体原理
服务暴露:
- 服务的暴露起始于 Spring IOC 容器刷新完毕之后,会根据配置参数组装成 URL, 然后根据 URL 的参数来进行本地或者远程调用。
- 通过 proxyFactory.getInvoker,利用 javassist 来进行动态代理,封装真的实现类,然后再通过 URL 参数选择对应的协议来进行 protocol.export,默认是 Dubbo 协议。
- 在第一次暴露的时候会调用 createServer 来创建 Server,默认是 NettyServer。
- 然后将 export 得到的 exporter 存入一个 Map 中,供之后的远程调用查找,然后会向注册中心注册提供者的信息。
服务引入:
- 服务的引入时机有两种,第一种是饿汉式,第二种是懒汉式。
- 饿汉式就是加载完毕就会引入,懒汉式是只有当这个服务被注入到其他类中时启动引入流程,默认是懒汉式。
- 需要先根据配置参数组装成 URL ,构建 RegistryDirectory 向注册中心注册消费者的信息,并且订阅提供者、配置、路由等节点。
- 得知提供者的信息之后会进入 Dubbo 协议的引入,会创建 Invoker,期间会包含 NettyClient来进行远程通信,最后通过 Cluster 来包装 Invoker,默认是 FailoverCluster,最终返回代理类。
Invoker
Invoker 就是 Dubbo 对远程调用的抽象。在服务提供方,Invoker用于调用服务提供类。在服务消费方,Invoker用于执行远程调用。
服务调用的流程:
- 调用某个接口的方法会调用之前生成的代理类,然后会从 cluster 中经过路由的过滤、负载均衡机制选择一个 invoker 发起远程调用,此时会记录此请求和请求的 ID 等待服务端的响应。
- 服务端接受请求之后会通过参数找到之前暴露存储的 map,得到相应的 exporter ,然后最终调用真正的实现类,再组装好结果返回,这个响应会带上之前请求的 ID。
- 消费者收到这个响应之后会通过 ID 去找之前记录的请求,然后找到请求之后将响应塞到对应的 Future 中,唤醒等待的线程,最后消费者得到响应。
dubbo集群负载均衡策略
包括随机、轮询、最少活跃、一致性哈希(相同参数请求会发到同一个提供者。一台机器宕机,可以根据虚拟节点分摊到其他提供者) ,默认是随机调用(选取提供者)。
dubbo与Spring的关系?
Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。
dubbo的集群容错方案有哪些?
- Failover Cluster(默认)
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。
- Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
- Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
- Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=”2″ 来设置最大并行数。
- Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。
dubbo超时时间怎样设置?
服务提供者和消费者都可以设置,不过一般推荐是提供者多做一些配置。Dubbo调用服务不成功/超时,默认会重试两次。
dubbo在安全机制方面是如何解决?
Dubbo通过Token令牌防止用户绕过注册中心直连,然后在注册中心上管理授权。Dubbo还提供服务黑白名单,来控制服务所允许的调用方。
SPI机制 DubboSPI机制是啥?
Java SPI :SPI(Service Provider Interface)主要用于框架中,框架定义好接口,不同的使用者有不同的需求,因此需要有不同的实现,而 SPI 就通过定义一个特定的位置,Java SPI 约定在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,然后文件里面记录的是此 jar 包提供的具体实现类的全限定名。
然后读取文件里面的内容去进行实现类的加载与实例化,做到了灵活的替换具体的实现类。。
Java的SPI方式存在很明显的缺点,查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费。因此 Dubbo 就自己实现了一个 SPI,给每个实现类配了个名字,通过名字去文件里面找到对应的实现类全限定名然后加载实例化,按需加载。
Dubbo SPI
因此 Dubbo 就自己实现了一个 SPI,按需加载的话首先你肯定得给个名字(key),通过名字去文件里面找到对应的实现类全限定名然后加载实例化即可。
Dubbo 就是这样设计的,配置文件里面存放的是键值对,我截一个 Cluster 的配置。
并且 Dubbo SPI 除了可以按需加载实现类之外,增加了 IOC 和 AOP 的特性,还有个自适应扩展机制。
我们先来看一下 Dubbo 对配置文件目录的约定,不同于 Java SPI ,Dubbo 分为三类目录。
-
META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI 。
-
META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件。
-
META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件。、、
Dubbo SPI主要是通过extensionLoader作为入口的。先通过接口类找到一个ExtensionLoader ,然后再通过 ExtensionLoader.getExtension(name) 得到指定名字的实现类实例。
具体过程:
如何拓展dubbo中的默认实现?
比如说我们想要实现自己的负载均衡策略:
- 我们创建对应的实现类
XxxLoadBalance
实现LoadBalance
接口或者AbstractLoadBalance
类。
package com.xxx;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.RpcException;
public class XxxLoadBalance implements LoadBalance {
public <T> Invoker<T> select(List<Invoker<T>> invokers, Invocation invocation)
throws RpcException {
// ...
}
}
- 将这个是实现类的路径写入到
resources
目录下的META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance
文件中即可。
//目录结构
src
|-main
|-java
|-com
|-xxx
|-XxxLoadBalance.java (实现LoadBalance接口)
|-resources
|-META-INF
|-dubbo
|-org.apache.dubbo.rpc.cluster.LoadBalance (这里是一个纯文本文件,内容:xxx=com.xxx.XxxLoadBalance)
org.apache.dubbo.rpc.cluster.LoadBalance
(这里是一个纯文本文件,内容:xxx=com.xxx.XxxLoadBalance)
xxx=com.xxx.XxxLoadBalance
其他还有很多可供扩展的选择,你可以在官方文档@SPI 扩展实现这里找到。
其他tips:
- 注册中心的作用了解么?
注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互。
- 服务提供者宕机后,注册中心会做什么?
注册中心会立即推送事件通知消费者。
- 注册中心和监控中心都宕机的话,服务都会挂掉吗?
不会。两者都宕机也不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表。注册中心和监控中心都是可选的,服务消费者可以直连服务提供者。