Dubbo第一天

1.RPC 场景和过程

1.1RPC 场景

在微服务环境下,存在大量的跨 JVM 进行方法调用的场景,如下图:

具体到某一个调用来说,希望 A 机器能通过网络,调用 B 机器内的某个服务方法,并得到返回值:

1.2RPC 的实现切入口

从本质上来讲,某个 jvm 内的对象方法,是无法在 jvm 外部被调用的,如下的代码:

OrderService orderService = (OrderService) ctx.getBean("orderService");

OrderEntiry entiry = orderService.getDetail("1");

orderService.getDetail("1")的这一句调用,是无法脱离本地 jvm 环境被调用的。但是,好在 java 中的对象方法的调用,还有反射模式的调用:

Method method = target.getClass().getMethod(methodName, argTypes);

return method.invoke(target, args); 

只要我们传入反射需要的目标对象 orderService,方法名称 getDetail和参数值"1",反射就能将 orderService.getDetail 调用起来于是,我们可以把调用某个方法的过程,改造成这样:

Map info = new HashMap();

info.put("target","orderService");

info.put("methodName","getDetail");

info.put("arg","1");

//反射调用

Object result = InvokeUtils.call(info,ctx); 

现在,只要谁告诉我了反射需要信息,target/method/arg,我就能调用本地的任何对象方法了。

1.3网络通信传递反射信息

1.3.1 定义一个继承自 remote 接口的类

public interface InfoService extends Remote {//继承 remote 接口

    String RMI_URL = "rmi://127.0.0.1:9080/InfoService";

    int port = 9080;

    Object passInfo(Mapinfo) throws RemoteException;

}

创建一个实现类(为简化实现,继承 UnicastRemoteObject 类):

public class InfoServiceImpl extends UnicastRemoteObject implements InfoService {

    public InfoServiceImpl() throws RemoteException {

    super();

    }

    @Override

    public Object passInfo(Mapinfo) {

    System.out.println("恭喜你,调通了,参数:"+JSON.toJSONString(info));

    info.put("msg","你好,调通了!");

    return info;}

}

1.3.2 RMI 开放服务到指定 URL

只需要将实例绑定注册到指定的 URL 和 port 上,远程即可调用此实例;

InfoService infoService = new InfoServiceImpl();

//注冊通讯端口

LocateRegistry.createRegistry(9080);

//注冊通讯路径

Naming.bind("rmi://127.0.0.1:9080/InfoService", infoService); 

1.3.3 RMI 远程通过 URL 连接并调用

//取远程服务实现

infoService = (InfoService) Naming.lookup(InfoService.RMI_URL);

//呼叫远程反射方法

Mapinfo = new HashMap();

info.put("target","orderService");

info.put("methodName","getDetail");

info.put("arg","1");

Object result = infoService.passInfo(info); 

至此,已经实现了通过 RMI 跨机器传递 target/method/arg。

1.4远程调用的融合

可以看到,前两步,已经实现了跨机器的反射信息收发和反射动作调用。我们现在只需要在 InfoService 的实现上,对传递过来的 info 信息,直接发起反射调用即可。

InfoService infoService = new InfoServiceImpl(){

    public Object passInfo(Mapinfo) {  //对象,方法,参数

    super.passInfo(info);  //info 内包含的信息,是反射需要的信息

    Object result = InvokeUtils.call(info,ctx);

    System.out.println("测试 InvokeUtils.call 调用功能,调用结果:" +     JSON.toJSONString(result));

    return result;

    }

}; 

这样,远程机器只要通过 infoService 传递信息过来,就自动将目标服务反射调用,并返回结果值回去,整个 RPC 过程完成。

1.5对客户端友好的透明化封装

虽然在上一节中,RPC 的整个调用链条已经拉通,但是我们发现还有一个易出错的地方,就是客户端封装反射信息的地方,功能不够内聚,容易出现手误,代码易读性也很差。

//呼叫远程反射方法

Mapinfo = new HashMap();

info.put("target","orderService");

info.put("methodName","getDetail");

info.put("arg","1");

Object result = infoService.passInfo(info); 

有什么改善的办法呢?仔细核对,其实 target/method/arg 这三个信息,全部都可以从接口方法调用OrderService.getDetail("1")中得到。

于是,我们可以为此接口做一个代理对象,在代理对象内部完成反射信息的包装:

OrderService service = new OrderService(){

    @Override

    public OrderEntiry getDetail(String id) {

        Map info = new HashMap();

        // 写死了反射的目标,静态代理

        info.put("target","orderService");//对象

        info.put("methodName","getDetail");//方法

        info.put("arg",id);//参数


        OrderEntiry result = null;

        try {

        result = (OrderEntiry)infoService.passInfo(info);

        } catch (RemoteException e) {

        e.printStackTrace();

    }

        return result;

    }

}; 

大功告成,从此,客户端远程传递反射信息的过程,直接变成调用接口的代理对象即可。

调用者,甚至不再需要区分,此接口代理对象到底是谁,像调用正常的本地服务一起使用就 ok。

1.6Dubbo 使命

在上面的章节,我们重点详述了,一个具体的 RPC 调用的全过程。那么在现实工作中,服务节点间的 RPC 调用是非常普遍并且错踪复杂的。

我们除了要关心 RPC 的过程实现,还需要考虑:

1.服务方是集群时,如何挑选一台机器来响应客户端?

2.因网络抖动引起的调用失败,如何重试来弥补?

3.服务方机器的动态增减,如何能够让客户端及时了解到并做出调整?

..... Dubbo 的使命,即是解决上述围绕 RPC 过程的一览子问题


2. Dubbo 简介

在分布式服务架构下,各个服务间的相互 rpc 调用会越来越复杂。最终形成网状结构,此时服务的治理极为关键。

Dubbo 是一个带有服务治理功能的 RPC 框架,提供了一套较为完整的服务治理方案,其底层直接实现了 rpc 调用的全过程,并尽力做事 rpc 远程对使用者透明。下图展示了 Dubbo 服务治理的功能。

简单的说,Dubbo 就是个服务调用的框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有使用 Dubbo 这样的分布式服务框架的需求,并且本质上是个服务调用的东东。

其核心部分包含:

 远程通讯:提供对多种基于长连接的 NIO 框架抽象封装,包括多种线程模型、序列化以及“请求-响应”模式的信息交换方式。

 集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持以及软负载均衡,失败容错、地址路由、动态配置等集群支持。

 自动发现:基于注册中心目录服务,使服务消费方能动态的查询×××提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

2.1dubbo 的架构及特点

下图展示了 dubbo 的整体结构:

Dubbo 总体架构设计一共划分了 10 层,而最上面的 Service 层是留给实际想要使用 Dubbo 开发分布式服务的开发者实现业务逻辑的接口层。图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。

 服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。

 配置层(Config):对外配置接口,以 ServiceConfig 和 ReferenceConfig 为中心,可以直接 new 配置类,也可以通过 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。Protocol 是服务域,它是 Invoker暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。Invoker 是实体域,它是 Dubbo 的核心模型,其他模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用。它有可能是一个本地的实现,也可能是一个远程的实现,也可能是一个集群实现。

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

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

 数据序列化层(Serialize):可复用的一些工具,扩展接口为 Serialization、ObjectInput、ObjectOutput 和 ThreadPool。

从上图可以看出,Dubbo 对于服务提供方和服务消费方,从框架的 10 层中分别提供了各自需要关心和扩展的接口,构建整个服务生态系统(服务提供方和服务消费方本身就是一个以服务为中心的)。

2.2Dubbo 服务的角色关系

服务提供方和服务消费方之间的调用关系,如图所示:

节点角色说明:

调用关系说明:

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

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

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

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

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

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


3. Dubbo 的基础配置使用

3.1xml 配置方式

3.1.1 dubbo 功能标签集

服务配置,用于暴露一个服务,定义服务的元信息,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心。

引用配置,用于创建一个远程服务代理,一个引用可以指向多个注册中心。

协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。

应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。

注册中心配置,用于配置连接注册中心相关信息。

模块配置,用于配置当前模块信息,可选。

监控中心配置,用于配置连接监控中心相关信息,可选。

提供方的缺省值,当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采用此缺省值,可选。

消费方缺省配置,当 ReferenceConfig 某属性没有配置时,采用此缺省值,可选。

方法配置,用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息。

用于指定方法参数配置。

dubbo 标签的使用以及标签属性详解见下。

3.1.2 标签详解

所有配置项分为三大类,参见下表中的"作用"一列。

 服务发现:表示该配置项用于服务的注册与发现,目的是让消费方找到提供方。

 服务治理:表示该配置项用于治理服务间的关系,或为开发测试提供便利条件。

 性能调优:表示该配置项用于调优性能,不同的选项对性能会产生影响。 所有配置最终都将转换为 URL 表示,并由服务提供方生成,经注册中心传递给消费方,各属性对应 URL 的参数,参见配置项一览表中的"对应 URL 参数"列。

注意:只有 group,interface,version 是服务的匹配条件,三者决定是不是同一个服务,其它配置项均为调优和治理参数。


服务提供者暴露服务配置:

配置类:com.alibaba.dubbo.config.ServiceConfig

服务消费者引用服务配置:

配置类:com.alibaba.dubbo.config.ReferenceConfig

服务提供者协议配置:

配置类:com.alibaba.dubbo.config.ProtocolConfig

说明:如果需要支持多协议,可以声明多个标签,并在中通过 protocol 属性指定使用的协议。


注册中心配置:

配置类:com.alibaba.dubbo.config.RegistryConfig

说明:如果有多个不同的注册中心,可以声明多个标签,并在的 registry 属性指定使用的注册中心。

监控中心配置:

配置类:com.alibaba.dubbo.config.MonitorConfig

应用信息配置:

配置类:com.alibaba.dubbo.config.ApplicationConfig

模块信息配置:

配置类:com.alibaba.dubbo.config.ModuleConfig

服务提供者缺省值配置:

配置类:com.alibaba.dubbo.config.ProviderConfig

说明:该标签为标签的缺省值设置。

服务消费者缺省值配置:

配置类:com.alibaba.dubbo.config.ConsumerConfig

说明:该标签为标签的缺省值设置。

方法级配置:

配置类:com.alibaba.dubbo.config.MethodConfig

说明:该标签为的子标签,用于控制到方法级。

比如:


方法参数配置:

配置类:com.alibaba.dubbo.config.ArgumentConfig

说明:该标签为的子标签,用于方法参数的特征描述,比如:


选项参数配置:

配置类:java.util.Map

说明:该标签为的子标签,用于配置自定义参数,该配置项将作为扩展点设置自定义参数使用。

3.2注解方式

注解方式的底层与 XML 一致,只是表现形式上的不同。目标都是将 Dubbo 基础信息配入,主要涉及以下五个必不可少的信息:ApplicationConfig、ProtocolConfig 、 RegistryConfig、service、reference 。

3.2.1 EnableDubbo 开启服务

@EnableDubbo:开启注解 Dubbo 功能 ,其中可以加入 scanBasePackages 属性配置包扫描的路径,用于扫描并注册 bean。其中 封装了组件 @DubboComponentScan,来扫描 Dubbo @Service 注解暴露 Dubbo 服务,以及扫描 Dubbo @Reference 字段或者方法注入 Dubbo 服务代理。 

其它 Dubbo 三种公共信息的配置,有两种方式,根据自己喜好选用。

3.2.2 Configuration 方式配置公共信息

@Configuration 方式:分别将 ApplicationConfig、ProtocolConfig 、 RegistryConfig 创建到 IOC容器中即可,如下:

@Configuration

@EnableDubbo(scanBasePackages = "com.enjoy.service")

class ProviderConfiguration {

    @Bean

    public ApplicationConfig applicationConfig() {

    ApplicationConfig applicationConfig = new ApplicationConfig();

    applicationConfig.setName("busi-provider");

    return applicationConfig;

    }

    @Bean

    public RegistryConfig registryConfig() {

    RegistryConfig registryConfig = new RegistryConfig();

    registryConfig.setProtocol("zookeeper");

    registryConfig.setAddress("192.168.0.128");

    registryConfig.setPort(2181);

    return registryConfig;

    }

    @Bean

    public ProtocolConfig protocolConfig() {

    ProtocolConfig protocolConfig = new ProtocolConfig();

    protocolConfig.setName("dubbo");

    protocolConfig.setPort(20880);

    return protocolConfig;

    }

}

3.2.3 Property 方式自动装配公共信息

Property 方式:使用 Springboot 属性文件方式,由 Dubbo 自动将文件信息配置入容器,示例如下:

@Configuration

@EnableDubbo(scanBasePackages = "com.enjoy.service")

@PropertySource("classpath:/dubbo-provider.properties")

static class ProviderConfiguration {}

dubbo-provider.properties 文件内容

dubbo.application.name=busi-provider

dubbo.registry.address=zookeeper://192.168.0.128:2181

dubbo.protocol.name=dubbo

dubbo.protocol.port=20880

你可能感兴趣的:(Dubbo第一天)