Dubbo基本见解

1、dubbo主要角色

  • Provider: 暴露服务的服务提供方。
  • Consumer: 调用远程服务的服务消费方。
    a.订阅注册中心,注册中心广播服务变更,第一次会主动全量pull所有信息,后面增量会被动监听注册中心的消息
    b.本地会缓存注册信息,注册中心挂了之后会拉本地缓存,保证服务之间可用。
  • Registry: 服务注册与发现的注册中心。
    1、根节点:dubbo
       2、一级子节点:提供服务的服务名(接口路径)
       3、二级子节点:固定的四个子节点:分别为:consumers、configurators、routers、providers
       4、三级内容注册信息存储的是临时节点,当服务变更会广播
  • Monitor: 统计服务的调用次调和调用时间的监控中心。
  • Container: 服务运行容器。
 

2、dubbo服务暴露(接口暴露)

服务的暴露起始于 Spring IOC 容器刷新完毕之后,会根据配置参数组装成 URL, 然后根据 URL 的参数来进行本地或者远程调用。
会通过 proxyFactory.getInvoker,利用 javassist 来进行动态代理,封装真的实现类,然后再通过 URL 参数选择对应的协议来进行 protocol.export,默认是 Dubbo 协议。
在第一次暴露的时候会调用 createServer 来创建 Server,默认是 NettyServer。
然后将 export 得到的 exporter 存入一个 Map 中,供之后的远程调用查找,然后会向注册中心注册提供者的信息。
 

  a、provider服务暴露过程

  • 1、链接zk
    Initiating client connection, connectString=zookeeper-1.fcdc.com:2181,zookeeper-2.fcdc.com:2181,zookeeper-3.fcdc.com:2181 sessionTimeout=60000 watcher=org.apache.curator.ConnectionState@19ad75e5
  • 2、启动Netty服务(用于可以消费者通讯)
     [DUBBO] Start NettyServer bind /0.0.0.0:20880, export /172.30.34.5:20880, dubbo version: 2.6.12, current host: 172.30.34.5
  • 3、接口暴露在本地服务
   spirng加载完再暴露服务
[DUBBO] The service ready on spring started. service:【接口路径】

[DUBBO] Export dubbo service 【接口路径】 to local registry, dubbo version: 2.6.12, current host: 172.30.34.5
  • 4、接口暴露在远程服务,把接口注册到zk
       [DUBBO] Export dubbo service 【接口路径】 to url dubbo://172.30.34.5:20880/【接口路径】?anyhost=true&application=cmp-biz&bean.name=ServiceBean:【接口路径】&bind.ip=172.30.34.5&bind.port=20880&default.register=false&dubbo=2.0.2&generic=false&interface=【接口路径】&methods=【方法】
     
     [DUBBO] Register dubbo service 【接口路径】 url dubbo://172.30.34.5:20880/【接口路径】?anyhost=true&application=cmp-biz&bean.name=ServiceBean:【接口路径】&bind.ip=172.30.34.5&bind.port=20880&default.register=false&dubbo=2.0.2&generic=false&interface=c【接口路径】&methods=【方法】&pid=10&revision=2.7.2-SNAPSHOT&side=provider&timestamp=1709207426440 to registry registry://  zookeeper-1.fcdc.com:2181/com.alibaba.dubbo.registry.RegistryService?application=cmp-biz&backup=zookeeper-2.fcdc.com:2181,zookeeper-3.fcdc.com:2181&dubbo=2.0.2&pid=10&registry=zookeeper&timestamp=1709207426440,   dubbo version: 2.6.12, current host: 172.30.34.5
  • 5、订阅监听接口变更(订阅zk的[configurators]节点)
    [DUBBO] Subscribe: provider://172.30.34.5:20880/【接口路径】?anyhost=true&application=cmp-biz&bean.name=ServiceBean:【接口路径】&category=configurators&check=false&default.register=false&dubbo=2.0.2&generic=false&interface=【接口路径】&methods=【方法】&pid=10&revision=2.7.2-SNAPSHOT&side=provider&timestamp=1709207427050, dubbo version: 2.6.12, current host: 172.30.34.5
                      
    [DUBBO] Notify urls for subscribe url provider://172.30.34.5:20880/【接口路径】?anyhost=true&application=cmp-biz&bean.name=ServiceBean:【接口路径】&category=configurators&check=false&default.register=false&dubbo=2.0.2&generic=false&interface=【接口路径】&methods=【方法】&pid=10&revision=2.7.2-SNAPSHOT&side=provider&timestamp=1709207427050, urls: [empty://172.30.34.5:20880/【接口路径】?anyhost=true&application=cmp-biz&bean.name=ServiceBean:【接口路径】&category=configurators&check=false&default.register=false&dubbo=2.0.2&generic=false&interface=【接口路径】&methods=【方法】&pid=10&revision=2.7.2-SNAPSHOT&side=provider&timestamp=1709207427050], dubbo version: 2.6.12, current host: 172.30.34.5
   最后就是提供者接口暴露的实现过程
  

  b、consumer服务加载过程

  • 1、链接zk
    Initiating client connection, connectString=zookeeper-1.fcdc.com:2181,zookeeper-2.fcdc.com:2181,zookeeper-3.fcdc.com:2181 sessionTimeout=60000 watcher=org.apache.curator.ConnectionState@19ad75e5
  • 2、启动Netty服务(用于可以消费者通讯)
    [DUBBO] Start NettyServer bind /0.0.0.0:20880, export /172.30.34.5:20880, dubbo version: 2.6.12, current host: 172.30.34.5
  • 3、注册到zk上的consumers节点
[DUBBO] Register: consumer://172.30.17.23/com.api.cabinet.CleanTaskFacade?application=cmp-biz&category=consumers&check=false&default.check=false&default.timeout=5000&dubbo=2.0.2&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&side=consumer&timeout=5000&timestamp=1709207412512, dubbo version: 2.6.12, current host: 172.30.17.23                
  • 4、订阅接口zk上providers,configurators,routers节点
    [DUBBO] Subscribe: consumer://172.30.17.23/com.api.cabinet.CleanTaskFacade?application=cmp-biz&category=providers,configurators,routers&default.check=false&default.timeout=5000&dubbo=2.0.2&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&side=consumer&timeout=5000&timestamp=1709207412512, dubbo version: 2.6.12, current host: 172.30.17.23
    
  • 5、监听providers,configurators,routers(第一次拉取全量)
      • 把服务的url信息缓存到本地文件到C: Uers/bobo/.dubbo/dubbo-registry-ipxxx.cache(线程来处理)
      • 然后加载到invoker里面,刷新Map<String,Invoker<T>>urlInvokerMap 缓存对象
      • 然后加入集群路由cluster.join(directory)
      • ProxyFactory$Adpative.getProxy创建代理对象
      • 用jdk字段带的InvocationHandler创建对象
    [DUBBO] Notify urls for subscribe url consumer://172.30.17.23/com.api.cabinet.CleanTaskFacade?application=cmp-biz&category=providers,configurators,routers&default.check=false&default.timeout=5000&dubbo=2.0.2&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&side=consumer&timeout=5000&timestamp=1709207412512, urls: [dubbo://172.30.16.14:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,confirmTaskUnlock,realCleanTaskApply&pid=27&retries=0&side=provider&threads=800&timestamp=1705580588547, dubbo://172.30.47.23:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&retries=0&side=provider&threads=800&timestamp=1705580595124, dubbo://172.30.35.8:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,confirmTaskUnlock,realCleanTaskApply&pid=10&retries=0&side=provider&threads=800&timestamp=1709126627225, dubbo://172.30.48.23:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,confirmTaskUnlock,realCleanTaskApply&pid=10&retries=0&side=provider&threads=800&timestamp=1708851066607, dubbo://172.30.51.32:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,confirmTaskUnlock,realCleanTaskApply&pid=10&retries=0&side=provider&threads=800&timestamp=1705580582842, dubbo://172.30.56.29:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&retries=0&side=provider&threads=800&timestamp=1705580583481, dubbo://172.30.37.14:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&retries=0&side=provider&threads=800&timestamp=1709126632152, dubbo://172.30.60.17:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&retries=0&side=provider&threads=800&timestamp=1709126643556, dubbo://172.30.54.22:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,confirmTaskUnlock,realCleanTaskApply&pid=10&retries=0&side=provider&threads=800&timestamp=1709126635717, dubbo://172.30.15.20:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&retries=0&side=provider&threads=800&timestamp=1705580586769, dubbo://172.30.25.32:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&retries=0&side=provider&threads=800&timestamp=1705580587619, dubbo://172.30.58.7:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,confirmTaskUnlock,realCleanTaskApply&pid=10&retries=0&side=provider&threads=800&timestamp=1709126631834, dubbo://172.30.49.22:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&retries=0&side=provider&threads=800&timestamp=1709126638552, dubbo://172.30.59.13:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&retries=0&side=provider&threads=800&timestamp=1709126628743, dubbo://172.30.53.17:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,confirmTaskUnlock,realCleanTaskApply&pid=10&retries=0&side=provider&threads=800&timestamp=1705580599362, dubbo://172.30.31.16:20880/com.api.cabinet.CleanTaskFacade?anyhost=true&application=cabinet-state-server&bean.name=com.api.cabinet.CleanTaskFacade&default.register=false&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=11&retries=0&side=provider&threads=800&timestamp=1709126631505, empty://172.30.17.23/com.api.cabinet.CleanTaskFacade?application=cmp-biz&category=configurators&default.check=false&default.timeout=5000&dubbo=2.0.2&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&side=consumer&timeout=5000&timestamp=1709207412512, empty://172.30.17.23/com.api.cabinet.CleanTaskFacade?application=cmp-biz&category=routers&default.check=false&default.timeout=5000&dubbo=2.0.2&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&side=consumer&timeout=5000&timestamp=1709207412512], dubbo version: 2.6.12, current host: 172.30.17.2
  • 6、代理到本地
    [DUBBO] Refer dubbo service com.api.cabinet.CleanTaskFacade from url zookeeper://zookeeper-1.fcdc.com:2181/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=cmp-biz&bean.name=com.api.cabinet.CleanTaskFacade&check=false&default.check=false&default.register=false&default.timeout=5000&dubbo=2.0.2&generic=false&interface=com.api.cabinet.CleanTaskFacade&methods=realCleanTaskCancel,realCleanTaskApply,confirmTaskUnlock&pid=10&register.ip=172.30.17.23&remote.timestamp=1705580588547&retries=0&side=consumer&timeout=5000&timestamp=1709207412512, dubbo version: 2.6.12, current host: 172.30.17.23
  下面是代理接口过程
 

3、 dubbo的SPI机制

  Java SPI:SPI 是 Service Provider Interface,主要用于框架中,框架定义好接口,不同的使用者有不同的需求,因此需要有不同的实现,而 SPI 就通过定义一个特定的位置,Java SPI 约定在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,然后文件里面记录的是此 jar 包提供的具体实现类的全限定名。
   Dubbo SPI: Dubbo 就自己实现了一个 SPI,给每个实现类配了个名字,通过名字去文件里面找到对应的实现类全限定名然后加载实例化,按需加载。按照key-value方式存储
  Dubbo SPI 除了可以按需加载实现类之外,增加了 IOCAOP 的特性,还有个自适应扩展机制。
  我们先来看一下 Dubbo 对配置文件目录的约定,不同于 Java SPI ,Dubbo 分为了三类目录。
META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI 。
META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件。
META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件。
  过程如图:
总结:Java SPI 会一次加载和实例化所有的实现类。
而 Dubbo SPI 则自己实现了 SPI,可以通过名字实例化指定的实现类,并且实现了 IOC 、AOP 与 自适应扩展 SPI 。
 

4、dubbo通讯

  1. dubbo默认是使用单一长连接
        即消费者与每个服务提供者建立一个单一长连接,即如果有消费者soa-user1,soa-user2,提供者soa-account三台,则每台消费者user都会与3台account建立一个连接,结果是每台消费者user有3个长连接到分别到3台提供者,每台提供者account维持到soa-user1和soa-user2的2个长连接。
        dubbo这么设计的原因是,一般情况下因为消费者是在请求链路的上游,消费者承担的连接数以及并发量都是最高的,他需要承担更多其他的连接请求,而对提供者来说,承担的连接只来于消费者,所以每台提供者只需要承接消费者数量的连接就可以了,dubbo面向的就是消费者数量远大于服务提供者的情况
     
  2. dubbo 缺省协议采用单一长连接,底层实现是 Netty 的 NIO 异步通讯机制
  3. Dubbo 实现了以下几种调用方式
    • 同步调用(默认)
    • 异步调用
    • 参数回调
    • 事件通知
  • 同步调用(异步转同步)
      Dubbo 的底层 IO 操作都是异步的。基于NIO非阻塞实现的,Consumer 端发起调用后,得到一个 Future 对象。对于同步调用,业务线程通过timeout则是 Consumer 端定义的超时时间。当结果返回后,会设置到此 Future,并唤醒阻塞的业务线程;当超时时间到结果还未返回时,业务线程将会异常返回。
      异步转同步原理:
      业务线程DefaultFuture.java里面定义一下重要变量,
    // 存储netty通讯管道,key=全量id
    private static final Map<Long, Channel> CHANNELS = new ConcurrentHashMap<Long, Channel>();
    
    // 存储通讯时的DefaultFuture,key=全量id
    private static final Map<Long, DefaultFuture> FUTURES = new ConcurrentHashMap<Long, DefaultFuture>();
    
    // 读写锁
    private final Lock lock = new ReentrantLock();
    // 信号量,done.await()使当前线程等待(异步转同步关键),done.signal()释放信号线程会重新获得锁
    private final Condition done = lock.newCondition();
    // 响应实体类,里面会有全局id,在请求和响应都带参
    private volatile Response response;
当一个请求发出后,currentClient.request(inv, timeout).get()会异步等待响应结果,具体实现主要是依赖一个while死循环done.await使当前线程等待,等待response赋值然后
然后响应返回response之后,会根据response的全局id拿到DefaultFuture,然后DefaultFuture.doReceived(response)会去赋值response,并且done.signal()释放信号线程会重新获得锁,使其做到异步同步返回结果:
 
public static void received(Channel channel, Response response) {
try {
        // 根据id拿DefaultFuture
DefaultFuture future = FUTURES.remove(response.getId());
        if (future != null) {
future.doReceived(response);
        } else {
            logger.warn("The timeout response finally returned at "
                    + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))
+ ", response " + response
                    + (channel == null ? "" : ", channel: " + channel.getLocalAddress()
+ " -> " + channel.getRemoteAddress()));
        }
    } finally {
        CHANNELS.remove(response.getId());
    }
}
 
  • 异步调用
      NIO future主动获取结果,返回结果放在RpcContext中,判断是异步  后面是可以根据上下文获取到结果
     
  • 响应超时
      针对请求响应超时,dubbo的实现机制是,在DefaultFuture启动一个守护线程循环监听是否超时,循环睡眠时间Thread.sleep(30),代码如下:
    public class DefaultFuture implements ResponseFuture {
    
        // 每个DefaultFuture开启一个线程判断是否超时
        static {
    Thread th = new Thread(new RemotingInvocationTimeoutScan(), "DubboResponseTimeoutScanTimer");
            th.setDaemon(true);
            th.start();
        }
     }
 
最后思考两个问题:
问题一:dubbo是如何异步转同步的,怎么等待?
请求后,dubbo会在当前线程while死循环等到response返回赋值,其中done.await(timeout, TimeUnit.MILLISECONDS))方法会使当前线程超时等待,直到拿到结果或者线程超时
 
问题二: dubbo既然异步,是如何保证多线程获取的response一致?
dubbo在通讯的时候会生成一个全局id,封装在request实体中,在请求传参过去并且以id为key存储DefaultFuture到缓存ConcurrentHashMap中。
然后server端响应的时候会把全量id响应回来,client端会根据id获取DefaultFuture处理,这样做到多线程下拿到结果是一致的。
 

5、dubbo的@Adaptive-自适应扩展机制

  • dubbo为什么要设计adaptive?
adaptive设计的目的是为了识别固定已知类和扩展未知类。
  • 注解在类上和注解在方法上的区别?
 
注解在扩展点实现类上:
代表人工实现,实现一个装饰类(设计模式中的装饰模式),它主要作用于固定已知类,目前整个系统只有2个,AdaptiveCompiler、AdaptiveExtensionFactory。
  • a. 为什么AdaptiveCompiler这个类是固定已知的? 因为整个框架仅支持Javassist和JdkCompiler;
  • b. 为什么AdaptiveExtensionFactory这个类是固定已知的? 因为整个框架仅支持2个objFactory,一个是spi,另一个是spring; ExtensionLoader.getAdaptiveExtension方法会直接返回这个类的实例

注解在扩展点接口方法上

代表自动生成和编译一个动态的Adpative类,含有@Adaptive的方法中都可以根据方法参数动态获取各自需要真实的扩展点。它主要是用于SPI,因为spi的类是不固定、未知的扩展类,所以设计了动态$Adaptive类; ExtensionLoader.getAdaptiveExtension方法会返回动态编译生成的$Adaptive
 
例如: Protocol的spi类有injvm、dubbo、registry、filter、listener等很多未知扩展类,ExtensionLoader.getAdaptiveExtension会动态编译Protocol$Adaptive的类,再通过在动态累的方法中调用ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(spi类);来提取对象。
 
 
  • Adaptive加载流程
在dubbo中一般会首先通过ExtensionLoader.getAdaptiveExtension获取Adaptive扩展。这个方法会首先在扩展点接口的所有实现类中查找类上是否有含有@Adaptive注解,如果有这样的类直接返回该类的实例,如果没有则会查找扩展点接口的方法是否有@Adaptive注解并动态编译一个类实现该接口并扩展这些含有@Adaptive注解的方法。
 

6、dubbo的编码解码

由于dubbo是基于netty的tcp通讯,就会出现tcp的粘包的问题,dubbo并没有使用netty的编码解码,是自己实现了一套,基于消息头消息体的包通讯。

  6.1、首先要了解什么是粘包

  1. 发送方原因:
    1. TCP协议默认使用Nagle算法,该算法会在数据包集合达到一定数量(通常是四个)时才一起发送,以减少数据包的数量和网络延迟。如果发送的数据包很小,Nagle算法可能会将这些数据包合并为一个更大的数据包后再发送,这在一定程度上可能导致了粘包现象。(1)(2)(3)
  • 当发送方的缓冲区长度大于网卡的MTU(Maximum Transmission Unit,最大传输单元)时,TCP会按照MTU的大小将数据包分成多个数据包进行发送,这也可能导致粘包现象。(2)(4)
  1. 接收方原因:
    1. 接收方如果在短时间内无法完全处理并清空接收缓冲区中的数据,可能会导致后续的数据包被存储在缓冲区中,进而被错误地组合起来作为单个数据包进行处理。
    2. 应用层的处理速度可能低于TCP接收数据的速度,导致多个数据包被接收并保存在接收缓存中,当应用层尝试读取这些数据包时,它们可能会以粘包的形式呈现给上层应用。(3)
 

  6.2、如何解决粘包问题?

  • 消息的定长,例如定100个字节
  • 就是在包尾增加回车或空格等特殊字符作为切割,典型的FTP协议
  • 将消息分为消息头消息体。例如 dubbo
 

  6.3、client的请求编码

  dubbo是基于消息头消息体解决粘包的,因而消息分为消息头消息体,可以看到源码ExchangeCodec.java的encode()编码,消息头为固定16位字节:
  调用链路
-->NettyCodecAdapter.InternalEncoder.encode
-->DubboCountCodec.encode
-->ExchangeCodec.encode
-->ExchangeCodec.encodeRequest
   -- >DubboCodec.encodeRequestData
 
  dubbo的消息头是一个定长的16个字节。
  • 第1-2个字节 :就是一个固定的数字,特殊标记
  • 第3个字节 :是通讯序列化的标识(默认是Hessian2Serialization序列化)
  • 第4个字节 :响应结果码 (request 没有第四个字节)
  • 第5-12个字节 :请求id:long型8个字节。异步变同步的全局唯ID,用来做consumer和provider的来回通信标记
  • 第13-16个字节 :消息体的长度,也就是消息头+请求数据的长度。
 

6.4、server的响应编码

dubbo是基于消息头消息体解决粘包的,因而消息分为消息头消息体,可以看到源码ExchangeCodec.java的encode()编码,消息头为固定16位字节:
dubbo的消息头是一个定长的16个字节。
  • 第1-2个字节 :就是一个固定的数字,特殊标记
  • 第3个字节 :是通讯序列化的标识(默认是Hessian2Serialization序列化)
  • 第4个字节 :响应结果码 (request 没有第四个字节)
  • 第5-12个字节 :请求id:long型8个字节。异步变同步的全局唯ID,用来做consumer和provider的来回通信标记
  • 第13-16个字节 :消息体的长度,也就是消息头+请求数据的长度。
 

6.5、dubbo处理粘包

dubbo在接受消息时候,通过循环获取数据直到获取到完整的消息体。
com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter.InternalDecoder#messageReceived
 

7、dubbo核心模型Invoker

  • 为什么说Invoker是Dubbo核心模型呢?
Invoker是Dubbo中的实体域,也就是真实存在的。其他模型都向它靠拢或转换成它,它也就代表一个可执行体,可向它发起invoke调用。在服务提供方,Invoker用于调用服务提供类。在服务消费方,Invoker用于执行远程调用。
 
  • 服务提供方的Invoker
在服务提供方中的Invoker是由ProxyFactory创建而来的,Dubbo默认的ProxyFactory实现类为JavassistProxyFactory。
 
JavassistProxyFactory创建了一个继承自AbstractProxyInvoker类的匿名对象,并覆写了抽象方法doInvoke。覆写后的doInvoke 逻辑比较简单,仅是将调用请求转发给了Wrapper类的invokeMethod 方法。以及生成 invokeMethod 方法代码和其他一些方法代码。代码生成完毕后,通过 Javassist 生成 Class 对象,最后再通过反射创建 Wrapper 实例。
 
  • 服务消费方的Invoker
在服务消费方,Invoker用于执行远程调用。Invoker是由 Protocol实现类构建而来。Protocol实现类有很多但是最常用的两个,分别是RegistryProtocol和DubboProtocol。
 
当前的Invoker底层依然是NettyClient,但是此时注册中心是集群搭建模式。所以需要将多个Invoker合并为一个,这里是逻辑合并的。实际上Invoker底层还是会有多个,只是通过一个集群模式来管理。所以暴露出来的就是一个集群模式的Invoker。于是进入Cluster.join方法。
 
Cluster是一个通用代理类,会根据URL中的cluster参数值定位到实际的Cluster实现类也就是FailoverCluster。这里用到了@SPI注解,也就是需要ExtensionLoader扩展点加载机制,而该机制在实例化对象是,会在实例化后自动套上Wapper
 
但是是集群模式所以需要Dubbo中另外一个核心机制——Mock。Mock可以在测试中模拟服务调用的各种异常情况,还可以实现服务降级。在MockerClusterInvoker中,Dubbo先检查URL中是否存在mock参数。(这个参数可以通过服务治理后台Consumer端的屏蔽和容错进行设置或者直接动态设置mock参数值)如果存在force开头,这不发起远程调用直接执行降级逻辑。如果存在fail开头,则在远程调用异常时才会执行降级逻辑。
 
可以说注册中心为集群模式时,Invoker就会外面多包裹一层mock逻辑。是通过Wapper机制实现的。最终可以在调用或者重试时,每次都通过Dubbo内部的负载均衡机制选出多个Invoker中的一个进行调用
 
 
总结一下,在服务提供方Invoker是javassist创建的服务类的实例,可以实现调用服务类内部的方法和修改字段。而在服务消费方的Invoker是基于Netty的客户端。最终通过服务消费方Netty客户端获得服务提供方创建的服务类实例。而后消费方为保护服务类就需要为其创建代理类,这样就可以在不实例化服务类情况下安全有效的远程调用服务类内部方法并且得到具体数据了。

相关面试资料

1、Dubbo是什么?
Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC 分布式服务框架,现已成为 Apache 基金会孵化项目。
面试官问你如果这个都不清楚,那下面的就没必要问了。
官网:http://dubbo.apache.org
2、为什么要用Dubbo?
因为是阿里开源项目,国内很多互联网公司都在用,已经经过很多线上考验。内部使用了 Netty、Zookeeper,保证了高性能高可用性。
使用 Dubbo 可以将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,可用于提高业务复用灵活扩展,使前端应用能更快速的响应多变的市场需求。
下面这张图可以很清楚的诠释,最重要的一点是,分布式架构可以承受更大规模的并发流量。
下面是 Dubbo 的服务治理图。
3、Dubbo 和 Spring Cloud 有什么区别?
两个没关联,如果硬要说区别,有以下几点。
1)通信方式不同
Dubbo 使用的是 RPC 通信,而 Spring Cloud 使用的是 HTTP RESTFul 方式。
2)组成部分不同
4、dubbo都支持什么协议,推荐用哪种?
dubbo://(推荐)
rmi://
hessian://
http://
webservice://
thrift://
memcached://
redis://
rest://
5、Dubbo需要 Web 容器吗?
不需要,如果硬要用 Web 容器,只会增加复杂性,也浪费资源。
6、Dubbo内置了哪几种服务容器?
Spring Container
Jetty Container
Log4j Container
Dubbo 的服务容器只是一个简单的 Main 方法,并加载一个简单的 Spring 容器,用于暴露服务。
7、Dubbo里面有哪几种节点角色?
8、画一画服务注册与发现的流程图
该图来自 Dubbo 官网,供你参考,如果你说你熟悉 Dubbo, 面试官经常会让你画这个图,记好了。
9、Dubbo默认使用什么注册中心,还有别的选择吗?
推荐使用 Zookeeper 作为注册中心,还有 Redis、Multicast、Simple 注册中心,但不推荐。
10、Dubbo有哪几种配置方式?
1)Spring 配置方式
2)Java API 配置方式
11、Dubbo 核心的配置有哪些?
我曾经面试就遇到过面试官让你写这些配置,我也是蒙逼。。
配置之间的关系见下图。
12、在 Provider 上可以配置的 Consumer 端的属性有哪些?
1)timeout:方法调用超时
2)retries:失败重试次数,默认重试 2 次
3)loadbalance:负载均衡算法,默认随机
4)actives 消费者端,最大并发调用限制
13、Dubbo启动时如果依赖的服务不可用会怎样?
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,默认 check="true",可以通过 check="false" 关闭检查。
14、Dubbo推荐使用什么序列化框架,你知道的还有哪些?
推荐使用Hessian序列化,还有Duddo、FastJson、Java自带序列化。
15、Dubbo默认使用的是什么通信框架,还有别的选择吗?
Dubbo 默认使用 Netty 框架,也是推荐的选择,另外内容还集成有Mina、Grizzly。
16、Dubbo有哪几种集群容错方案,默认是哪种?
17、Dubbo有哪几种负载均衡策略,默认是哪种?
18、注册了多个同一样的服务,如果测试指定的某一个服务呢?
可以配置环境点对点直连,绕过注册中心,将以服务接口为单位,忽略注册中心的提供者列表。
19、Dubbo支持服务多协议吗?
Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。
20、当一个服务接口有多种实现时怎么做?
当一个接口有多种实现时,可以用 group 属性来分组,服务提供方和消费方都指定同一个 group 即可。
21、服务上线怎么兼容旧版本?
可以用版本号(version)过渡,多个不同版本的服务注册到注册中心,版本号不同的服务相互间不引用。这个和服务分组的概念有一点类似。
22、Dubbo可以对结果进行缓存吗?
可以,Dubbo 提供了声明式缓存,用于加速热门数据的访问速度,以减少用户加缓存的工作量。
23、Dubbo服务之间的调用是阻塞的吗?
默认是同步等待结果阻塞的,支持异步调用。
Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。
异步调用流程图如下。
24、Dubbo支持分布式事务吗?
目前暂时不支持,后续可能采用基于 JTA/XA 规范实现,如以图所示。
25、Dubbo telnet 命令能做什么?
dubbo 通过 telnet 命令来进行服务治理,具体使用看这篇文章《dubbo服务调试管理实用命令。
telnet localhost 8090
26、Dubbo支持服务降级吗?
Dubbo 2.2.0 以上版本支持。
27、Dubbo如何优雅停机?
Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才会执行。
28、服务提供者能实现失效踢出是什么原理?
服务失效踢出基于 Zookeeper 的临时节点原理。
29、如何解决服务调用链过长的问题?
Dubbo 可以使用 Pinpoint 和 Apache Skywalking(Incubator) 实现分布式服务追踪,当然还有其他很多方案。
30、服务读写推荐的容错策略是怎样的?
读操作建议使用 Failover 失败自动切换,默认重试两次其他服务器。
写操作建议使用 Failfast 快速失败,发一次调用失败就立即报错。
31、Dubbo必须依赖的包有哪些?
Dubbo 必须依赖 JDK,其他为可选。
32、Dubbo的管理控制台能做什么?
管理控制台主要包含:路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡,等管理功能。
33、说说 Dubbo 服务暴露的过程。
Dubbo 会在 Spring 实例化完 bean 之后,在刷新容器最后一步发布 ContextRefreshEvent 事件的时候,通知实现了 ApplicationListener 的 ServiceBean 类进行回调 onApplicationEvent 事件方法,Dubbo 会在这个方法中调用 ServiceBean 父类 ServiceConfig 的 export 方法,而该方法真正实现了服务的(异步或者非异步)发布。
34、Dubbo 停止维护了吗?
2014 年开始停止维护过几年,17 年开始重新维护,并进入了 Apache 项目。
35、Dubbo 和 Dubbox 有什么区别?
Dubbox 是继 Dubbo 停止维护后,当当网基于 Dubbo 做的一个扩展项目,如加了服务可 Restful 调用,更新了开源组件等。
36、你还了解别的分布式框架吗?
别的还有 Spring cloud、Facebook 的 Thrift、Twitter 的 Finagle 等。
37、Dubbo 能集成 Spring Boot 吗?
可以的,项目地址如下。
https://github.com/apache/incubator-dubbo-spring-boot-project
38、在使用过程中都遇到了些什么问题?
Dubbo 的设计目的是为了满足高并发小数据量的 rpc 调用,在大数据量下的性能表现并不好,建议使用 rmi 或 http 协议。
39、你读过 Dubbo 的源码吗?
要了解 Dubbo 就必须看其源码,了解其原理,花点时间看下吧,网上也有很多教程,后续有时间我也会在公众号上分享 Dubbo 的源码。
40、你觉得用 Dubbo 好还是 Spring Cloud 好?
扩展性的问题,没有好坏,只有适合不适合

  

 
posted @ 2024-03-04 16:42  低调人生  阅读(110)  评论(0编辑  收藏  举报