已发表的技术专栏
0 grpc-go、protobuf、multus-cni 技术专栏 总入口
4 grpc、oauth2、openssl、双向认证、单向认证等专栏文章目录)
从本小节开始,我们开始分享在gRPC-go框架源码中最核心,也是最难的滑动窗口原理(也可以称为流量控制原理);
我们最基本的目的之一,就是,搞清楚grpc服务器端或者grpc客户端是如何控制发送数据量大小,接收数据量大小的;
知道原理后,是不是可以将这种思想应用到自己的项目中去。
1、滑动窗口基本介绍 |
滑动窗口基本介绍:
- 滑动窗口并不是一个函数,或者一个方法,是客户端跟服务器端运行时交互的效果。也可以称为流控等等,类似的词语。
- 滑动窗口针对的是数据帧传输的场景
- 滑动窗口的目的是,计算出下次发送数据帧的最大字节数
- 滑动窗口过程描述:
- a)客户端的帧发送器在发送数据帧时存在一些发送指标,服务器端在接收数据帧的不同阶段,也存在一些指标;
- b)服务器在接收数据帧时,会根据自己的接收数据帧、存储数据帧、读取数据帧的速度,向客户端发送窗口更新帧或者设置帧;
- c)客户端接收到服务器端发送过来的设置帧或者窗口更新帧后,会更新本地的发送指标
- d)发送指标的更新,会影响到客户端下次发送数据帧的最大字节数
- e)最大字节数的计算,服务器端会通过发送窗口更新帧或者设置帧来动态的影响客户端计算数据帧大小的参数,从而影响下次发送的最大字节数。
- 简单描述:服务器端将自己的接收情况,通知客户端,客户端根据服务器端的接收的情况,来决定下次发送数据帧的最大字节数。
- 服务器端一侧:
- a)在数据帧存储到缓存前,大概需要经历3种流控;
- b)在从缓存里读取数据前,需要做一次窗口更新
- c)每读取完一次数据,会执行一次流控;(从缓存里读取数据时,很有可能会读取多次,才能完整的获取到数据帧里的所有数据)
备注:
这里描述的滑动窗口是,客户端向服务器端发送数据帧的情况,当然,服务器端向客户端发送数据帧时,滑动窗口原理是一样的。
2、滑动窗口整体流程图 |
2.1、滑动窗口流程图 |
可以从下面的图中,了解滑动窗口的流程:
(该图在某些细节上并不是非常的准确,不必担心;主要是希望通过该图能够描述清楚滑动窗口的整体处理流程,某些细节可以忽略)
主要流程说明:
1 )客户端一侧 |
- 构建好数据帧dataFrame
- 通过发送参数指标,计算本次发送的最大字节数maxSize;
- 数据帧截取器,从数据帧里截取指定的字节数maxSize,交由帧发送器
- 帧发送器将截取的字节,转换成http2原生的帧,发送给服务器端
2 )服务器端一侧 |
- 帧接收器接收到数据帧,交由帧分发器处理
- 帧分发器根据帧的类型,交由数据处理器handleData处理
- 数据处理器handleData:
- a)抽样级别流控:对本地的接收参数进行更新(如b.sampelCount,b.sample,b.bwMax,b.bdp),触发阈值条件后,会向客户端发送窗口更新帧outgoingWindowUpdate,其中设置streamID=0,或者发送设置帧outgoingSettings
- b)链接级别流控:对本地的接收参数进行更新(如f.unacked,f.limit),触发阈值条件后,会向客户端发送窗口更新帧outgoingWindowUpdate,其中设置streamID=0
- c)流级别流控:对本地的接收参数进行更新(如f.pendingData,f.pendingUpdate,f.limit,f.delta),触发阈值条件后,会向客户端发送窗口更新帧outgoingWindowUpdate,其中设置streamID=0
- d)将数据帧存储到go语言原生自带的缓存bytes.Buffer里
- e)构建recvMsg结构体,将bytes.Buffer存储到recvMsg里;(recvMsg就是对bytes.Buffer封装)
- f)将recvMsg存储到recvBuffer里:存储逻辑是
- i.若recvMsg类型的通道里,没有数据的话,就直接将recvMsg存储到该通道里
- ii.若recvMsg类型的通道里,已经有数据了的话,就将recvMsg添加到类型为recvMsg的切片的尾部。
- 接收数据并解压recvAndDecompress:
- a)读取数据前,先对本地的窗口参数进行调整,如f.delta;满足触发阈值条件的话,就向客户端发送窗口更新帧outgoingWindowUpdate,其中streamID非0;
- b)recvBufer读取器:
- i.从recvBuffer的通道里获取数据recvMsg
- ii.数据获取到后,recvBuffer缓存中,将切片的第一个数据,加载到通道里;(因为通道里刚才已经消费了数据,需要重新添加上)
- iii.从recvMsg里获取到bytes.Buffer对象
- iv.将bytes.Buffer里的数据读取到字节切片里
- c)读取完成数据后,更新流级别参数,如f.pendingData,f.pendingUpdate若满足触发阈值条件的话,就向客户端发送窗口更新帧outgoingWindowUpdate,其中streamID非0;
- d)对切片里的数据进行解压,对解压后的数据,交由handle方法
- e)handle方法:就是grpc服务器内部,真正执行客户端请求的方法入口
- i.对解压后的数据,进行反序列化,得到请求方法的具体参数值
- ii.真正的执行客户端的请求方法,得到执行结果
- f)将执行结果封装到数据帧里,存储到controlBuf缓存里
- g)帧发送器,从帧缓存里获取到数据帧,发送给客户端
3 )客户端一侧 |
- 客户端接收到窗口更新帧后,
- a)若streamID=0,则更新l.sendQuota值,对其进行累加n;
- b)若streamID!=0,则更新str.bytesOutStanding,对其进行递减n
- 客户端接收到设置帧:
- a)则更新l.oiws值
- 若接收到数据帧,得到了请求服务的最终的执行结果。
如果客户端的数据帧,需要多次发送的话,是重复交互的过程;
服务器端每接收到一次数据帧,就会执行上面的一次流程,向客户端发送窗口更新帧,设置帧;
客户端收到后,会更新本地的的发送指标参数,重新计算下次发送的最大字节数。
2.2、帧发送器的发送参数指标 |
大概有5个:
- l.sendQuota、
- str.bytesOutStanding、
- l.oiws、
- http2MaxFrameLen、
- dataFrame字节数;
但是,服务器端发送的窗口更新帧或者设置帧,其实主要是影响客户端中帧发送器的三个参数:
- l.sendQuota、
- str.bytesOutStanding、
- l.oiws;
其中:
- http2MaxFrameLen,为固定值16384;
- dataFrame字节数,不同的请求,数据帧的字节数可能不一样。
3、总结 |
- 对滑动窗口进行了简单概述,以及滑动窗口是用来实现什么目的的;
- 通过滑动窗口流程图描述了滑动窗口的整体处理流程;
- 最核心的是通过发送参数指标来计算出每次发送的数据帧大小;而这些发送参数指标会受到服务器端的发送的窗口更新帧或者设置帧影响。
有了这些基本认识后,接下来,学习、理解滑动窗口的核心原理以及源码时就不会没有头绪了。
下一篇文章
帧发送器是如何将数据帧发送给服务器端的基本流程图介绍
点击下面的图片,返回到专栏大纲 |