概述
前文介绍了RPC思想和框架,可以移步:
远程过程调用RPC 1:举例理解
远程过程调用RPC 2:RPC思想与RPC框架
高性能的RPC框架
- RPC框架的需求与性能
- 序列化框架
- I/O模型与线程模型
- 服务方
- 客户端
- 同步Sync
- 异步Future
- 回调Callback
- 单向调用Oneway
- 网络传输
- 代理
- 负载均衡
- 服务发现与注册
- 参考
RPC框架的需求与性能
在前一篇文章中介绍了RPC框架的一些内容。一个高性能的RPC框架应该拥有在一些模块实现一些基本功能并提升自己的性能,可以是:
- 服务发现与注册
- 线程调度模型
- I/O调度模型
- 序列化框架
- 负载均衡策略
- 网络传输
其中最核心的是I/O调度模型、线程调度模型、序列化框架
序列化框架
在RPC调用的时候,需要把各种参数和调用信息,序列化成字节流传递给服务提供方,再把二进制字节流反序列化得到各种信息;在调用结束后,也需要将返回结果序列化和反序列化,最终调用方得到结果。
因此,序列化框架的选择对于一个RPC框架的性能影响是非常显著的。
影响序列化性能的关键因素:
- 序列化后的码流大小(网络带宽的占用)
- 序列化的性能(CPU资源占用)
- 是否支持跨语言(异构系统的对接和开发语言切换)
序列化框架 | 优点 | 缺点 |
---|---|---|
jdk原生 | 无法跨语言、序列化后的码流太大、序列化的性能差 | |
Fastjson | 接口简单易用、目前java语言中最快的json库 | 过于注重快,而偏离了“标准”及功能性 |
Protobuf | 序列化后码流小,性能高、结构化数据存储格式(XML JSON等)、通过标识字段的顺序,可以实现协议的前向兼容、结构化的文档更容易管理和维护 | 需要依赖于工具生成代码、支持的语言相对较少,官方只支持Java 、C++ 、python |
Hessian | 采用二进制协议的轻量级二进制web service协议,支持动态类型、跨语言 | 无法跨语言、序列化后的码流太大、序列化的性能差 |
Kryo | 基于Protobuf协议,只支持java语言,需要注册(Registration),然后序列化(Output),反序列化(Input) | 无法跨语言 |
但是,在进行选型的时候,还是要根据业务实际情况进行选择,可以考虑几个问题:
- 是否需要跨平台、跨语言
- 序列化速度
- 生成的体积
I/O模型与线程模型
服务方
常见的I/O模型有:同步阻塞I/O(BIO),非阻塞I/O(NIO),这里的I/O主要理解为网络I/O,例如事件轮询、编解码、数据传输等。采用多线程非阻塞模型可以提升这方面的性能。
图源:Dubbo官网-线程模型
线程模型主要可以理解为业务线程和I/O线程。业务线程主要执行的是业务逻辑,如果业务非常的复杂,例如需要频繁的操作数据库、进行复杂业务计算的话,就需要我们将业务线程分配到业务线程池中去,防止业务操作一直阻塞I/O线程。
上图是Dubbo的线程模型,可以看出,此时我们需要一个dispatcher将业务线程分配到业务线程池中。当然,也并不是必须分配到业务线程池中,这个是可选的。当业务逻辑简单时,可以直降将业务逻辑交由I/O线程去处理。这里Dubbo就提供了五种可选方案,具体Dubbo的内容后面文章再详细介绍。
客户端
成熟的RPC框架需要能够提供多种的调用方式:
- 同步Sync
- 异步Future
- 回调Callback
- 单向Oneway
RPC框架的性能和吞吐量与合理使用调用方式是息息相关的。
同步Sync
客户端线程发起RPC调用后,当前线程会一直阻塞,直至服务端返回结果或者处理超时异常。一般RPC框架默认的调用方式就是同步调用。所以客户端需要设置超时时间。
图源:23 架构设计:如何实现一个高性能分布式 RPC 框架
异步Future
同步调用发起后,客户需要一直等待有返回之后才能够继续客户逻辑,在此之间会被阻塞。因此,考虑异步的方式,客户端发起调用后不会再阻塞等待,而是拿到RPC框架返回的Future对象,调用结果会被服务端缓存,客户端自行决定后续何时获取返回结果。
图源:23 架构设计:如何实现一个高性能分布式 RPC 框架
回调Callback
其实设想现实中的场景,一个人(客户端)要求另外一个人(服务端)去做一件事情,一个非常正常的想法就是“你做完告诉我一声”。那么在进行设计的时候也可以通过回调的方式进行交流。回调的实现是接口或抽象类来实现的,我们把实现这种接口的类称为回调类,回调类的对象称为回调对象。
客户端发起调用时,将Callback对象传递给RPC框架,无须同步等待返回结果,直接返回。当获取到服务端响应结果或者超时异常后,再执行用户注册的Callback回调。所以Callback接口一般包含onResponse和onException两个方法,分别对应成功返回和异常返回两种情况。
单向调用Oneway
客户端发起请求之后直接返回,忽略返回结果。
图源:23 架构设计:如何实现一个高性能分布式 RPC 框架
网络传输
作为RPC思想两大核心内容,网络传输自然是对RPC框架的性能产生很重大的影响。
大部分主流RPC框架会选择TCP、HTTP协议,出名的gRPC框架使用的则是HTTP2。TCP、HTTP、HTTP2都是稳定可靠的,但其实使用UDP协议也是可以的,具体看业务使用的场景。
代理
就像在第一篇里面讲到的,需要依赖动态代理的方式,以实现屏蔽掉框架调用细节。代理类是在运行时生成的,所以代理类的生成速度、生成的字节码大小都会影响 RPC 框架整体的性能和资源消耗。
常见的动态代理方式有几种:
- JDK动态代理
通过反射调用的形式代理类中的方法,性能肯定低于直接调用。在运行时可以动态创建代理类,但是JDK动态代理的功能比较局限,代理对象必须实现一个接口,否则抛出异常。 - Cglib动态代理
Cglib 是基于 ASM 字节码生成框架实现的,通过字节码技术生成的代理类。所以性能要优于反射,且代理类型是不受限制的。代理方法方面,Cglib是有优势的,它采用了FastClass机制,可以效率更该的定位要调用的方法 - Javassist和ASM
二者都是Java字节码操作框架,使用起来难度较大。比反射的性能要高。
负载均衡
在分布式系统中,服务提供者和服务消费者都会有多台节点,采用合适的负载均衡策略是至关重要的,会影响PRC框架的吞吐量。受制于服务器性能等各方面的不同,合理的负载均衡策略才能发挥整体服务集群的性能。
几个常见的负载均衡策略:
- Round-Robin 轮询
依次轮询 - Weighted Round-Robin 权重轮询
按照不同服务节点的服务能力进行控制,增加权重系数,根据能力调整负载均衡状态 - Least Connections 最少连接数
客户端根据服务端节点当前的连接数进行负载均衡,客户端会选择连接数最少的一台服务器进行调用。类似的,也可以采用最少请求数、CPU利用率最低等其他维度进行分配 - Consistent Hash 一致性Hash
目前主流的负载均衡策略。是一种特殊的哈希算法,在服务端节点扩容或者下线时,尽可能保证客户端请求还是固定分配到同一台服务器节点。Consistent Hash算法是采用哈希环来实现的,通过Hash函数将对象和服务器节点放置在哈希环上,一般来说服务器可以选择IP + Port进行Hash,然后为对象选择对应的服务器节点,在哈希环中顺时针查找距离对象 Hash 值最近的服务器节点
在进行选择的时候,可以根据业务灵活选择负载均衡策略,根据CPU、连接数、内存、健康状态等都是可以的。
服务发现与注册
分布式系统中,存在多个服务实例,因此对实例的感知是非常重要的。
这里就使用注册中心来实现服务注册和发现的功能。服务端节点上线后自行向注册中心注册服务列表,节点下线时需要从注册中心将节点元数据信息移除。客户端向服务端发起调用时,自己负责从注册中心获取服务端的服务列表,然后在通过负载均衡算法选择其中一个服务节点进行调用。以上是最简单直接的服务端和客户端的发布和订阅模式,不需要再借助任何中间服务器,性能损耗也是最小的。
由于存在实例非正常下线的情况,需要对下线进行感知。可以采用主动通知+心跳检测的方案。对服务实例进行感知。
采用注册中心可以解耦客户端和服务端之间的关系,并且能够实现对服务的动态管理。服务配置可以支持动态修改,然后将更新后的配置推送到客户端和服务端,而无须重启任何服务。
目前常见的可以作为服务发现与注册模块的有:Zookeeper、Eureka、Etcd、Nacos等。
参考
23 架构设计:如何实现一个高性能分布式 RPC 框架
Dubbo 文档
回调(callback)
面试官:详细说说你对序列化的理解
JavaGuide
最后
以上就是懵懂咖啡为你收集整理的远程过程调用RPC 3:高性能的RPC框架RPC框架的需求与性能序列化框架I/O模型与线程模型网络传输代理负载均衡服务发现与注册参考的全部内容,希望文章能够帮你解决远程过程调用RPC 3:高性能的RPC框架RPC框架的需求与性能序列化框架I/O模型与线程模型网络传输代理负载均衡服务发现与注册参考所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复