1、RPC原理

1.1、什么是RPC

RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP/IP或UDP,为通信程序之间携带信息数据。RPC将原来的本地调用转变为调用远端的服务器上的方法,给系统的处理能力和吞吐量带来了近似于无限制提升的可能。在OSI网络通信模型中,RPC跨域了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

1.2 RPC架构

一个完整的RPC架构里面包含了四个核心的组件,分别是Client,Client Stub,Server以及Server Stub,这个Stub可以理解为存根。

  • 客户端(Client),服务的调用方。
  • 客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
  • 服务端(Server),真正的服务提供者。
  • 服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。

1.3 RPC调用过程

RPC&Dubbo - 图1

(1) 客户端(client)以本地调用方式(即以接口的方式)调用服务;
(2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);
(3) 客户端通过sockets将消息发送到服务端;
(4) 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);
(5) 服务端存根( server stub)根据解码结果调用本地的服务;
(6) 本地服务执行并将结果返回给服务端存根( server stub);
(7) 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);
(8) 服务端(server)通过sockets将消息发送到客户端;
(9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);
(10) 客户端(client)得到最终结果。
RPC的目标是要把2、3、4、7、8、9这些步骤都封装起来。
注意:无论是何种类型的数据,最终都需要转换成二进制流在网络上进行传输,数据的发送方需要将对象转换为二进制流,而数据的接收方则需要把二进制流再恢复为对象。

2、自定义RPC框架范例

设计框架视图如下:
rpc.jpg

与1.3架构图相比较,rpc-sample-client相当于client-functions,通过接口方法发起请求;而rpc-client相当于client-stub,负责把方法和参数序列化并发送给查找到的服务器。

rpc-server相当于server stub,负责把客户端发送过来的消息反序列化,并通过反射调用本地服务,再把本地服务返回的结果序列化发送给客户端;

rpc-sample-server则相当于server-functions,提供本地服务并返回得出的结果给rpc-server。

rpc-client等待接收rpc-server返回的结果,并将结果反序列化,再把反序列化最终得到的结果返回给rpc-sample-client。

rpc-common提供基于netty的序列化与反序列化方法,并封装rpc请求和rpc响应,序列化和反序列的对象就是封装好的RpcRequest和RpcResponse。

rpc-registry则是基于zookeeper设计的分布式服务器动态上下线通知的应用程序,通过rpc-registry可以查找服务器或者注册服务器,用于均衡负载。

rpc-client与rpc-server之间的通信是基于netty的,netty底层也是sockets设计,但是效率比sockets高。

3、Dubbo

dubbo-architecture.jpg

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

调用关系说明
  1. 服务容器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

用法
  1. **提供方**增加暴露服务配置 `<dubbo:service>`,在**消费方**增加引用服务配置 `<dubbo:reference>`

入门
  1. provider.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
  5. 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">
  6. <!-- 提供方应用信息,用于计算依赖关系 -->
  7. <dubbo:application name="hello-world-app" />
  8. <!-- 使用multicast广播注册中心暴露服务地址 -->
  9. <dubbo:registry address="multicast://224.5.6.7:1234" />
  10. <!-- 用dubbo协议在20880端口暴露服务 -->
  11. <dubbo:protocol name="dubbo" port="20880" />
  12. <!-- 声明需要暴露的服务接口 -->
  13. <dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" />
  14. <!-- 和本地bean一样实现服务 -->
  15. <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl" />
  16. </beans>
  1. consumer.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
  5. 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">
  6. <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
  7. <dubbo:application name="consumer-of-helloworld-app" />
  8. <!-- 使用multicast广播注册中心暴露发现服务地址 -->
  9. <dubbo:registry address="multicast://224.5.6.7:1234" />
  10. <!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
  11. <dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" />
  12. </beans>

配置
标签 用途 解释
<dubbo:service/> 服务配置 用于暴露一个服务,定义服务的元信息,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心
<dubbo:reference/> 引用配置 用于创建一个远程服务代理,一个引用可以指向多个注册中心
<dubbo:protocol/> 协议配置 用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受
<dubbo:application/> 应用配置 用于配置当前应用信息,不管该应用是提供者还是消费者
<dubbo:module/> 模块配置 用于配置当前模块信息,可选
<dubbo:registry/> 注册中心配置 用于配置连接注册中心相关信息
<dubbo:monitor/> 监控中心配置 用于配置连接监控中心相关信息,可选
<dubbo:provider/> 提供方配置 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采用此缺省值,可选
<dubbo:consumer/> 消费方配置 当 ReferenceConfig 某属性没有配置时,采用此缺省值,可选
<dubbo:method/> 方法配置 用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息
<dubbo:argument/> 参数配置 用于指定方法参数配置

优先级
  • 方法级优先,接口级次之,全局配置再次之。
  • 如果级别一样,则消费方优先,提供方次之。

zookeeper

默认所有的配置都存储在 /dubbo/config 节点,具体节点结构图如下:

zk-configcenter.jpg

属性配置

Dubbo 可以自动加载 classpath 根目录下的 dubbo.properties

重写与优先级

dubbo-properties-override.jpg

优先级从高到低:

  • JVM -D 参数:当你部署或者启动应用时,它可以轻易地重写配置,比如,改变 dubbo 协议端口;
  • XML:XML 中的当前配置会重写 dubbo.properties 中的;
  • Properties:默认配置,仅仅作用于以上两者没有配置时。
  1. 如果在 classpath 下有超过一个 dubbo.properties 文件,比如,两个 jar 包都各自包含了 dubbo.properties,dubbo 将随机选择一个加载,并且打印错误日志。
  2. 如果 id 没有在 protocol 中配置,将使用 name 作为默认属性。

注解配置

基础配置:

  1. 服务:
  1. # dubbo-provider.properties
  2. dubbo.application.name=annotation-provider
  3. dubbo.registry.address=zookeeper://127.0.0.1:2181
  4. dubbo.protocol.name=dubbo
  5. dubbo.protocol.port=20880
  1. 消费:
  1. # dubbo-consumer.properties
  2. dubbo.application.name=annotation-consumer
  3. dubbo.registry.address=zookeeper://127.0.0.1:2181
  4. dubbo.consumer.timeout=3000

暴露服务:@Service

启用dubbo:@EnableDubbo

消费服务: @Reference

配置项
  1. **1、启动时间检查:**
  1. <!--关闭某个服务的启动时检查 (没有提供者时报错)-->
  2. <dubbo:reference interface="com.foo.BarService" check="false" />
  1. **2、集群容错:**

cluster.jpg

  1. **2.1. Failover Cluster:**
  2. 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 `retries="2"` 来设置重试次数(不含第一次)
  3. **2.2. Failfast Cluster:**
  4. 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
  5. **2.3.Failsafe Cluster:**
  6. 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
  7. **2.4.Failback Cluster:**
  8. 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
  9. **2.5.Forking Cluster:**
  10. 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 `forks="2"` 来设置最大并行数。
  11. **2.6.Broadcast Cluster:**
  12. 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

示例:

  1. <dubbo:service cluster="failsafe" />

负载均衡

Random LoadBalance

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

RoundRobin LoadBalance

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

LeastActive LoadBalance

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

ConsistentHash LoadBalance

  • 一致性 Hash,相同参数的请求总是发到同一提供者。
  • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
  • 算法参见:http://en.wikipedia.org/wiki/Consistent_hashing
  • 缺省只对第一个参数 Hash,如果要修改,请配置 <dubbo:parameter key="hash.arguments" value="0,1" />
  • 缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key="hash.nodes" value="320" />

示例: 也可定义在消费者dubbo:reference 、方法dubbo:method 级别上

  1. <dubbo:service interface="..." loadbalance="roundrobin" />

直连提供者

不借助注册中心,直接连接提供者

  1. <dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />

多版本,灰度发布

可以按照以下的步骤进行版本迁移:

  1. 在低压力时间段,先升级一半提供者为新版本
  2. 再将所有消费者升级为新版本
  3. 然后将剩下的一半提供者升级为新版本

示例:在消费者、提供者上定义版本号

  1. <dubbo:service interface="com.foo.BarService" version="1.0.0" />

参数验证

示例:

  1. import javax.validation.constraints.Future;
  2. import javax.validation.constraints.Max;
  3. import javax.validation.constraints.Min;
  4. import javax.validation.constraints.NotNull;
  5. import javax.validation.constraints.Past;
  6. import javax.validation.constraints.Pattern;
  7. import javax.validation.constraints.Size;
  8. // 省略……………………
  9. @NotNull // 不允许为空
  10. @Size(min = 1, max = 20) // 长度或大小范围
  11. private String name;
  12. // 省略………………
  13. @Pattern(regexp = "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$")
  14. // 省略

泛化调用

泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成。

在 Spring 配置申明 generic="true"

  1. <dubbo:reference id="barService" interface="com.foo.BarService" generic="true" />