zoukankan      html  css  js  c++  java
  • 浅析dubbo原理和实现 转

    出处: 浅析dubbo原理和实现

    一、Duboo基本概念解释

    Dubbo是一种分布式服务框架。 Webservice也是一种服务框架,但是webservice并不是分布式的服务框架,他需要结合F5实现负载均衡。因此,dubbo除了可以提供服务之外,还可以实现软负载均衡。它还提供了两个功能Monitor 监控中心和调用中心。这两个是可选的,需要单独配置。

    Dubbo的计数架构图如下:

    我们解释以下这个架构图:

      Consumer服务消费者,Provider服务提供者。Container服务容器。消费当然是invoke提供者了,invoke这条实线按照图上的说明当然同步的意思了,多说一句,在实际调用过程中,Provider的位置对于Consumer来说是透明的,上一次调用服务的位置(IP地址)和下一次调用服务的位置,是不确定的。这个地方就是实现了软负载。

      Monitor这是一个监控,图中虚线表明Consumer 和Provider通过异步的方式发送消息至Monitor,Consumer和Provider会将信息存放在本地磁盘,平均1min会发送一次信息。Monitor在整个架构中是可选的(图中的虚线并不是可选的意思),Monitor功能需要单独配置,不配置或者配置以后,Monitor挂掉并不会影响服务的调用

    调用关系说明:

    • 调用服务容器负责启动,加载,运行服务提供者
    • 服务提供者在启动时,向注册中心注册自己提供的服务(接口为名称:服务地址信息)
    • 服务消费者在启动时,像注册中心订阅自己所需要的服务(把接口名称取出在注册中心中查找)
    • 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
    • 服务消费者,从提供地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,在算另一台调用(注:这里是不经过注册中心的,而是Duboox框架)
    • 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

     Dubbox默认请求时间是1000ms,请求三次,如果在业务加载时间较长时可以设置请求超时时间

    Duboox支持的通讯协议

      dubbo协议:(如dubbo://192.168.0.1:20188),dubbo默认的协议,单一长连接NIO异步通信,基于hessian作为序列化协议,适用于并发高,传输数据量小。
      ( 为了要支持高并发场景,一般是服务提供者就几台机器,但服务消费者有上百台,可能每天调用量就达到上亿次!此时使用长连接最合适就是跟每个服务消费者维持一个长连接,然后后面直接基于长连接NIO异步通信,可以支撑高并发请求 )

      rmi协议:走java二进制序列化,多个短链接,适合消费者和提供者差不多的情况下,适合用于文件传输,一般较少使用
      hessian协议:走hessian序列化协议,多个短链接,适用于提供者数量比消费者数量还多,适用与文件传输,一般较少使用
      http协议:走json序列化
      webservice协议:走soap文本序列化

    二 分析源代码,基本原理如下:

      1:client一个线程调用远程接口,生成一个唯一的ID(比如一段随机字符串,UUID等),Dubbo是使用AtomicLong从0开始累计数字的
      2:将打包的方法调用信息(如调用的接口名称,方法名称,参数值列表等),和处理结果的回调对象callback,全部封装在一起,组成一个对象object
      3:向专门存放调用信息的全局ConcurrentHashMap里面put(ID, object)
      4:将ID和打包的方法调用信息封装成一对象connRequest,使用IoSession.write(connRequest)异步发送出去
      5:当前线程再使用callback的get()方法试图获取远程返回的结果,在get()内部,则使用synchronized获取回调对象callback的锁, 再先检测是否已经获取到结果,如果没有,然后调用callback的wait()方法,释放callback上的锁,让当前线程处于等待状态。
      6:服务端接收到请求并处理后,将结果(此结果中包含了前面的ID,即回传)发送给客户端,客户端socket连接上专门监听消息的线程收到消息,分析结果,取到ID,再从前面的ConcurrentHashMap里面get(ID),从而找到callback,将方法调用结果设置到callback对象里。
      7:监听线程接着使用synchronized获取回调对象callback的锁(因为前面调用过wait(),那个线程已释放callback的锁了),再notifyAll(),唤醒前面处于等待状态的线程继续执行(callback的get()方法继续执行就能拿到调用结果了),至此,整个过程结束。

     当前线程怎么让它“暂停”,等结果回来后,再向后执行?
        答:先生成一个对象obj,在一个全局map里put(ID,obj)存放起来,再用synchronized获取obj锁,再调用obj.wait()让当前线程处于等待状态,然后另一消息监听线程等到服 务端结果来了后,再map.get(ID)找到obj,再用synchronized获取obj锁,再调用obj.notifyAll()唤醒前面处于等待状态的线程。

     正如前面所说,Socket通信是一个全双工的方式,如果有多个线程同时进行远程方法调用,这时建立在client server之间的socket连接上会有很多双方发送的消息传递,前后顺序也可能是乱七八糟的,server处理完结果后,将结果消息发送给client,client收到很多消息,怎么知道哪个消息结果是原先哪个线程调用的?
        答:使用一个ID,让其唯一,然后传递给服务端,再服务端又回传回来,这样就知道结果是原先哪个线程的了。

     

    三、远程调用细节

    服务提供者暴露一个服务的详细过程:

     

    上图是服务提供者暴露服务的主过程: 
      首先ServiceConfig类拿到对外提供服务的实际类ref,然后将ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到invoker的转化。接下来就是Invoker转换到Exporter的过程。 
      Dubbo处理服务暴露的关键就在Invoker转换到Exporter的过程,下面我们以Dubbo和rmi这两种典型协议的实现来进行说明 

    1. Dubbo的实现: 

      Dubbo协议的Invoker转为Exporter发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接收客户端发来的各种请求,通讯细节由dubbo自己实现。 

    2. Rmi的实现: 

      RMI协议的Invoker转为Exporter发生在RmiProtocol类的export方法,他通过Spring或Dubbo或JDK来实现服务,通讯细节由JDK底层来实现。

    服务消费者消费一个服务的详细过程:

    上图是服务消费的主过程: 
      首先ReferenceConfig类的init方法调用Protocol的refer方法生成Invoker实例。接下来把Invoker转为客户端需要的接口。

    附:发布服务配置文件: 

    •  使用dubbo发布服务 
    <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="some-manager" />
    <dubbo:registry protocol="zookeeper"
    address="192.168.25.154:2181,192.168.25.154:2182,192.168.25.154:2183" />
    <!-- 用dubbo协议在20880端口暴 露服务 -->
    <dubbo:protocol name="dubbo" port="20880" />
    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.xx.service.ItemService" ref="itemServiceImpl"  timeout=“3000”/>//设置服务超时时间:服务调用超时时间默认1秒
    • (消费)调用服务:
    <!-- 引用dubbo服务 -->
    <dubbo:application name="some-manager-web"/>
       <dubbo:registry protocol="zookeeper" address="192.168.25.154:2181,192.168.25.154:2182,192.168.25.154:2183"/>
    <dubbo:reference interface="com.xx.service.ItemService" id="itemService" />

    接口要么是采用服务方的接口API依赖,要么是复制相同的引用接口API到消费端,以便调用。

    • 添加dubbo的依赖 加入dubbo相关的jar包。服务层、表现层都添加。
    <!-- dubbo相关 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>dubbo</artifactId>
    <!-- 排除依赖 -->
    <exclusions>
        <exclusion>
            <groupId>org.springframework</groupId>
           <artifactId>spring</artifactId>
       </exclusion>
        <exclusion>
             <groupId>org.jboss.netty</groupId>
             <artifactId>netty</artifactId>
          </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
         <artifactId>zookeeper</artifactId>
    </dependency>
    <dependency>
         <groupId>com.github.sgroschupf</groupId>
          <artifactId>zkclient</artifactId>
    </dependency>
  • 相关阅读:
    Android从零开发目录
    全国软考数据库系统工程师教程(第2版) 第1章 计算机系统知识
    全国软考数据库系统工程师教程(第2版)目录
    jvm性能调优(转载)
    开博宣言
    使用C#为Uipath封装控件
    Java时间简单操作
    js限制文本框内只能输入数字
    正则表达式语法
    JVM调优浅谈
  • 原文地址:https://www.cnblogs.com/myseries/p/12439171.html
Copyright © 2011-2022 走看看