学习网址:https://juejin.cn/course/bytetech/7142811324462923783/section/7142809631831228429
一、RPC框架分层实现
1、基本概念
远程函数调用(RPC-remote Procedure Calls),例如网上商城服务和支付服务部署在不同的机器上,中间隔着网络。
RPC需要解决的问题:
1、函数映射 -- 网上商城如何告诉支付服务要调用付款这个服务
2、数据转换成字节流 -- 网上商城如何把参数告诉远程
3、网络传输 -- 怎么在网络上高效稳定的传输数据
RPC概念模型:
RPC的过程由5个模型组成,User、User-Stub、Server、Server-Stub、RPC-Runtime
RPC调用过程:
Caller machine指的是调用端,Callee machine指的是被调用端,首先User模块发起一个本地调用,调用User-Stub,将参数打包,打包后交给RPC Runtime,RPC Runtime把数据传送给对端,接收后交给Server-Stub解压数据,之后调用真正的业务逻辑server,server处理完逻辑之后,返回,返回的时候将返回结果打包,再通过网络传输到对端的RPC-Runtime,User-Stub将数据解压出来,再返回。
一次RPC的完整过程:
相关概念:
(1)IDL文件
相对于本地函数调用,我们不知道远程函数有什么方法,也不知道参数是什么样的,所以需要一种方式描述和声明有哪些方法,有哪些参数,按照约定来调用,约定的描述文件叫做IDL文件。
IDL通过一种中立的方式来描述接口,使得在不同平台上的对象和不同语言编写的程序可以相互通信。
(2)生成代码
通过约定的规范进行调用的,双方依赖于相同的IDL文件,需要相应的工具生成代码,在具体调用的时候用户代码直接依赖生成代码,可以把用户代码和生成代码看成一个整体。
(3)编解码
从内存中表示到字节序列的转换称为编码,反之为解码,解决了跨语言的数据交互格式。
(4)通信协议
规范数据在网络中的传输内容和格式,除了携带必须的请求或响应的数据外,通常还会包含额外的元数据。
(5) 网络传输
基于成熟的网络库走TCP/UDP传输。
RPC的好处:
1、单一职责,有利于分工协作和运维开发,部署,运维独立,不同的服务采用不同的语言开发。
2、可扩展性更强,资源使用率更优,压力大时,可独立的扩充资源,针对性扩容,底层资源可以复用。
3、故障隔离,服务的整体可靠性更高
RPC的弊端(通过RPC框架来处理):
1、服务宕机,对方应如何处理?
2、在调用的过程中可能发生网络异常,如何保证消息的可达性?
3、请求量突增导致服务无法及时处理,有什么应对措施?
2、分层设计
编解码层、协议层、网络通信层
分层设计:
编解码层 ---- 生成代码
数据格式:
(1)语言特定的格式,许多编程语言内建了将内存对象编码为字节序列的支持,与特定的编程语言绑定,其他语言无法读取数据,兼容性低。
(2)文本格式
具有人类可读性,JSON,XML,CSV等文本格式,描述可能不严谨,会存在缺陷,JSON可以区分字符串和数组,但无法区分整数和浮点数,也不能指定精度。JSON编解码的实现采用反射的机制,性能差。
(3)二进制编码
具有跨语言和高性能等特点,常见的右Thrift的BinaryProtocol,protobuf等,TLV编码 。
编解码层 - 选型
兼容性:支持增加新的字段,而不影响老的服务,提高系统的灵活性。
通用性:跨平台、跨语言
性能:从时间和空间两个维度来考虑,编码后数据的大小和编码耗费时长。
协议层
编解码层只是将数据转换成字节流,但并不是将字节流直接打包发送给对方,还需要特定的通信协议,添加一些元数据。
特殊结束符:需要有一个标志位,常见的是HTTP协议。
LENGTH:数据包大小,不包含自身。
HEADER MAGIC:解析协议的版本信息,知道这是什么类型的协议。
SEQUENCE NUMBER:数据包的seqID,在单连接内递增,适合于多路复用(在一个连接内可以有多个请求流,肯定要表示seqID)。
HEADER SIZE:从第十四个字节到payload前整个都是头部。
对协议进行解析:
首先从内存中读取指定的部分,先读取magic number所在的位置,知道是什么类型的协议,再读取编解码的方式,再把解出来 。
网络通信层
网络之间的通信通过socket API,操作系统提供的sockets API位于应用层和传输层之间。首先在sockets编程的时候必须要知道两个信息,IP和端口,sockets创建一个套接字,首先会有一个bind操作,将套接字绑定到IP+端口上,再去监听进来的连接,放到一个队列里面,队列可能由于满了发生阻塞,需要等到队列清空再放进去,队列有一个长度规定backlog,linux默认的长度规定是128,之后是accept,在客户端发起connect请求的时候,接收连接的请求,没有请求的时候就会一直阻塞等待连接进来,调用read,write读写进行通信。
使用封装好的网络库作为RPC框架的网络通信层需要满足:
1、提供易用API,封装底层Sockets API,对连接进行管理,对可读,可写事件进行分发。
2、功能,协议支持UDP,TCP,UDS等,优雅退出,对底层的异常进行处理。
3、性能,通过应用层的buffer减少copy,高性能的定时器,对象池
3、关键指标
稳定性、易用性、扩展性、观测性、高性能
稳定性 -- 保障策略
保障策略主要有三种:
熔断:服务A调用服务B,服务B调用服务C,服务C响应超时,导致B一直等待,返回给A就是超时的,A就会频繁的调用服务B,因为频繁堆积的请求导致服务B宕机,就会产生服务雪崩 。
保护调用方,防止被调用的服务出现问题影响到整个链路。
限流:保护被调用方,防止大流量把服务压垮。
超时控制:避免资源浪费在不可用的节点上。被调用端可能由于某种原因响应过慢,调用端会停止一些不必要的请求,快速的返回,及时释放资源,避免资源浪费在不可调用的服务上。
稳定性 -- 请求成功率
负载均衡(服务A均匀调用服务B的节点)、重试(多试几次可能就好了)
稳定性 -- 长尾请求
明显高于平均响应时间的占比比较小的那一部分请求
提高长尾请求的请求成功率,通过backup request
稳定性 -- 注册中间件
无论是client还是server,创建的时候都可以通过options的方式将这些可选的方式加上,灵活的注入稳定性策略,保证请求的稳定。
易用性
开箱即用,提供合理的默认配置,提供丰富的文档(遇到问题解决)
周边工具:生成代码工具(辅助用户更好的使用框,IDL生成代码工具)、脚手架工具(生成一些重复的代码)
RPC框架会有一个suite提供熔断、限流、降级等基本的功能。
扩展性
编解码层:支持不同的编解码
协议层:支持尽可能多的RPC通信协议
网络传输层:支持不同的网络库
中间件的执行流程:request会分别经过middleware1,2,n后才会真正的将请求发送到远端,负载均衡,超时控制都是通过中间件的方式加一些策略的。
观测性
RPC在运行过程中相对于用于来说是一个空盒,不知道服务现在是什么状况
传统的三件套:Log(日志)、Mrtric(监控)、Tracing(跟踪,知道在那个阶段超时,往往会携带ID,通过ID将整个链路串起来。)
RPC框架有内置观测性服务,框架内部的配置是什么,运行过程中环境变量是什么,运行过程中的线程有多少,协程有多少,使用了哪些中间件,都需要框架主动的暴露出来。
高性能
目标:高吞吐(在单位时间内尽可能处理多的请求)、低延迟(发出请求返回时间尽可能的短)
场景:单机多机、单连接多连接、不同大小的请求包、不同请求类型(不是简单的发送一个请求返回一个响应,一个请求多个响应)、单/多 client ,单/多 server
手段:连接池、多路复用(提高连接的复用率,减少重复创建消费连接的开销)、高性能编解码协议、高性能网络库