理解HTTP/2流量控制

在理解HTTP/2协议流量控制部分的过程中,容易被一些问题困惑。HTTP/2构建于tcp之上,tcp协议已经提供了流量控制机制,HTTP/2的流量控制有何不同?HTTP/2流量控制是什么原理,怎样才能发挥它的最大价值? 本文将从协议、源码来寻找问题的答案。

协议中怎么说?

协议中对流量控制的目的和特点有清晰的描述

  1. Flow control (Section 5.2)helps to ensure that only data that can be used by a receiver is transmitted.

  2. 流量控制有助于确保只传播接受者需要使用的数据数据。

  3. Using streams for multiplexing introduces contention over use of the TCP connection, resulting in blocked streams. A flow control scheme ensures that streams on the same connection do not destructively interfere with each other. Flow control is used for both individual streams and for the connection as a whole.

  4. HTTP/2 provides for flow control through use of the WINDOW_UPDATE frame (Section 6.9).

  5. HTTP/2利用流来实现多路复用,这引入了对TCP连接的使用争夺,会造成流被阻塞。流量控制方案确保在同一连接上的多个流之间不会造成破坏性的干扰。流量控制会用于各个独立的流,也会用于整个连接。

  6. HTTP/2通过使用WINDOW_UPDATE帧来进行流量控制。

HTTP/2“流”的流量控制的目标是:在不改变协议的情况下允许使用多种流量控制算法。

HTTP/2的流量控制具有以下特征:

  1. 流量控制是特定于一个连接的。每种类型的流量控制都是在单独的一跳的两个端点之间的,并不是在整个端到端的路径上的。(这里的一跳指的是HTTP连接的一跳,而不是IP路由的一跳)
  2. 流量控制是基于WINDOW_UPDATE帧的。接收方公布自己打算在每个流以及整个连接上分别接收多少字节。这是一个以信用为基础的方案。
  3. 流量控制是有方向的,由接收者全面控制。接收方可以为每个流和整个连接设置任意的窗口大小。发送方必须尊重接收方设置的流量控制限制。客户方、服务端和中间代理作为接收方时都独立地公布各自的流量控制窗口,作为发送方时都遵守对端的流量控制设置。
  4. 无论是新流还是整个连接,流量控制窗口的初始值是65535字节。
  5. 帧的类型决定了流量控制是否适用于帧。目前,只有DATA帧服从流量控制,所有其它类型的帧并不消耗流量控制窗口的空间。这保证了重要的控制帧不会被流量控制阻塞。
  6. 流量控制不能被禁用。
  7. HTTP/2只定义了WINDOW_UPDATE帧的格式和语义,并没有规定接收方如何决定何时发送帧、发送什么样的值,也没有规定发送方如何选择发送包。具体实现可以选择任何满足需求的算法。

重点看第7条,HTTP/2没有提出流量控制的具体算法,实现者可以有最大的自由度,去实现任何算法。

怎么实现的?

基本概念

流量控制窗口 (Flow Control Window)

HTTP/2中流量控制是通过每个流上每个发送端,保持一个窗口来实现的。流量控制窗口是一个简单的整数值,指示发送端被允许传输的字节数;因此,它的大小是接收端的缓存能力的衡量。

流量控制窗口对流和连接的流量控制窗口都适用。发送端绝对不能发送超出接收端广播的流量控制窗口大小的可用空间长度的受流量控制影响的帧。在各个流量控制窗口中没有可用空间时,可以发送带有END_STREAM标记的长度为0的帧(例如,空数据帧)。

WINDOW_UPDATE帧

HTTP/2定义的帧类型的一种,用途是通知对端增加窗口值,WINDOW_UPDATE会指定增加的大小

算法描述:

  1. 发送端保有一个流量控制窗口(window)初始值。初始值的设定请参考SETTING 帧的 SETTINGS_INITIAL_WINDOW_SIZE
  2. 发送端每发送一个DATA帧,就把window递减,递减量为这个帧的大小。如果当前window小于帧大小,那么这个帧就必须被拆分到不大于window,如果window等于0,就不能发送任何帧
  3. 接收端可以发送 WINDOW_UPDATE帧给发送端,发送端以帧内指定的Window Size Increment作为增量,加到window上

我们来看看nginx 1.10.3中相关的源码

看看在各种情况下流量控制窗口值的变化。

nginx定义了一个recv_window用来控制发送window_update帧的时机

基本数据结构

  1. struct ngx_http_v2_connection_s { //HTTP/2连接数据结构

  2. ngx_connection_t *connection;

  3. ngx_http_connection_t *http_connection;

  4. ngx_uint_t processing;

  5. size_t send_window; //发送窗口

  6. size_t recv_window; //接收窗口

  7. size_t init_window; //初始化窗口大小 用于初始化stream recv_window以及发送给客户端SETTING帧中的<strong>SETTINGS_INITIAL_WINDOW_SIZE</strong>

  8. size_t frame_size;

  9. ngx_queue_t waiting;

  10. ngx_http_v2_state_t state;

  11. ngx_http_v2_hpack_t hpack;

  12. ngx_pool_t *pool;

  13. ngx_http_v2_out_frame_t *free_frames;

  14. ngx_connection_t *free_fake_connections;

  15. ngx_http_v2_node_t **streams_index;

  16. ngx_http_v2_out_frame_t *last_out;

  17. ngx_queue_t dependencies;

  18. ngx_queue_t closed;

  19. ngx_uint_t last_sid;

  20. unsigned closed_nodes:8;

  21. unsigned settings_ack:1;

  22. unsigned blocked:1;

  23. unsigned goaway:1;

  24. };

nginx相关配置

指令:http2_body_preread_size

默认:http2_body_preread_size 65535

上下文:http server

用来定义 Nginx 在客户端收到 SETTINGS 帧之前可以接受多大的 DATA 帧,默认为 64KB。

这个值设置为0,可能会导致一个POST bug。详情见: https://imququ.com/post/nginx-http2-post-bug.html

请求处理部分

建立连接(connection)初始阶段

NGINX 会设置整个连接的接收窗口和发送窗口的初始大小

  1. h2c->send_window = NGX_HTTP_V2_DEFAULT_WINDOW; // 默认65535。 这个就是协议中说到的流量控制窗口

  2. h2c->recv_window = NGX_HTTP_V2_MAX_WINDOW; // 值为2^31-1 接收窗口是nginx衡量自身接收能力定义出来的,并没有在协议中出现。

  3. h2c->init_window = NGX_HTTP_V2_DEFAULT_WINDOW; // 65535

发送SETTING帧

在h2连接建立的初始阶段,nginx会发送一个SETTING帧,

SETTINGS_INITIAL_WINDOW_SIZE

值为h2scf->preread_size 默认值是65535 不能大于2%31-1

表明发送方的流级别的流量控制的初始窗口大小(以字节为单位)。初始值是2^16-1(65535)字节。这个设置影响所有流的窗口大小。超过65535的窗口大小必选被视为类型是FLOW_CONTROL_ERROR的连接错误。

发送WINDOW_UPDATE帧

Window Size Increment的大小为

建立流(stream)初始阶段

建立连接之后,收到HEADERS帧时,就会创建一个流(stream)

接收到HEADER帧

  1. stream->send_window = h2c->init_window; // 默认65535
  2. stream->recv_window = h2scf->preread_size; // 默认65535 可配置

流的发送窗口和接收窗口在初始化的时候,设置的值默认比较小,只有65535.

一般情况下,流建立后,客户端会给服务端发送一个SETTING帧,重新设置发送窗口大小

收发数据阶段

接收到DATA帧

  1. h2c->recv_window -= h2c->state.length

  2. if (h2c->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) { //连接的接收窗口大小降到窗口最大值1/4时, 即发送window_update帧

  3. if (ngx_http_v2_send_window_update(h2c, 0, NGX_HTTP_V2_MAX_WINDOW

    • h2c->recv_window)
  4. == NGX_ERROR)

  5. {

  6. return ngx_http_v2_connection_error(h2c,

  7. NGX_HTTP_V2_INTERNAL_ERROR);

  8. }

  9. h2c->recv_window = NGX_HTTP_V2_MAX_WINDOW; //发送完毕后,连接的接收窗口恢复到最大值 2^31-1

  10. }

  11. stream->recv_window -= h2c->state.length;

  12. if (stream->no_flow_control // 默认是0

  13. && stream->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) <span style="font-family:Arial, Helvetica, sans-serif;">//当前流的接收窗口大小降到窗口最大值1/4时, 即发送window_update帧</span>

  14. {

  15. if (ngx_http_v2_send_window_update(h2c, node->id,

  16. NGX_HTTP_V2_MAX_WINDOW

    • stream->recv_window)
  17. == NGX_ERROR)

  18. {

  19. return ngx_http_v2_connection_error(h2c,

  20. NGX_HTTP_V2_INTERNAL_ERROR);

  21. }

  22. stream->recv_window = NGX_HTTP_V2_MAX_WINDOW; <span style="font-family:Arial, Helvetica, sans-serif;">//发送完毕后,当前流的接收窗口恢复到最大值 2^31-1</span>

  23. }

HTTP/2没有规定接收方如何决定何时发送WINDOW_UPDATE帧、发送什么样的值,具体算法依赖服务器具体实现。

nginx选择在接收窗口小于窗口最大值1/4时发送WINDOW_UPDATE帧,并且将窗口大小增长到最大值2^31-1

并不是所有服务器都这样实现的。

比如有的实现是这样的,收到一个DATA帧,马上返回一个WINDOW_UPDATE帧,增长的值就是DATA帧的大小

发送DATA帧

  1. <span style="white-space:pre;"> </span>h2c->send_window -= frame_size;

  2. stream->send_window -= frame_size;

管理帧的处理

有一些与请求数据无关的帧,对连接和流的行为和状态能够进行控制,暂且叫它管理帧。

比如SETTING WINDOW_UPDATE帧,就能够对流量窗口进行修改

接收SETTING帧

  1. stream->send_window += value-h2c->init_window //所有的stream都会被设置, 但是不超过NGX_HTTP_V2_MAX_WINDOW

  2. h2c->init_window = value; //

接收WINDOW_UPDATE帧

  1. if (h2c->state.sid) { // 判断是不是设置了sid
  2. node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0);
  3. ...
  4. stream = node->stream;
  5. ...
  6. stream->send_window += window;
  7. }
  8. h2c->send_window += window;

正确使用流控

看完HTTP/2 的send_window 在各种情形下的变化情况,可以发现从理念到实现上都非常简单,对于普通用户来说,可以将这个当成透明的。

流量控制的定义是用来保护端点在资源约束条件下的操作。例如,一个代理需要在很多连接之间共享内存,也有可能有缓慢的上游连接和快速的下游连接。流量控制解决的情况是接收端在一个流上处理数据的同时同样想继续处理同个连接上的其他流。

调度过程中不需要这种能力时可以广播一个最大值的流量控制窗口,增加接收新数据时的可用空间。发送数据时总是受接收端广播的流量控制窗口的管理(见[RFC1323])。

资源约束下(例如内存)的调度可以使用流量来限制一个对等端可以消耗的内存数量。需要注意的是如果在不知道带宽延迟乘积的时候启用流量控制可能导致无法最优的利用可用的网络资源(see RFC1323)。

即便是对当前的网络延迟乘积有充分的认识,流量控制的实现也可能很复杂。当使用流量控制时,接收端必须及时地从TCP接收缓冲区读取数据。这样做可能导致在一些例如WINDOW_UPDATE的关键帧在HTTP/2不可用时导致死锁。但是流量控制可以保证约束资源能在不需要减少连接利用的情况下得到保护。

参考

http://blog.csdn.net/jianfyun/article/details/48549939

https://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-5.3

https://imququ.com/series.html

https://segmentfault.com/a/1190000002675667

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容