Dubbo详解

一、基础知识

1、 RPC

RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数, 而不用程序员显式编码这个远程调用的细节。即程序员无论是调用用本地的还是远程的函数,本质上编写的调用代码基本相同。

(1)、首先调用方需要有个RPCclient,被调用方需要有 RCPServer,这两个服务用于RPC通信。

(2)、调用者通过RPCClient调用指定方法,RPCClient则将请求封装后(将方法参数值进行二进制序列化),传递给server

(3)、server收到请求后,反序列化参数,恢复成请求原本的形式,然后找到对应的方法进行本地调用。将方法的返回值通过 RPC返回给client

(4)、client收到结果后,将结果返回给调用者作为返回值。

RPC两个核心模块: 通讯, 序列化

决定RPC的效率: 通信效率, 序列化及反序列化效率

RPC框架:如 dubbo, gRPC, Thrift, HSF

2、hadoop的RPC(六、RPC基本原理_51CTO博客_rpc原理)

1)、hdfs的RPC基本流程

hdfs的rpc流程基本如上,其中的关键就是获得NameNode代理对象。

2)、java中的代理方式

(1)静态代理 先定义一个公共接口,里面包括了可以通过RPC调用的方法列表。而且被代理对象以及对象本身都需要实现该接口

(2)动态代理 先定义一个公共接口,里面包括了可以通过RPC调用的方法列表。被代理对象以及对象本身都不需要实现该接口。而是通过匿名内部类+反射的机制实现。hadoop就是使用这种方式

3)、hadoop rpc框架的例子

例子结构:

Server
    MyImpl.java         server本地执行的方法的具体实现代码
    MyInterface.java    可以rpc执行的方法列表
    MyRpcServer.java    rpc server端
Client
    MyRpcClient.java    rpc client端

Server:

/*
MyInterface.java    
*/
package Server;
​
import org.apache.hadoop.ipc.VersionedProtocol;
​
public interface MyInterface extends VersionedProtocol {
    public static long versionID = 1001; //这个是标记RPC的client和server对应的标记
    public String helloWorld(String name);
}
​
​
/*
MyImpl.java
*/
package Server;
​
import org.apache.hadoop.ipc.ProtocolSignature;
​
import java.io.IOException;
​
public class MyImpl implements MyInterface{
    /*这是实际目标*/
​
//重写我们在上面接口自定义的方法
@Override
public String helloWorld(String name) {
    return "hello," + name;
}
​
//返回版本号
@Override
public long getProtocolVersion(String s, long l) throws IOException {
    return MyInterface.versionID;
}
​
//返回签名信息
@Override
public ProtocolSignature getProtocolSignature(String s, long l, int i) throws IOException {
    return new ProtocolSignature(MyInterface.versionID, null);
}
​
}
​
/*
MyRpcServer.java
*/
package Server;
​
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.ipc.RPC;
​
import java.io.IOException;
​
public class MyRpcServer {
    public static void main(String[] args) {
        //建立rpc通道对象
        RPC.Builder builder = new RPC.Builder(new Configuration());
​
•    //设置RPC server参数
•    builder.setBindAddress("localhost");
•    builder.setPort(7788);
​
•    //部署程序,传入实现server业务代码的接口定义,这里面包括了该rpcserver 可以提供的方法,也就是给client调用的方法列表,通过反射的方式引入类对象
•    builder.setProtocol(MyInterface.class);
​
•    //部署接口的实现类对象
•    builder.setInstance(new MyImpl());
​
•    //开启server
•    try {
•        RPC.Server server = builder.build();
•        server.start();
•    } catch (IOException e) {
•        e.printStackTrace();
•    }
​
}
​
}

client:

/*
MyRpcClient.java
*/
package Client;
​
import Server.MyInterface;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.ipc.RPC;
​
import java.io.IOException;
import java.net.InetSocketAddress;
​
public class MyRpcClient {
    public static void main(String[] args) {
        try {
            //获取代理对象,设置接口类对象、RPC通信的versionID,rpcserver地址、configuration对象
            MyInterface proxy = RPC.getProxy(
                    MyInterface.class,
                    MyInterface.versionID,
                    new InetSocketAddress("localhost", 7788),
                    new Configuration());
            
​
•        //获得代理对象之后,就可以通过proxy调用接口类中的方法,这里就调用上面定义的 helloWorld对象
•        System.out.println(proxy.helloWorld("king"));
•    } catch (IOException e) {
•        e.printStackTrace();
•    }
}
​
}

下面启动server端和client端,执行结果为:

//server:可以看到显示监听端口 7788
[main] INFO org.apache.hadoop.ipc.CallQueueManager - Using callQueue: class java.util.concurrent.LinkedBlockingQueue queueCapacity: 100 scheduler: class org.apache.hadoop.ipc.DefaultRpcScheduler
[Socket Reader #1 for port 7788] INFO org.apache.hadoop.ipc.Server - Starting Socket Reader #1 for port 7788
[IPC Server Responder] INFO org.apache.hadoop.ipc.Server - IPC Server Responder: starting
[IPC Server listener on 7788] INFO org.apache.hadoop.ipc.Server - IPC Server listener on 7788: starting
​
//client:  我们传入“King”作为参数,能够争取执行
​
hello,king
-----------------------------------

3、dubbo核心概念

  1. 简介

    Apache dubbo 是一款高性能、 轻量及的开源 java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

    官网: Apache Dubbo

    1. 特性

      面向接口代理的高性能RPC调用

      服务自动注册与发现

      运行期流量调度

      智能负载均衡

      高度可扩展能力

      可视化的服务治理与运维

3. dubbo Architecture

总体架构

节点角色说明

节点角色说明
Provider暴露服务的服务提供方
Consumer调用远程服务的服务消费方
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心
Container服务运行容器

调用关系说明

  1. 服务容器负责启动,加载,运行服务提供者。

  2. 服务提供者在启动时,向注册中心注册自己提供的服务。

  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。

  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。

连通性

  • 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小

  • 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示

  • 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销

  • 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销

  • 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外

  • 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者

  • 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表

  • 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者

健壮性

  • 监控中心宕掉不影响使用,只是丢失部分采样数据

  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务

  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台

  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯

  • 服务提供者无状态,任意一台宕掉后,不影响使用

  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

伸缩性

  • 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心

  • 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者

升级性

当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构:

节点角色说明

节点角色说明
Deployer自动部署服务的本地代理
Repository仓库用于存储服务应用发布包
Scheduler调度中心基于访问压力自动增减服务提供者
Admin统一管理控制台
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心

4.代码架构

整体设计

图例说明:

  • 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。

  • 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。

  • 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。

  • 图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调用链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。

各层说明

  • Config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类

  • Proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory

  • Registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService

  • Cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance

  • Monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService

  • Protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter

  • Exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer

  • Transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec

  • Serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool

关系说明

  • 在 RPC 中,Protocol 是核心层,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 调用,然后在 Invoker 的主过程上 Filter 拦截点。

  • 图中的 Consumer 和 Provider 是抽象概念,只是想让看图者更直观的了解哪些类分属于客户端与服务器端,不用 Client 和 Server 的原因是 Dubbo 在很多场景下都使用 Provider, Consumer, Registry, Monitor 划分逻辑拓扑节点,保持统一概念。

  • 而 Cluster 是外围概念,所以 Cluster 的目的是将多个 Invoker 伪装成一个 Invoker,这样其它人只要关注 Protocol 层 Invoker 即可,加上 Cluster 或者去掉 Cluster 对其它层都不会造成影响,因为只有一个提供者时,是不需要 Cluster 的。

  • Proxy 层封装了所有接口的透明化代理,而在其它层都以 Invoker 为中心,只有到了暴露给用户使用时,才用 Proxy 将 Invoker 转成接口,或将接口实现转成 Invoker,也就是去掉 Proxy 层 RPC 是可以 Run 的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。

  • 而 Remoting 实现是 Dubbo 协议的实现,如果你选择 RMI 协议,整个 Remoting 都不会用上,Remoting 内部再划为 Transport 传输层和 Exchange 信息交换层,Transport 层只负责单向消息传输,是对 Mina, Netty, Grizzly 的抽象,它也可以扩展 UDP 传输,而 Exchange 层是在传输层之上封装了 Request-Response 语义。

  • Registry 和 Monitor 实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。

模块分包

模块说明:

  • dubbo-common 公共逻辑模块:包括 Util 类和通用模型。

  • dubbo-remoting 远程通讯模块:相当于 Dubbo 协议的实现,如果 RPC 用 RMI协议则不需要使用此包。

  • dubbo-rpc 远程调用模块:抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。

  • dubbo-cluster 集群模块:将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。

  • dubbo-registry 注册中心模块:基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。

  • dubbo-monitor 监控模块:统计服务调用次数,调用时间的,调用链跟踪的服务。

  • dubbo-config 配置模块:是 Dubbo 对外的 API,用户通过 Config 使用Dubbo,隐藏 Dubbo 所有细节。

  • dubbo-container 容器模块:是一个 Standlone 的容器,以简单的 Main 加载 Spring 启动,因为服务通常不需要 Tomcat/JBoss 等 Web 容器的特性,没必要用 Web 容器去加载服务。

整体上按照分层结构进行分包,与分层的不同点在于:

  • Container 为服务容器,用于部署运行服务,没有在层中画出。

  • Protocol 层和 Proxy 层都放在 rpc 模块中,这两层是 rpc 的核心,在不需要集群也就是只有一个提供者时,可以只使用这两层完成 rpc 调用。

  • Transport 层和 Exchange 层都放在 remoting 模块中,为 rpc 调用的通讯基础。

  • Serialize 层放在 common 模块中,以便更大程度复用。

依赖关系

图例说明:

  • 图中小方块 Protocol, Cluster, Proxy, Service, Container, Registry, Monitor 代表层或模块,蓝色的表示与业务有交互,绿色的表示只对 Dubbo 内部交互。

  • 图中背景方块 Consumer, Provider, Registry, Monitor 代表部署逻辑拓扑节点。

  • 图中蓝色虚线为初始化时调用,红色虚线为运行时异步调用,红色实线为运行时同步调用。

  • 图中只包含 RPC 的层,不包含 Remoting 的层,Remoting 整体都隐含在 Protocol 中。

调用链

展开总设计图的红色调用链,如下:

暴露服务时序

展开总设计图右边服务提供方暴露服务的蓝色初始化链,时序图如下:

引用服务时序

展开总设计图左边服务消费方引用服务的绿色初始化链,时序图如下:

领域模型

在 Dubbo 的核心领域模型中:

  • Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。

  • Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠拢,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

  • Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。

基本设计原则

  • 采用 Microkernel + Plugin 模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。

  • 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。

二、dubbo开发基础

1、搭建环境

安装zookeeper注册中心(https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/registry/zookeeper/)

使用docker安装:

1)、编写 docker-compose.yml:

version: "3.1"
services:
  zk:
    image: daocloud.io/daocloud/zookeeper:latest
    restart: always
    container_name: zk
    ports:
      - 2181:2181

2)、docker-compose up -d

3)、docker exec -it 63 bash

安装监控中心(GitHub - apache/dubbo: Apache Dubbo is a high-performance, java based, open source RPC framework.

GitHub - apache/dubbo-admin: The ops and reference implementation for Apache Dubbo

1、 Run With Kubernetes

  1. Get Kubernetes manifests**

$ git clone https://github.com/apache/dubbo-admin.git

All we need from this step is the Admin kubernetes manifests in deploy/k8s

But before you can apply the manifests, override the default value defined in application.properties by adding items in configmap.yaml.

$ cd /dubbo-admin/deploy/k8s
  1. 2.Deploy Dubbo Admin

# Change configuration in ./deploy/application.yml before apply if necessary
$ kubectl apply -f ./
  1. Visit Admin**

$ kubectl port-forward service dubbo-admin 38080:38080

Open web browser and visit http://localhost:38080, default username and password are root

2、 Run With Docker

The prebuilt docker image is hosted at: Docker

You can run the image directly by mounting a volume from the host that contains an application.properties file with the accessible registry and config-center addresses specified.

$ docker run -it --rm -v /the/host/path/containing/properties:/config -p 38080:38080 apache/dubbo-admin

Replace /the/host/path/containing/properties with the actual host path (must be an absolute path) that points to a directory containing application.properties.

Open web browser and visit http://localhost:38080, default username and password are root

3、 Compile From Source

  1. Clone source code on develop branch git clone https://github.com/apache/dubbo-admin.git

  2. Specify registry address in dubbo-admin-server/src/main/resources/application.properties

    admin.registry.address=zookeeper://192.168.0.252:2181?blockUntilConnectedWait=200000&timeout=200000
    admin.config-center=zookeeper://192.168.0.252:2181
    admin.metadata-report.address=zookeeper://192.168.0.252:2181
    ​
    dubbo.registry.timeout=200000
    dubbo.metadata-report.timeout=200000
    ​
    dubbo.metadata-report.address=${admin.metadata-report.address}
  3. Build

    • mvn clean package -Dmaven.test.skip=true

  4. Start

    • mvn --projects dubbo-admin-server spring-boot:run OR

    • cd dubbo-admin-distribution/target; java -jar dubbo-admin-0.1.jar

  5. Visit http://localhost:8080, default username and password are root

注意:如果是虚拟机,要关闭防火墙和selinux

#关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
​
#关才selinux 
sed -i 's/enforcing/disabled/' /etc/selinux/config  #永久
setenforce 0 #临时

2、dubbo-helloworld

1)、提出需求

某个电商系统,订单服务需要调用用户服务获取某个用户的所有地址;

需要创建两个服务模块进行测试:

模块功能
订单服务web模块创建订单等
用户服务service模块查询用户地址等

测试预期结果:

订单服务web模块在A服务器,用户服务模块在B服务器,A可以远行调用B的功能。

创建API包(服务化最佳实践 | Apache Dubbo)

创建bean

public class UserAddress implements Serializable{
​
    private Integer id;
    private String userAddress;
    private String userId;
    private String consignee;  //收货人
    private String phoneNum;   //电放号码
    private String isDefault;  //是否为默认地址
}

创建公共接口

public interface OrderService {
​
    public void initOrder(String userId)
}
public interface UserService {
​
    public List<UserAddress> getUserAddressList(String userId);
}

如下所示:

2)、服务提供者

1、创建maven工程

2、 引入API公共包:

    
<dependency>
         <groupId>com.study.gmall</groupId>
         <artifactId>gmall-interface</artifactId>
         <version>0.0.1-SNAPSHOT</version>
    </dependency>

3、 实现服务提供者的接口

public class UserServiceImpl implements UserService {
​
    public List<UserAddress> getUserAddressList(String userId) {
        
        UserAddress userAddress1 = new UserAddress(1, "西安市高新区高新十小", "1", "康老师", "13854524576", "是");
        UserAddress userAddress2 = new UserAddress(2, "西安市高新区高新十小", "2", "孙老师", "13973747319", "否");
        return Arrays.asList(userAddress1, userAddress2);
    }
​
}

4、引入dubbo

1、将服务提供者注册到注册中心(暴露服务)

(1)、导入dubbo依赖(2.6.2)以及引入操作zookper的客户端(curator)

  <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.2</version>
        </dependency>

导入zookeeper依赖(2.6版本以上)

 <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>

(2.6版本以下)

<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>

(2) 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd        http://dubbo.apache.org/schema/dubbo        http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
​
   <!-- 1、指定当前服务/应用的名字(同样的服务名字相同, 不要和别的服务同名) -->
    <dubbo:application name="user-service-provider"  />
 
    <!-- 2、指定注册中心的位置 -->
    <dubbo:registry address="zookeeper://192.168.0.252:2181" />
 
    <!--3、指定通信规则(通信规则?通信端口) -->
    <dubbo:protocol name="dubbo" port="20881" />
 
    <!--4、暴露服务 ref: 指向服务的真正的实现对象 -->
    <dubbo:service interface="com.study.gmall.service.UserService" ref="userServiceImpl" />
 
    <!--5、服务的实现 -->
    <bean id="userServiceImpl" class="com.study.gmall.service.impl.UserServiceImpl" />
</beans>
​

5、测试:

public class App 
{
    public static void main( String[] args ) throws IOException
    {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("provider.xml");
        ioc.start();
        System.in.read();
    }
}

6、结果:

3)、创建消费者:

1、创建maven工程

2、引入依赖:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>

3、实现:

/**
 * 1、将服务提供者注册到注册中心(暴露服务) 1)、 导入dubbo依赖(2.6.2) \操作zookeeper的客户端(curator) 2)、
 * 配置服务提供者
 * 
 * 2、让服务消费者去注册中心订阅服务提供都的服务地址
 */
​
@Service
public class OrderServiceImpl implements OrderService {
​
    @Autowired
    UserService userService;
​
    public void initOrder(String userId) {
        // TODO Auto-generated method stub
        System.out.println("用户ID: " + userId);
        // 1、查询用户的收货地址
        List<UserAddress> addresses = userService.getUserAddressList(userId);
        for(UserAddress userAddress : addresses) {
            System.out.println(userAddress.getUserAddress());
        }
    }
​
}

4、配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd 
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
           http://dubbo.apache.org/schema/dubbo        http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    
    <context:component-scan base-package="com.study.gmall.service.impl"></context:component-scan>
​
    <dubbo:application name="order-service-consumer"></dubbo:application>
     <dubbo:registry address="zookeeper://192.168.0.252:2181" />
     
     <!-- 声明需要调用的远程服务的接口,生成远程服务代理 -->
     <dubbo:reference interface="com.study.gmall.service.UserService" id="userService"></dubbo:reference>
    
</beans>
 

5、测试:

public class App 
{
    public static void main( String[] args ) throws IOException
    {
        ClassPathXmlApplicationContext applicationContext = new  ClassPathXmlApplicationContext("consumer.xml");;
        OrderService orderService = applicationContext.getBean(OrderService.class);
        orderService.initOrder("1");
        System.out.println("调用结束..........................");
        System.in.read();
    }
}

6、结果:

3、springboot 整合 dubbo

1)、服务提供者

1、创建springboot工程

2、引入API包,实现服务

        <dependency>
            <groupId>com.study.dubbo</groupId>
            <artifactId>gmall-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
@Component
public class UserServiceImpl implements UserService {
​
    public List<UserAddress> getUserAddressList(String userId) {
        
        UserAddress userAddress1 = new UserAddress(1, "北京市海淀区中关村大街132号", "1", "康老师", "13854524576", "是");
        UserAddress userAddress2 = new UserAddress(2, "深圳市罗湖区香湖湾101号", "2", "孙老师", "13973747319", "否");
        return Arrays.asList(userAddress1, userAddress2);
    }
​
}

3、引入dubbo, 及相关的zookeeper组件

<!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.10</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.8.0</version>
        </dependency>

4、编写配置文件(application.properties)

dubbo.application.name=boot-user-service-provider
dubbo.registry.address=zookeeper://192.168.0.252:2181?blockUntilConnectedWait=200000&timeout=200000
dubbo.registry.protocol=zookeeper
dubbo.protocol.name=dubbo
dubbo.protocol.port=20882
dubbo.monitor.protocol=registry
dubbo.metadata-report.address=zookeeper://192.168.0.252:2181

5、暴露服务

在服务上引入 @org.apache.dubbo.config.annotation.DubboService

低版本引入 @org.apache.dubbo.config.annotation.Service 或

@com.alibaba.dubbo.config.annotation.Service 这个注解将要过期

@org.apache.dubbo.config.annotation.DubboService //暴露服务
@Component
public class UserServiceImpl implements UserService {
​
    public List<UserAddress> getUserAddressList(String userId) {
        
        UserAddress userAddress1 = new UserAddress(1, "北京市海淀区中关村大街132号", "1", "康老师", "13854524576", "是");
        UserAddress userAddress2 = new UserAddress(2, "深圳市罗湖区香湖湾101号", "2", "孙老师", "13973747319", "否");
        return Arrays.asList(userAddress1, userAddress2);
    }
​
}

6、开启dubbo服务

@EnableDubbo           //开启基于注解的dubbo功能
@SpringBootApplication
public class BootUserServiceProviderApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(BootUserServiceProviderApplication.class, args);
    }
}

7、启动服务

2)、服务消费者

1、创建springboot服务, 因为是消费端,使用war包,web工程

2、引入API包,实现接口

<dependency>
            <groupId>com.study.dubbo</groupId>
            <artifactId>gmall-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
@Service
public class OrderServiceImpl implements OrderService {
​
    @Autowired
    UserService userService;
​
    public List<UserAddress> initOrder(String userId) {
        // TODO Auto-generated method stub
        System.out.println("用户ID: " + userId);
        // 1、查询用户的收货地址
        List<UserAddress> addresses = userService.getUserAddressList(userId);
        
        return addresses;
    }
​
}

3、创建controller,供web访问

@Controller
public class OrderController {
​
    @Autowired
    OrderService orderService;
    
    @ResponseBody
    @RequestMapping("/initOrder")
    public List<UserAddress> initOrder(@RequestParam("uid") String userId) {
        return orderService.initOrder(userId);
    }
}
 

4、配置文件

dubbo.application.name=boot-order-service-consumer
dubbo.registry.address=zookeeper://192.168.0.252:2181?blockUntilConnectedWait=200000&timeout=200000
dubbo.monitor.protocol=registry
dubbo.metadata-report.address=zookeeper://192.168.0.252:2181
server.port=8081

5、引入dubbo及zookeeper客户端

<!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.10</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.8.0</version>
        </dependency>

6、引用dubbo的消费服务

低版本使用@Reference

@Service
public class OrderServiceImpl implements OrderService {
​
    @DubboReference
    UserService userService;
​
    public List<UserAddress> initOrder(String userId) {
        // TODO Auto-generated method stub
        System.out.println("用户ID: " + userId);
        // 1、查询用户的收货地址
        List<UserAddress> addresses = userService.getUserAddressList(userId);
        
        return addresses;
    }
​
}

7、开启dubbo服务

@EnableDubbo
@SpringBootApplication
public class BootOrderServiceConsumerApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(BootOrderServiceConsumerApplication.class, args);
    }
​
}

8、启动服务,测试

4、XML 配置

属性配置 | Apache Dubbo

1)、属性 配置

 

 

配置生效

(1)、 精确优先(方法级优先,接口级次之,全局配置再次之)

(2)、消费者设置优先(如果级别一样,则消费方优先。提供方次之)

2)、启动时检查

 

3)、重试次数配置

幂等(设置重试次数)【查询、删除、修改】、非幂等(不能设置重试次数)【新增】

retries="": 重试次数,不包含第一次调用, 0代表不重试

4)、多版本

 

5)、Springboot与dubbo整合的三种方式

1)、导入dubbo-starter, 在application.properties配置属性, 使用@service【暴露服务】使用@Reference【引用服务】

老版本可用如下配置,则不需要开启"@EnableDubbo"

dubbo.scan.base-packages=com.study.gmall

2)、保留dubbo xml配置文件(精确到方法级别)

创建provider.xml文件

在SpringBootApplication中配置 @ImportResource(locations="classpath:provider.xml")

3)、Annotation 配置

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/config/annotation/

以 Annotation、Spring Boot 开发 Dubbo 应用

本文以 Spring Boot + Annotation 模式描述 Dubbo 应用开发,在此查看无 Spring Boot 的 Spring 注解开发模式 完整示例

在 Dubbo Spring Boot 开发中,你只需要增加几个注解,并配置 application.propertiesapplication.yml 文件即可完成 Dubbo 服务定义:

  • 注解有 @DubboService@DubboReferenceEnableDubbo。其中 @DubboService@DubboReference 用于标记 Dubbo 服务,EnableDubbo 启动 Dubbo 相关配置并指定 Spring Boot 扫描包路径。

  • 配置文件 application.propertiesapplication.yml

以下内容的完整示例请参考 dubbo-samples

增加 Maven 依赖

使用 Dubbo Spring Boot Starter 首先引入以下 Maven 依赖

    
<dependencyManagement>
        <dependencies>
            <!-- Spring Boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- Dubbo -->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-bom</artifactId>
                <version>${dubbo.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- Zookeeper -->
            <!-- NOTICE: Dubbo only provides dependency management module for Zookeeper, add Nacos or other product dependency directly if you want to use them. -->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-dependencies-zookeeper</artifactId>
                <version>${dubbo.version}</version>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

然后在相应的模块的 pom 中增加

    
<dependencies>
        <!-- dubbo -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-dependencies-zookeeper</artifactId>
            <type>pom</type>
        </dependency>
​
        <!-- dubbo starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>
​
        <!-- spring starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
    </dependencies>

application.yml 或 application.properties

除 service、reference 之外的组件都可以在 application.yml 文件中设置,如果要扩展 service 或 reference 的注解配置,则需要增加 dubbo.properties 配置文件或使用其他非注解如 Java Config 方式,具体请看下文 扩展注解的配置

service、reference 组件也可以通过 id 与 application 中的全局组件做关联,以下面配置为例:

dubbo:
  application:
    name: dubbo-springboot-demo-provider
  protocol:
    name: dubbo
    port: -1
  registry:
    id: zk-registry
    address: zookeeper://127.0.0.1:2181
  config-center:
    address: zookeeper://127.0.0.1:2181
  metadata-report:
    address: zookeeper://127.0.0.1:2181

通过注解将 service 关联到上文定义的特定注册中心

@DubboService(registry="zk-registry")
public class DemoServiceImpl implements DemoService {}

通过 Java Config 配置进行关联也是同样道理

@Configuration
public class ProviderConfiguration {
    @Bean
    public ServiceConfig demoService() {
        ServiceConfig service = new ServiceConfig();
        service.setRegistry("zk-registry");
        return service;
    }
}

注解

@DubboService 注解

@Service 注解从 3.0 版本开始就已经废弃,改用 @DubboService,以区别于 Spring 的 @Service 注解

定义好 Dubbo 服务接口后,提供服务接口的实现逻辑,并用 @DubboService 注解标记,就可以实现 Dubbo 的服务暴露

@DubboService
public class DemoServiceImpl implements DemoService {}

如果要设置服务参数,@DubboService 也提供了常用参数的设置方式。如果有更复杂的参数设置需求,则可以考虑使用其他设置方式

@DubboService(version = "1.0.0", group = "dev", timeout = 5000)
public class DemoServiceImpl implements DemoService {}

@DubboReference 注解

@Reference 注解从 3.0 版本开始就已经废弃,改用 @DubboReference,以区别于 Spring 的 @Reference 注解

@Component
public class DemoClient {
    @DubboReference
    private DemoService demoService;
}

@DubboReference 注解将自动注入为 Dubbo 服务代理实例,使用 demoService 即可发起远程服务调用

@EnableDubbo 注解

@EnableDubbo 注解必须配置,否则将无法加载 Dubbo 注解定义的服务,@EnableDubbo 可以定义在主类上

@SpringBootApplication
@EnableDubbo
public class ProviderApplication {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

Spring Boot 注解默认只会扫描 main 类所在的 package,如果服务定义在其它 package 中,需要增加配置 EnableDubbo(scanBasePackages = {"org.apache.dubbo.springboot.demo.provider"})

扩展注解配置

虽然可以通过 @DubboServiceDubboReference 调整配置参数(如下代码片段所示),但总体来说注解提供的配置项还是非常有限。在这种情况下,如果有更复杂的参数设置需求,可以使用 Java Configdubbo.properties 两种方式。

@DubboService(version = "1.0.0", group = "dev", timeout = 5000)
@DubboReference(version = "1.0.0", group = "dev", timeout = 5000)

使用 Java Config 代替注解

注意,Java Config 是 DubboServiceDubboReference 的替代方式,对于有复杂配置需求的服务建议使用这种方式。

@Configuration
public class ProviderConfiguration {
    @Bean
    public ServiceConfig demoService() {
        ServiceConfig service = new ServiceConfig();
        service.setInterface(DemoService.class);
        service.setRef(new DemoServiceImpl());
        service.setGroup("dev");
        service.setVersion("1.0.0");
        Map<String, String> parameters = new HashMap<>();
        service.setParameters(parameters);
        return service;
    }
}

通过 dubbo.properties 补充配置

对于使用 DubboServiceDubboReference 的场景,可以使用 dubbo.properties 作为配置补充,具体格式这里有更详细解释。

dubbo.service.org.apache.dubbo.springboot.demo.DemoService.timeout=5000
dubbo.service.org.apache.dubbo.springboot.demo.DemoService.parameters=[{myKey:myValue},{anotherKey:anotherValue}]
dubbo.reference.org.apache.dubbo.springboot.demo.DemoService.timeout=6000

properties 格式配置目前结构性不太强,比如体现在 key 字段冗余较多,后续会考虑提供对于 yaml 格式的支持。

三、高可用

1、zookeeper宕机与dubbo直连

现象: zookeeper注册中心宕机,还可以消费dubbo暴露的服务;

原因: 健状性

监控中心宕掉不影响使用,只是丢失部分采样数据

数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务。

注册中心对等集群,任意一台宕掉后,将自动切换到另一台

注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯。

服务提供者无状态,任意一台宕掉后,不影响使用

服务提供都全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

高可用:通过设计,减少系统不能提供服务的时间

@DubboReference(url="127.0.0.1:20882")   //dubbo直连

2、集群下dubbo负载均衡配置

在集群负载均衡时,Dubbo提供了多种均衡策略,缺省为random随机调用.

负载均衡策略

Random LoadBalance

随机,按权重设置随机概率: 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

RoundRobin LoadBalance

轮循,按公约后的权重设置轮循比率:存在慢的提供者累积请求的问题,比如:第二台机器慢,但没挂,当请求调到第二台时就卡住,久而久之,所有请求都卡在调到第二台上。

@DubboReference(loadbalance="roundRobin")   //配置

LeastActive LoadBalance

最少活跃调用数,相同活跃数据的随机,活跃数指调用前后计数差。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

ConsistentHash LoadBalance

一致性Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。详见: 深度解析dubbo负载均衡之ConsistentHashLoadBalance_$码出未来的博客-CSDN博客Dubbo源码学习--ConsistentHashLoadBalance负载均衡(五)_归田的博客-CSDN博客

3、服务降级

当服务压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心变易正常动作或高效动作。

可以通过服务降级功能临时屏蔽某个出错的非关键 服务,并定义降级后的返回策略。向注册中心写入动态配置覆盖规则:

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));

其中:

mock=force:return+null 表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。

mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常。用来容忍

4、集群容错

在集群调用失败时,Dubbo提供了多种容错方案,缺省为Failover重试

集群容错模式:详见(Dubbo集群容错_爱coding的李同学的博客-CSDN博客)

附:

  1. 集群容错 集群调用失败时,Dubbo 提供的容错方案。

在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。

各节点关系:

这里的 Invoker 是 Provider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息 Directory 代表多个 Invoker,可以把它看成 List ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更 Cluster 将 Directory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个 Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等 LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选

  1. 集群容错模式分类 Cluster类图如下:

    

2.1 Failover Cluster(缺省配置) 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)。

重试次数配置如下:

<dubbo:service retries="2" />
<dubbo:reference retries="2" />
<dubbo:reference>
    <dubbo:method name="findFoo" retries="2" />
</dubbo:reference>

源码分析:

copyinvokers 变量,候选的 Invoker 集合。 调用父 #checkInvokers(copyinvokers, invocation) 方法,校验候选的 Invoker 集合非空。如果为空,抛出 RpcException 异常。 获得最大可调用次数:最大可重试次数 +1 。默认最大可重试次数Constants.DEFAULT_RETRIES = 2 。 le 变量,保存最后一次调用的异常。 invoked 变量,保存已经调用的 Invoker 集合。 providers 变量,保存已经调用的网络地址集合。 failover 机制核心实现:如果出现调用失败,那么重试其他服务器。 超过最大调用次数,抛出 RpcException 异常。该异常中,带有最后一次调用异常的信息。 2.2 Forking Cluster 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数。

源码分析:

count 变量,异常计数器。 ref 变量,阻塞队列。通过它,实现线程池异步执行任务的结果通知,非常亮眼。 循环 selected 集合,提交线程池,发起 RPC 调用。 从 ref 队列中,阻塞等待,直到获得到结果或者超时。至此,ForkingClusterInvoker 实现了并行调用,且只要一个成功即返回。当然,还有一个隐性的,所有都失败才返回。 处理等待的“结果”。

2.3 Failfast Cluster 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

2.4 Failsafe Cluster 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

2.5 Failback Cluster 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

2.6 Broadcast Cluster 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

现在广播调用中,可以通过 broadcast.fail.percent 配置节点调用失败的比例,当达到这个比例后,BroadcastClusterInvoker 将不再调用其他节点,直接抛出异常。 broadcast.fail.percent 取值在 0~100 范围内。默认情况下当全部调用失败后,才会抛出异常。 broadcast.fail.percent 只是控制的当失败后是否继续调用其他节点,并不改变结果(任意一台报错则报错)。broadcast.fail.percent 参数 在 dubbo2.7.10 及以上版本生效。

Broadcast Cluster 配置 broadcast.fail.percent。

broadcast.fail.percent=20 代表了当 20% 的节点调用失败就抛出异常,不再调用其他节点。

@reference(cluster = "broadcast", parameters = {"broadcast.fail.percent", "20"})

源码分析:

集群模式配置

按照以下示例在服务提供方和消费方配置集群模式

<dubbo:service cluster="failsafe" />
<dubbo:reference cluster="failsafe" />

5、整合Hystrix

Hystrix旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。

1)、配置spring-cloud-starter-netflix-hystrix

spring boot官方提供了对hystrix的集成,直接在pom.xml里加入依赖

      <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>

然后在Application类上增加@EnableHystrix来启用hystrix starter

四、dubbo原理

1、RPC原理

1)服务消费方(client)调用以本地调用方式调用服务;

2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;

3)client stub找到服务地址,并将消息发送到服务端;

4)server stub收到消息后进行解码;

5)server stub根据解码结果调用本地的服务;

6)本地服务执行并将结果返回给server stub;

7)server stub将返回结果打包成消息并发送至消费方;

8)client stub接收到消息,并进行解码;

9)服务消费方得到最终结果。

  RPC的目标就是要2~8这些步骤都封装起来,让用户对这些细节透明,不可见的。

2、netty通信原理

Netty是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。它极大地简化了TCP和UDP套接字服务器等网络编程。具体模型详见(https://www.cnblogs.com/hlkawa/p/15047212.html).

BIO:(Blocking IO)

NIO:(Non-Blocking IO)

Netty基本原理:(Java开发中Netty线程模型原理解析-JavaEE资讯-博学谷)

Java开发中Netty线程模型原理解析,Netty是Java领域有名的开源网络库具有高性能和高扩展性的特点,很多流行的框架都是基于它来构建。Netty 线程模型不是一成不变的,取决于用户的启动参数配置。通过设置不同的启动参数Netty ,可同时支持 Reactor 单线程模型、多线程模型。

一、线程组

Netty抽象了两组线程池BossGroup和WorkerGroup,其类型都是NioEventLoopGroup,BossGroup用来接受客户端发来的连接WorkerGroup则负责对完成TCP三次握手的连接进行处理。

NioEventLoopGroup里面包含了多个NioEventLoop管理NioEventLoop的生命周期。每个NioEventLoop中包含了一个NIO Selector、一个队列、一个线程;其中线程用来做轮询注册到Selector上的Channel的读写事件和对投递到队列里面的事件进行处理。

Boss NioEventLoop线程的执行步骤:

(1)处理accept事件与client建立连接, 生成NioSocketChannel。

(2)将NioSocketChannel注册到某个worker NIOEventLoop上的selector

(3)处理任务队列的任务 即runAllTasks。

Worker NioEventLoop线程的执行步骤:

(1)轮询注册到自己Selector上的所有NioSocketChannel的read和write事件。

(2)2处理read和write事件在对应NioSocketChannel处理业务。

(3)#runAllTasks处理任务队列TaskQueue的任务,一些耗时的业务处理可以放入TaskQueue中慢慢处理这样不影响数据在pipeline中的流动处理。

Worker NIOEventLoop处理NioSocketChannel业务时,使用了pipeline (管道),管道中维护了handler处理器链表用来处理channel中的数据。

二、ChannelPipeline

Netty将Channel的数据管道抽象为ChannelPipeline,消息在ChannelPipline中流动和传递。ChannelPipeline持有I/O事件拦截器ChannelHandler的双向链表,由ChannelHandler对I/O事件进行拦截和处理,可以方便的新增和删除ChannelHandler来实现不同的业务逻辑定制不需要对已有的ChannelHandler进行修改能够实现对修改封闭和对扩展的支持。

ChannelPipeline是一系列的ChannelHandler实例,流经一个Channel的入站和出站事件可以被ChannelPipeline 拦截。每当一个新的Channel被创建了,都会建立一个新的ChannelPipeline并绑定到该Channel上,这个关联是永久性的;Channel既不能附上另一个ChannelPipeline也不能分离当前这个。这些都由Netty负责完成,而无需开发人员的特别处理。

根据起源一个事件将由ChannelInboundHandler或ChannelOutboundHandler处理,ChannelHandlerContext实现转发或传播到下一个ChannelHandler。一个ChannelHandler处理程序可以通知ChannelPipeline中的下一个ChannelHandler执行。Read事件(入站事件)和write事件(出站事件)使用相同的pipeline,入站事件会从链表head 往后传递到最后一个入站的handler出站事件会从链表tail往前传递到最前一个出站的 handler,两种类型的 handler 互不干扰。

ChannelInboundHandler回调方法:

ChannelOutboundHandler回调方法:

三、异步非阻塞

写操作:通过NioSocketChannel的write方法向连接里面写入数据时候是非阻塞的,马上会返回即使调用写入的线程是我们的业务线程。Netty通过在ChannelPipeline中判断调用NioSocketChannel的write的调用线程是不是其对应的NioEventLoop中的线程,如果发现不是则会把写入请求封装为WriteTask投递到其对应的NioEventLoop中的队列里面,然后等其对应的NioEventLoop中的线程轮询读写事件时候,将其从队列里面取出来执行。

读操作:当从NioSocketChannel中读取数据时候并不是需要业务线程阻塞等待,而是等NioEventLoop中的IO轮询线程发现Selector上有数据就绪时,通过事件通知方式来通知业务数据已就绪,可以来读取并处理了。

每个NioSocketChannel对应的读写事件都是在其对应的NioEventLoop管理的单线程内执行,对同一个NioSocketChannel不存在并发读写,所以无需加锁处理。

使用Netty框架进行网络通信时,当我们发起I/O请求后会马上返回,而不会阻塞我们的业务调用线程;如果想要获取请求的响应结果,也不需要业务调用线程使用阻塞的方式来等待,而是当响应结果出来的时候,使用I/O线程异步通知业务的方式,所以在整个请求 -> 响应过程中业务线程不会由于阻塞等待而不能干其他事情。

3、Dubbo原理

1)、 框架设计

(代码架构 | Apache Dubbo)

图例说明:

  • 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。

  • 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。

  • 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。

  • 图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调用链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。

各层说明

  • Config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类

  • Proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory

  • Registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService

  • Cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance

  • Monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService

  • Protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter

  • Exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer

  • Transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec

  • Serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool

关系说明

  • 在 RPC 中,Protocol 是核心层,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 调用,然后在 Invoker 的主过程上 Filter 拦截点。

  • 图中的 Consumer 和 Provider 是抽象概念,只是想让看图者更直观的了解哪些类分属于客户端与服务器端,不用 Client 和 Server 的原因是 Dubbo 在很多场景下都使用 Provider, Consumer, Registry, Monitor 划分逻辑拓扑节点,保持统一概念。

  • 而 Cluster 是外围概念,所以 Cluster 的目的是将多个 Invoker 伪装成一个 Invoker,这样其它人只要关注 Protocol 层 Invoker 即可,加上 Cluster 或者去掉 Cluster 对其它层都不会造成影响,因为只有一个提供者时,是不需要 Cluster 的。

  • Proxy 层封装了所有接口的透明化代理,而在其它层都以 Invoker 为中心,只有到了暴露给用户使用时,才用 Proxy 将 Invoker 转成接口,或将接口实现转成 Invoker,也就是去掉 Proxy 层 RPC 是可以 Run 的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。

  • 而 Remoting 实现是 Dubbo 协议的实现,如果你选择 RMI 协议,整个 Remoting 都不会用上,Remoting 内部再划为 Transport 传输层和 Exchange 信息交换层,Transport 层只负责单向消息传输,是对 Mina, Netty, Grizzly 的抽象,它也可以扩展 UDP 传输,而 Exchange 层是在传输层之上封装了 Request-Response 语义。

  • Registry 和 Monitor 实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。

模块分包

模块说明:

  • dubbo-common 公共逻辑模块:包括 Util 类和通用模型。

  • dubbo-remoting 远程通讯模块:相当于 Dubbo 协议的实现,如果 RPC 用 RMI协议则不需要使用此包。

  • dubbo-rpc 远程调用模块:抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。

  • dubbo-cluster 集群模块:将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。

  • dubbo-registry 注册中心模块:基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。

  • dubbo-monitor 监控模块:统计服务调用次数,调用时间的,调用链跟踪的服务。

  • dubbo-config 配置模块:是 Dubbo 对外的 API,用户通过 Config 使用Dubbo,隐藏 Dubbo 所有细节。

  • dubbo-container 容器模块:是一个 Standlone 的容器,以简单的 Main 加载 Spring 启动,因为服务通常不需要 Tomcat/JBoss 等 Web 容器的特性,没必要用 Web 容器去加载服务。

整体上按照分层结构进行分包,与分层的不同点在于:

  • Container 为服务容器,用于部署运行服务,没有在层中画出。

  • Protocol 层和 Proxy 层都放在 rpc 模块中,这两层是 rpc 的核心,在不需要集群也就是只有一个提供者时,可以只使用这两层完成 rpc 调用。

  • Transport 层和 Exchange 层都放在 remoting 模块中,为 rpc 调用的通讯基础。

  • Serialize 层放在 common 模块中,以便更大程度复用。

依赖关系

图例说明:

  • 图中小方块 Protocol, Cluster, Proxy, Service, Container, Registry, Monitor 代表层或模块,蓝色的表示与业务有交互,绿色的表示只对 Dubbo 内部交互。

  • 图中背景方块 Consumer, Provider, Registry, Monitor 代表部署逻辑拓扑节点。

  • 图中蓝色虚线为初始化时调用,红色虚线为运行时异步调用,红色实线为运行时同步调用。

  • 图中只包含 RPC 的层,不包含 Remoting 的层,Remoting 整体都隐含在 Protocol 中。

调用链

展开总设计图的红色调用链,如下:

暴露服务时序

展开总设计图右边服务提供方暴露服务的蓝色初始化链,时序图如下:

引用服务时序

展开总设计图左边服务消费方引用服务的绿色初始化链,时序图如下:

领域模型

在 Dubbo 的核心领域模型中:

  • Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。

  • Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠拢,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

  • Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。

基本设计原则

  • 采用 Microkernel + Plugin 模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。

  • 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。

2)、服务暴露流程

spring的接口: BeanDefinitionParser (bean定义解析器)

DubboNamespaceHandler 注册标签,在DubboBeanDefinitionParser初始化时,注册。

 

3)、服务引用流程

ReferenceBean是FactoryBean

4)、服务调用流程

调用链

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值