让我带你弄明白什么是RPC ,帮你整理一下你的小脑瓜!

639 阅读12分钟

关注并私信我领取关于Java的学习资料和最新面试资料

RPC Dubbo

一、系统架构的演变

1、单一应用架构

将所有的功能模块都放在1个工程中编码、编译、打包并且部署在1个tomcat容器中的架构。这样维护成本比较低。 优点:

  1. 项目前期开发周期快,团队成员少的时候能够快速迭代;
  2. 架构简单:MVC架构,只需借助IDE开发,调试即可。
  3. 易于测试:只通过单元测试或者浏览器完成
  4. 易于部署

缺点:

  1. 随着时间推移业务增加,功能不断迭代,项目会变得臃肿,业务耦合严重

  2. 新增业务困难

  3. 核心业务与边缘业务混在一块儿,出现问题相互影响。

2、垂直架构

为了解决单体框架出现的问题,开始按着业务做垂直划分。把原来的一个单体拆成一堆单体应用,这时候就由原来的单应用变成多应用部署。 优点:

  1. 可以针对不同模块进行优化;
  2. 方便水平扩展,负载均衡,容错率提高。
  3. 系统间相互独立,互不影响,新的业务迭代时会更加高效。

缺点:

  1. 服务之间相互调用,如果某个服务的端口或者IP地址发生改变,调用的系统得手动改变。
  2. 搭建集群之后,实现负载均衡比较复杂;
  3. 服务之间调用方式不统一,基于httpclient、webservice,接口协议不统一。
  4. 服务监控不到位:除了依靠端口、进程的监控,调用的成功率、失败率、总耗时等等这些的监控指标是没有用的。

3、SOA架构

【领取资料】 即面向服务的架构。其思想就是根据实际业务,把业务拆分成合适的、独立部署的模块,模块之间相互独立(通过Webservice/Double等技术进行通信)

二、RPC

1、RPC核心和调用的大体流程

1)RPC(remote procedure call) 远程过程调用,简单的理解是一个节点请求另外1个节点的服务。

调用过程: 客户端(Client):服务调⽤⽅。

客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成⽹络消息, 再通过⽹络传输发送给服务端。

服务端存根(Server Stub):接收客户端发送过来的请求消息并进⾏解包,然后再调⽤本地服务进⾏ 处理。

服务端(Server):服务的真正提供者。

Network Service:底层传输,可以是 TCP 或 HTTP

2、完整的RPC架构图

在一个典型RPC的使用场景中,包括了服务发现、负载、容错、网络传输、序列化等组件,其中RPC协议就指了程序如何进行网络传输和序列化。

3、RPC核心功能及实现

服务寻址

数据流的序列化和反序列化

三、网络传输

1、服务寻址

  1. 在RPC中,所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。
  2. 客户在做远程调用时必须附上这个ID。然后我们还需要在客户端和服务端分别维护⼀个 函数和Call ID的对应表。
  3. 当客户端需要进⾏远程调⽤时,它就查⼀下这个表,找出相应的 Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调⽤的函数,然后执⾏相应函数的代码

2、序列化和反序列化

本地调用,我们只需要把参数压到栈里,然后然函数直接从栈里读就行。但是在远程调用时客户端和服务端是不同的进程,不能通过内存传递参数。这时就需要客户端把参数转换成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。

定义:

序列化:将对象转换转换成二进制流程的过程叫做序列化

反序列化:将二进制流转换成对象的过程叫做反序列化

3、网络传输

【领取资料】 网络传输层需要把call ID和序列化的参数字节流传给服务端,然后再把序列化好的调用结果传给客户端。

所以,要实现一个RPC框架,只需要把以下三点实现了就基本完成; :

  1. call id映射:可以直接使用函数字符串,也可以使用整数ID。映射表一般是1个Hash表。
  2. 序列化反序列化:可以自己写,也可以使用protobuf或者FlatBuffers之类的。
  3. 网络传输,可以自己写Socket,或者用Netty

四、Dubbo

经典的RPC框架

调用关系说明:

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

Config 配置中心

Proxy 代理

Registry 注册中心

Cluster 负载均衡、路由(轮询、权重、一致性hash)

Monitor 监控

Protocal 发起调用

Exchange 交换

Transport 传输(netty)

Serialize 序列化

dubbo 架构特点分别是连通性健壮性伸缩性、以及向未来架构的升级

连通性

  • 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
  • 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
  • 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
  • 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
  • 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
  • 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
  • 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
  • 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者 监控中心宕掉不影响使用,只是丢失部分采样数据
  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
  • 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
  • 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者

健壮性

  • 监控中心宕掉不影响使用,只是丢失部分采样数据
  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

伸缩性

  • 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
  • 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者

五、Springboot 中Dubbo使用

1、安装

  1. 安装zookeeper
  2. 监控中心 dubbo admin

2、使用方式

配置类、注解方式

Service 一定要引入dubbo的注解。

看官网

3、超时配置

【领取资料】

<dubbo:reference interface="com.foo.BarService" check="false"

timeout="1000"/>

配置超时时间的优先顺序如下:

  1. 方法级优先,接口级次之,全局配置再次之
  2. 如果级别一样,则消费方优先,提供方次之。 其中,服务提供方配置,通过URL经由注册中心传递给消费方。

Dubbo常用的配置

启动检查

<dubbo:reference interface="com.foo.BarService" check="false" />

超时配置

<dubbo:reference interface="com.foo.BarService" check="false"

timeout="1000"/>

重试次数

幂等(设置重试次数,查询、删除、修改**) ⾮幂等(不能设置,新增)

<dubbo:reference id="orderService"

interface="com.end.dubbo.api.service.OrderService" retries="3" />

多版本分组

<dubbo:reference id="orderService"

interface="com.end.dubbo.api.service.OrderService" group="g1" />

六、api 和spi

Spi:调用方来定制接口,实现方来针对接口实现不同的实现。调用方来选择自己需要的实现方法。 常见的spi:

  1. 数据库驱动
  2. 日志log
  3. dubbo扩展点开发
  4. Springboot自动装配机

JDK中的spi实现方式

原理:

在ServiceLoader的load⽅法中⾸先会获取上下⽂类加载器,然后构造⼀个ServiceLoader,在 ServiceLoader中有⼀个懒加载器,懒加载器会通过BufferedReader来从META-INF/services路径 下读取 对应的接⼝名的全路径名⽂件,也就是我们配置的⽂件,然后通过⽂件的类解析器读取⽂件中的内 容,再通过类加载器加载类的全路径。

缺点:

  1. 无法按需加载;
  2. 不具有IOC的功能;
  3. serviceLoader不是线程安全的,会出现线程安全问题

dubbo中的spi实现方式

man=dubbo.impl.Man

woman=dubbo.impl.Woman 可以按需加载,可扩展的能力

七、线程派发模型

1、配置

<dubbo:protocol name="dubbo" port="20880" threadpool="fixed" threads="200"

iothreads="8" accepts="0" queues="100" dispatcher="all"/>

在这里插入图片描述

Netty

【领取资料】

在netty中存在两种线程:boss线程和worker线程

1)boss线程

作用:accept客户端的连接:将接收到的连接注册到一个worker线程上。

通常情况下,服务端每绑定一个端口,开启一个boss线程

2)worker线程

作用:处理注册在其身上的连接connection上的各种io事件

个数:核数+1

注意:一个worker线程可以注册多个connection;

⼀个connection只能注册在⼀个worker线程上

八、线程池的5种派发策略

默认是all:所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,⼼跳等。 即 worker线程接收到事件后,将该事件提交到业务线程池中,⾃⼰再去处理其他事。

  • direct:worker线程接收到事件后,由worker执⾏到底(所有消息都不派发到线程池,全部在 Io线程上直接执⾏)。
  • message:只有请求响应消息派发到线程池,其它连接断开事件,⼼跳等消息,直接在 IO线 程上执⾏
  • **execution:**只请求消息派发到线程池,不含响应(客户端线程池),响应和其它连接断开事 件,⼼跳等消息,直接在 IO 线程上执⾏
  • **connection:**在 IO 线程上,将连接断开事件放⼊队列,有序逐个执⾏,其它消息派发到线程

dubbo4个常用的threadpool:

fixed 固定⼤⼩线程池,启动时建⽴线程,不关闭,⼀直持有。

cached 缓存线程池,空闲⼀分钟⾃动删除,需要时重建。

limited 可伸缩线程池,但池中的线程数只会增⻓不会收缩。只增⻓不收缩的⽬的是为了避免 收缩时突然来了⼤流量引起的性能问题。

eager 优先创建Worker线程池。在任务数量⼤于corePoolSize但是⼩于maximumPoolSize 时,优先创建Worker来处理任务。当任务数量⼤于maximumPoolSize时,将任务放⼊阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相⽐于cached:cached在任务数量超过maximumPoolSize时直接抛出异常⽽不是将任务放⼊阻塞队列。

Dubbo的线程派发模型 整体步骤:(受限于派发策略,以默认的all为例)

  1. 客户端的主线程发出⼀个请求后获得future,在执⾏get时进⾏阻塞等待;

  2. 服务端使⽤worker线程(netty通信模型)接收到请求后,将请求提交到server线程池中进⾏处理!

  3. server线程处理完成之后,将相应结果返回给客户端的worker线程池(netty通信模型),最后,worker线程将响应结果提交到client线程池进⾏处理!

  4. client线程将响应结果填充到future中,然后唤醒等待的主线程,主线程获取结果,返回给客户端。

在这里插入图片描述