分布式专题|Dubbo面试必知必会

2020-12-24 10:39:04 浏览数 (1)

dubbo常见的两种注册中心

dubbo目前支持了zookeeper、redis、consul、etcd3、eureka等注册中心,我这里主要讲下常见的两种注册中心redis,zookeeper

Redis注册中心

使用redis作为注册中心,主要使用到了其map数据结构和发布/订阅特性

  • dubbo到底在redis的map中存储了什么?
    • key为服务名和类型 如:"/dubbo/lezai.dubbo.server.UserService/providers"
    • 整个key就是dubbo服务的url地址
    • value为服务的有效时间,过期删除,一般这个数据都会有心跳服务去刷新这个时间,用来处理脏数据
  • dubbo如何使用redis的发布/订阅实现服务的注册和解注册?
    • 通过事件的值来区分不同的事件类型:register,unregister
    • 消费者直接订阅服务提供者注册时提供的key,用key作为主题,提供者的服务如果挂机,将会从此主题发送一个unregister/register,消费者会把重新获取提供者列表,然后进行订阅
  • 调用过程
    1. 服务提供方启动时,向 Key:/dubbo/com.lezai.userService/providers 下,添加当前提供者的地址
    2. 并向 Channel:/dubbo/com.lezai.userService/providers 发送 register 事件
    3. 服务消费方启动时,从 Channel:/dubbo/com.lezai.userService/providers 订阅 register 和 unregister 事件
    4. 并向 Key:/dubbo/com.lezai.userService/providers 下,添加当前消费者的地址
    5. 服务消费方收到 register 和 unregister 事件后,从 Key:/dubbo/com.lezai.userService/providers 下获取提供者地址列表
    6. 服务监控中心启动时,从 Channel:/dubbo/* 订阅 register 和 unregister,以及 subscribe和unsubsribe事件
    7. 服务监控中心收到 register 和 unregister 事件后,从 Key:/dubbo/com.foo.BarService/providers下获取提供者地址列表
    8. 服务监控中心收到 subscribe 和 unsubsribe 事件后,从 Key:/dubbo/com.lezai.userService/consumers 下获取消费者地址列表
  • 当服务提供者突然宕机,状态能立即变更么?dubboe在注册中心、消费者和提供者之间建立了心跳机制,每隔30秒更新一次有效期

zookeeper注册中心

相对于redis做注册中心,zookeeper就更加灵活了,通过使用zookeeper的watch机制、临时节点特性、树结构能完成所有功能,我们先看下,dubbo在zookeeper中创建的节点分布图:

  • 服务提供者(provider)在初始化启动时,会在zookeeper中的dubbo节点下的服务节点下(com.lezau.UserServie)的providers节点下创建一个子节点并且写入自己的URL地址,路径(目录)为/dubbo/com.lezau.UserServie/providers/,该路径(目录)下的子节点均为服务提供者。此时这些节点均为临时节点,因为临时节点的生命周期和客户端会话相关,所以一旦服务提供者所在的机器出现故障导致无法提供服务时,该临时节点就会从zookeeper中删除。
  • 服务消费者(consumer)初始化启动时,会订阅/dubbo/com.lezau.UserServie/providers/路径(目录)下的提供者的URL地址,并在/dubbo/com.lezau.UserServie/consumers/路径(目录)下创建临时子节点并且写入自己的URL地址,该路径(目录)下的子节点均为服务消费者。
  • 注册中心由于服务提供者、消费者、注册中心之间是长连接,注册中心能感知服务提供者宕机,会通知消费者。因为监控中心是dubbo服务治理体系中重要的一部分,它需要知道服务提供者和消费者的所有情况变化情况,所以它在启动时会在路径(目录)为/dubbo/com.lezau.UserServie/的服务节点上(com.lezau.UserServie)注册一个watcher来监听子节点的变化,即订阅/dubbo/com.lezau.UserServie/路径(目录)下的所有提供者和消费者URL地址,因此它也能感知到服务提供者的宕机。
  • 特性dubbo中的zookeeper还有个特性就是zookeeper的节点结构设计,它以服务名和类型,也就是dubbo/com.lezau.UserServie/类型 作为节点路径(目录),符合dubbo的订阅和通知的需求,保证了以服务为粒度的变更通知,通知范围易于控制,所以即使服务提供者和消费者频繁变,对zookeeper的性能也不会造成多大的影响。

调用模块支持的负载均衡算法

dubbo支持的均衡算法有随机、轮询、最少活跃调用度、一致性hash,这里主要讲下其中两种算随机和一致性hash,这也是面试过程中必问的两大算法:

随机

定义:按权重设置随机概率,dubbo默认使用这种算法 实现思想:如果一组服务提供者的权重分别为1,10,6,那么我如何能保证第二台机器命中概率最大呢?

何为一致性hash?

  • 一致性hash解决了什么问题?
    • 数据聚集(使数据分散开来)
    • 对机器的扩容或宕机提供了较好的处理机制,防止全部重新进行hash
  • 一致性hash算法的原理
  1. 首先设定2^31 个节点
  2. 然后对所有机器的标识进行hash然后对2^31 进行取模,得到每个机器在这2^31 个节点的位置
  3. 用户发起请求,会根据请求的标识进行hash,同样对2^31 个节点数进行取模,然后得到一个位置
  4. 再从这个位置顺时针进行查找,将找到的第一个机器节点作为命中点,将会使用该机器进行处理
  5. 如上图,请求将会交给NodeA处理

在这里插入图片描述

  1. 如果这个时候新增了机器或者机器宕机了,将只会影响到一部分的数据,如上图,请求将会交给NodeB处理。只会影响D和A节点的请求

总结:以上方法解决了由于机器宕机或新加入机器产生的全局hash的问题

这时你也许会产生这样的疑问,如果我的机器很少,但是2^31 的节点又那么多,会不会导致好多数据都会落在同一个节点上呢,这也就是数据倾斜的问题,面试必问的,我们看下作者是如何解决这个问题的:

  • 如何解决数据倾斜的问题:
    • 用户可以自己设定总的节点数,不需要设置默认的2^31 个,视业务情况决定
    • 虚拟节点映射:假如现在有100个真实机器,分别散落在不同位置上,这个时候总的节点数设置的是2^31 ,那么一致性hash算法会虚拟出100个或更多个机器,分别散落在不同位置,然后这些虚拟机器会分别指向这个100个真实机器。

调用模块支持的容错策略

  1. 失败自动切换:调用失败后基于retries=“2”属性重试其它服务器,默认容错策略
  2. 快速失败:快速失败,只发起一次调用,失败立即报错。
  3. 忽略失败:失败后忽略,不抛出异常给客户端。
  4. 失败重试:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作
  5. 并行调用:只要一个成功即返回,并行调用指定数量机器,可通过forks="2"来设置最大并行数。
  6. 广播调用:广播调用所有提供者,逐个调用,任意一台报错则报错

Dubbo如何防止服务链接被盗用?

消费者每次调用服务端的时候都会从zookeeper中拿到目标服务的地址链接:dubbo://xxx 然后直接去调用目标服务,如果被非法用户拿到这个地址那么不就可以直接调用了么,那么如何避免链接被盗用呢?dubbo中提供了token机制,用来保护链接被盗用:

在这里插入图片描述

dubbo的泛化提供与引用知道使用来干嘛的么?

有很多同学在面试过程中被问到什么是泛化,很多同学第一感觉以为是在问泛型吧!

  • 泛化提供 泛化提供试用在服务提供方的,是指不通过接口的方式直接将服务暴露出去。通常用于Mock框架或服务降级框架实现。代码中可以使用GenericService注入
  • 泛化引用 通常用在消费端, 是指不通过常规接口的方式去引用服务,通常用于测试框架,消费端不用去依赖服务端提供的接口,自己直接使用全类名进行调用
代码语言:javascript复制
   //弱类型接口名  
        reference.setInterface("com.tuling.teach.service.DemoService");
        //声明为泛化接口
   reference.setGeneric(true);

如何实现dubbo调用链追踪?

在dubbo提供了隐式参数来实现调用链追踪的需求,该参数式可以从消费端传递到服务端的,存在于整个调用链中,设置和获取方法如下:

代码语言:javascript复制
RpcContext.getContext().setAttachment("index","1");
//隐式传参,后面的远程
Stringindex=RpcContext.getContext().getAttachment("index");

因为在dubbo调用过程中,dubbo会维护一个rpc本地线程map来存储这些参数, 但是如果你不是直接调用目标服务,而是中间多了一个服务,然后由这个服务去调用目标服务,这个时候目标服务是拿不到这个参数,例如:A 设置了一个参数,然后调用C,然后C再去调用B,这样B是拿不到这个参数的,C能拿到。

dubbo调用过程的链路

0 人点赞