RPC框架
RPC概述
分布式节点通信
跨节点通信方式
- Socket:原生socket编程。
- RMI:远程方法调用,只用于Java,可以把RMI看作是用java语言实现了RPC。
- 标准公有协议:利用标准的公有协议进行跨节点服务调用,例如:http+xml,RESTful+json,webs-ervice等。
- RPC:是一个计算机通信协议/规范/标准,允许运行于一台计算机的程序调用另一台计算机的子程序。就像调用本地程序一样,且是一种Client/Server模式。
RPC的作用及优势
RPC 的主要功能目标是让构建分布式计算(应用)更容易,是一种通过网络从远程计算机程序上请求服务, 而不需要了解底层网络技术的协议规范,简单的来说就是像调用本地服务一样调用远程服务,对开发者而言是透明的。
为什么用RPC
- 分布式设计
- 部署灵活
- 解耦服务
- 扩展性强
常见RPC框架
- Dubbo:阿里巴巴,java
- gRPC:Google,多语言
- Thrift:Facebook/apache,多语言
- Spring Cloud:不仅仅是RPC,更多的是微服务架构下的一站式解决方案
RPC的优势
- RPC框架一般使用长链接,不必每次通信都要3次握手,减少网络开销
- RPC框架一般都有注册中心,有丰富的监控管理
- 发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作
- 协议私密,安全性较高
- rpc 能做到协议更简单内容更小,效率更高
- rpc是面向服务的更高级的抽象,支持服务注册发现,负载均衡,超时重试,熔断降级等高级特性
RPC架构设计
服务端
- 服务注册
- 网络传输
- 协议解码
- 反序列化(解码)
- 调用模块
- 故障检测
- 序列化(编码)
- 协议编码
- 线程模型
客户端
- 服务发现
- 路由
- 负载均衡
- 超时重试
- 限流熔断
- 序列化(编码)
- 协议编码
- 网络传输
- 协议解码
- 反序列化(解码)
- 线程模型
注册中心
- 保存(服务名称,ip,port,接口列表元数据信息)
- 客户端主动订阅服务,当客户端订阅的服务有变化时主动推送给客户端。
- 定时向客户端发送心跳检测
- 服务单发布服务信息
- 定时向服务端发送心跳检测
RPC框架架构分析
RPC调用时序
RPC流程图
RPC框架实现要点
注册中心
服务注册发现的作用
在高可用的生产环境中,服务一般都以集群方式提供服务,集群里面的IP 等重要参数信息可能随时会发生变化,节点也可能会动态扩缩容,客户 端需要能够及时感知服务端的变化,获取集群最新服务节点的连接信息 ,而这些变化要求是要对调用方应用无感知的
主流服务注册工具
- Zookeeper
- Consul
- Nacos
注册中心数据结构
代理技术
为什么要用代理
RPC的调用对用户来讲是透明的,内部核心技术采用的就是代理技术,RPC 会自动给接口生成一个代理实现,当我们在项目中注入接口的时候,运行过程中实际绑定的是这个接口生成的代理实现。在接口方法被调用的时候,它实际上是被生成代理类拦截到了,这样就可以在生成的代理类里面,加入其他调用处理逻辑
常见代理技术
JDK动态代理
在运行期动态的创建代理类,它是通过接口生成代理类的,与静态代理相比更加灵活,但是也有一定的限制,第一是代理对象必须实现一个接口,否则会报异常。第二是有性能问题,因为是通过反射来实现调用的,所以比正常的直接调用来得慢,并且通过生成类文件也会多消耗部分方法区空间,可能引起Full GC。
ASM
ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入Java虚拟机之前动态改变类行为(也就是生成的代码可以覆盖原来的类也可以是原始类的子类)。不过ASM在创建class字节码的过程中,操纵的是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解
CGLIB
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库。其原理是动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法,在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快
bytebuddy
Byte Buddy本身也是基于 ASM API 实现的,是一个较高层级的抽象的字节码操作工具,通过使用 Byte Buddy,任何熟悉 Java 编程语言的人都有望非常容易地进行字节码操作。
Javassist
Javassist 使操作Java字节码变得简单,一个可以用于编辑Java字节码的类库,提供了两种级别的API:源码级别和字节码级别。如果用户使用源码级API,他们可以在不需要过多了解Java字节码规范的前提下使用它提供的基于java语言的API来编辑字节码文件。如果使用字节码级API则允许用户直接编辑字节码文件。Javassist在复杂的字节码级操作上提供了更高级别的抽象层。另外Javassist使用了反射机制,这使得它在运行时比ASM慢。
序列化技术
序列化的作用
在网络传输中,数据必须采用二进制形式, 所以在RPC调用过程中, 需要采用序列化技术,对入参和出参进行序列化与反序列化
序列化选型要点
- 解析效率
- 压缩率、压缩后体积
- 扩展性、兼容性
- 可读性、可调试
- 跨语言
- 通用性
常见序列化技术框架
JDK原生序列化
- JAVA语言本身提供,使用比较方便和简单
- 不支持跨语言处理,性能相对不是很好,序列化以后产生的数据相对较大
JSON
轻量级数据交换格式 1.可读性好,方便阅读和调试,多语言支持,序列化以后的字节码文件相对较大,效率相对不高,但对比XML序列化后的字节流更小,在企业运用普遍,特别是对前端和三方提供api。
二进制Hessian2
- Hessian 是一个动态类型,二进制序列化,并且支持跨语言特性的序列化框架。
- Hessian 性能上要比 JDK、JSON 序列化高效很多,并且生成的字节数也更小。有非常好的兼容性和稳定性,所以Hessian 更加适合作为 RPC 框架远程通信的序列化协议
Protobuf
开源,高效
- Google 推出的开源序列库,它是一种轻便、高效的结构化数据存储格式,多语言支持。
- 速度快,压缩比高,体积小,序列化后体积相比 JSON、Hessian 小很多
- 消息格式的扩展、升级和兼容性都不错,可以做到向后兼容。
网络
RPC通信协议
系统IO
IO选型
RPC的调用过程中涉及到网络IO的操作,一般来说网络IO往往会成为系统的瓶颈所在,而不管上层应用如何使用,底层都是基于操作系统的IO模型。
IO模型
- 同步阻塞IO
- 同步非阻塞IO
- IO多路复用
- 信号驱动IO
- 异步非阻塞IO
线程模型
异步如何实现 常用的方式就是Future 方式,它是返回 Future 对象,通过GET方式获取结果;或者采用入参为 Callback 对象的回调方式,处理结果。
超时
超时重试机制
问题
有很多无意义的遍历操作开销,浪费CPU
时间轮算法
概念
在时钟轮机制中,有时间槽和时钟轮的概念,时间槽就相当于时钟的刻度;而时钟轮就相当于指针跳动的一个周期,我们可以将每个任务放到对应的时间槽位上。
优势
- 每个任务会按要求只扫描执行一次,能很好的解决CPU浪费的问题
- 秒级轮,分钟轮,小时轮
除了用于检测rpc调用是否超时,也可以将定时心跳的任务添加到时间轮中,当前时间的心跳执行完后再将下一秒的心跳任务添加到时间轮中,这样就能做到每秒的定时心跳
路由,负载均衡
用途
RPC Server为了高可用,可用选择做集群,因此在RPC Client端调用时要使用相应的均衡策略,这属于客户端负载均衡。
负载均衡策略
- 轮训
- 随机
- 权重
- 最少连接
- 自适应
熔断,限流
熔断
作用
熔断器如同电力过载保护器。它可以实现快速失败,如果它在一段时间内侦测到许多类似的错误,会强迫其以后的多个调用快速失败,不再访问远程服务器,从而防止应用程序不断地尝试执行可能会失败的操作,使得应用程序继续执行而不用等待修正错误,或者浪费CPU时间去等到长时间的超时产生。熔断器也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试恢复调用操作
限流
作用
实际生产环境中,每个服务节点都可能由于访问量过大而引起一系列问题,就需要业务提供方能够进行自我保护,从而保证在高访问量、高并发的场景下,系统依然能够稳定,高效运行。限流器的作用是用来限制其请求的速率,保护后台响应服务,以免服务过载导致服务不可用现象出现。
滑动窗口算法
限流组件