HTTP/2笔记之流和多路复用
零。前言
本部分将讲解HTTP/2协议中对流的定义和使用,其实就是在说HTTP/2是若何做到多路复用的。
一。流和多路复用的关系
1. 流的概念
流(Stream),服务器和客户端在HTTP/2连接内用于交换帧数据的独立双向序列,逻辑上可看做一个较为完整的交互处理单元,即表达一次完整的资源请求-响应数据交换流程;一个业务处理单元,在一个流内进行处理完毕,这个流生命周期完结。
特点如下:
- 一个HTTP/2连接可同时保持多个打开的流,任一端点交换帧
- 流可被客户端或服务器单独或共享创建和使用
- 流可被任一端关闭
- 在流内发送和接收数据都要按照顺序
- 流的标识符自然数表示,1~2^31-1区间,有创建流的终端分配
- 流与流之间逻辑上是并行、独立存在
2. 多路复用
流的概念提出是为了实现多路复用,在单个连接上实现同时进行多个业务单元数据的传输。逻辑图如下:
实际传输可能是这样的:
只看到帧(Frame),没有流(Stream)嘛。
需要抽象化一些,就好理解了:
- 每一个帧可看做是一个学生,流可以认为是组(流标识符为帧的属性值),一个班级(一个连接)内学生被分为若干个小组,每一个小组分配不同的具体任务。
- HTTP/1.* 一次请求-响应,建立一个连接,用完关闭;每一个小组任务都需要建立一个班级,多个小组任务多个班级,1:1比例
- HTTP/1.1 Pipeling解决方式为,若干个小组任务排队串行化单线程处理,后面小组任务等待前面小组任务完成才能获得执行机会,一旦有任务处理超时等,后续任务只能被阻塞,毫无办法,也就是人们常说的线头阻塞
- HTTP/2多个小组任务可同时并行(严格意义上是并发)在班级内执行。一旦某个小组任务耗时严重,但不会影响到其它小组任务正常执行
- 针对一个班级资源维护要比多个班级资源维护经济多了,这也是多路复用出现的原因
这样简单梳理,就有些小清晰了。
3. 流的组成
流的概念提出,就是为了实现多路复用。影响因素:
- 流的优先级(priority)属性建议终端(客户端+服务器端)需要按照优先级值进行资源合理分配,优先级高的需要首先处理,优先级低的可以稍微排排队,这样的机制可保证重要数据优先处理。
- 流的并发数(或者说同一时间存在的流的个数)初始环境下不少于100个
- 流量控制阀协调网络带宽资源利用,由接收端提出发送端遵守其规则
- 流具有完整的生命周期,从创建到最终关闭,经历不同阶段
流总体组成如下:
搞清楚了流和多路复用之间关系,下面稍微深入一点,学习流的一些细节。
二。流的属性
1. 流状态/生命周期
帧的行为以及END_STREAM标志位都会对流的状态的产生变化。因为流由各个端独立创建,没有协商,消极后果就是(两端无法匹配的流的状态)导致发送完毕RST_STREAM帧之后“关闭”状态受限,因为帧的传输和接收需要一点时间。
帧的状态列表:
- idle,所有流的开始状态值
- 发送/接收HEADERS帧,进入open状态
- PUSH_PROMISE帧只能在已有流上发送,导致创建的本地推送流处于"resereved(local)"状态
- 在已有流上接收PUSH_PORMISE帧,导致本地预留一个流处于"resereved(remote)"状态
- HEADERS/PUSH_PROMISE帧以及后面的零个或多个CONTINUATION帧,只要携带有END_STREAM标志位,流状态将进入"half closed"状态
- 只能接收HEADERS和PRIORITY,否则报PROTOCOL_ERROR类型连接错误
reserved,为推送保留一个流稍后使用
- reserved (local),服务器端发送完PUSH_PROMISE帧本地预留的一个用于推送流所处于的状态
- 只能发送HEADERS、RST_STREAM、PRIORITY帧
- 只能接收RST_STREAM、PRIORITY、WINDOW_UPDATE帧
reserved (remote),客户端接收到PUSH_PROMISE帧,本地预留的一个用于接收推送流所处于的状态
- 只能发送WINDOW_UPDATE、RST_STREAM、PRIORITY帧
- 只能接收RST_STREAM、PRIORITY、HEADERS帧
不满足条件,需要报PROTOCOL_ERROR类型连接错误
- reserved (local),服务器端发送完PUSH_PROMISE帧本地预留的一个用于推送流所处于的状态
- open,用于两端发送帧,需要发送数据的对等端需要遵守流量控制的通告。
- 每一端可以发送包含END_STREAM标志位的帧,导致流进入"half closed"状态
- 每一端都可以发送RST_STREAM帧,流进入"closed"状态
half closed
- half closed (local),发送包含有END_STREAM标志位帧的一端,流进入本地半关闭状态
- 不能发送WINDOW_UPDATE,PRIORITY和RST_STREAM帧
- 可以接收到任何类型帧
- 接收者可以忽略WINDOW_UPDATE帧,后续可能会马上接收到包含有END_STREAM标志位帧
- 接收到优先级PRIORITY帧,可用来变更依赖流的优先级顺序,有些小复杂了
- 一旦接收到包含END_STREAM标志位的帧,将进入"closed"状态
half closed (remote),接收到包含有END_STREAM标志位帧的一端,流进入远程半关闭状态
- 对流量控制窗口可不用维护
- 只能接收RST_STREAM、PRIORITY、WINDOW_UPDATE帧,否则报STREAM_CLOSED流错误
- 终端可以发送任何类型帧,但需要遵守对端的当前流的流量控制限制
- 一旦发送包含END_STREAM标志位的帧,将进入"closed"状态
一旦接收或发送RST_STREAM帧,流将进入"closed"状态。
- half closed (local),发送包含有END_STREAM标志位帧的一端,流进入本地半关闭状态
- closed,流的最终关闭状态
- 只允许发送PRIORITY帧,对依赖关闭的流进行重排序
- 终端接收RST_STREAM帧之后,只能接收PRIORITY帧,否则报STREAM_CLOSED流错误
- 接收的DATA/HEADERS帧包含有END_STREAM标志位,在一个很短的周期内可以接收WINDOW_UPDATE或RST_STREAM帧;超时后需要作为错误对待
- 终端必须忽略WINDOW_UPDATE或RST_STREAM帧
- 终端发送RST_STREAM帧之后,必须忽略任何接收到的帧
- 在RST_STREAM帧被发送之后收到的流量受限DATA帧,转向流量控制窗口连接处理。尽管这些帧可以被忽略,因为他们是在发送端接收到RST_STREAM之前发送的,但发送端会认为这些帧与流量控制窗口不符。
- 终端在发送RST_STREAM之后接收PUSH_PROMISE帧,尽管相关流已被重置,但推送帧也能使流变成“保留”状态。因此,可用RST_STREAM帧关闭一个不想要的承诺流
要求如下:
- 针对具体状态中出现没有允许出现的帧,需要作为协议错误(PROTOCOL_ERROR)类型的连接错误处理
- 在流的任何状态下,PRIORITY帧都可以被发送或接收
- 未知帧可以被忽略
2. 流标识符
- 31个字节表示无符号的整数,1~2^31-1
- 客户端创建的流以奇数表示,服务器端创建流以偶数表示
- 0x0用来表示连接控制信息流,不能够创建新流
- 通过http/1.1 101 协议切换升级切换到HTTP/2,0x1所指代流处于"half closed(local)",不能用于创建新流
- 新建流的标识符要大于已有流和预留的流的标识符
- 新建流第一次被使用时,低于此标识符的并且处于空闲"idle"状态的流都会被关闭
- 已使用的流标识符不能被再次使用
- 终端的流标识符若被耗尽的情况下
- 若是客户端,需要关闭连接,创建新的连接创建新流
- 若是服务器端,需要发送一个GOAWAY帧通知客户端,强迫其打开一个新连接
3. 流的并发数量
- 每一端都可以发送包含有SETTINGS_MAX_CONCURRENT_STREAMS参数的SETTINGS帧限制对等端流的最大并发量
- 对等端接收之后遵守终端最大并发量限制约定
- 状态为"open"或"half closed"的流需要计入限制总数
- 保留态"reserved"流不算入限制总数内
- 终端接收到HEADERS帧导致创建的流总数超过限制,需要响应PROTOCOL_ERROR或REFUSED_STREAM错误,具体哪一种错误,需要根据终端是否可以检测得到允许自动重复重试
- 终端想降低SETTINGS_MAX_CONCURRENT_STREAMS设置的活动流的上限,若低于当前已经打开流的数值,可以选择光比溢出的流或者允许流继续存在直到完成
4. 流的优先级
流的优先级在于允许终端向对端表达所期待的给予具体流更多资源支持的意见的表达,不能保证对端一定会遵守,非强制性需求建议;默认值16。在资源有限时,可以保证基本数据的传输。
优先级改变:
- 终端可在新建的流所传递HEADERS帧中包含优先级priority属性
- 可单独通过PRIORITY帧专门设置流的优先级属性
5. 流依赖
- 流与流之间存在依赖、被依赖关系。所有流默认依赖流0x0;推送流依赖于传输PUSH_PROMISE的关联流。
- 依赖权重值1~256区间,对于依赖同一父级的子节点,应该根据权重比列进行分配资源。
- 对于依赖同一个父级流的子节点被指定相关权重值,以及可用资源的分配比重。子节点之间顺序不固定。
A A
/ \ ==> /|\
B C B D C
- 一旦设置独家专属标志(exclusive flag)将为现有依赖插入一个水平的依赖关系,其父级流只能被插入的新流所依赖。比如流D设置专属标志并依赖于流A:
A
A |
/ \ ==> D
B C / \
B C
- 流的依赖树形模型,底层的流只能等到上层流被关闭或无法正常运转/失效时,才会被分配到资源
- 流无法依赖自身,否则为PROTOCOL_ERROR流错误
- 在流依赖树形模型中,父节点优先级,以及专属依赖流的加入等,都会导致已有优先级重排序
? ? ? ?
| / \ | |
A D A D D
/ \ / / \ / \ |
B C ==> F B C ==> F A OR A
/ \ | / \ /|\
D E E B C B C F
| | |
F E E
(intermediate) (non-exclusive) (exclusive)
6. 流优先级状态管理
- 流的依赖树形模型,任一节点被移除,都需要重建优先级顺序,重新分配资源
- 终端建议在流关闭一段时间内保留优先级信息,减少潜在的指派错误
- 处于"idle"状态流可被指派默认优先级16,这时可以变成其它流的父节点,可以指派新的优先级值
- 终端持有的流优先级信息不受SETTINGS_MAX_CONCURRENT_STREAMS限制,但可能会造成终端状态维护负担,其数量可以被限制不多于SETTINGS_MAX_CONCURRENT_STREAMS所定义数量
- 优先级状态信息的维持在负载较高时可以被丢弃,以减少资源占用。
- 终端若有能力保留足够状态,在接收到PRIORITY帧目的修改已被关闭流的优先级时,可以为其子节点重建优先级顺序
7. 流量控制
多路复用会引入资源竞争,流量控制可以保证流之间不会严重影响到彼此。流量控制通过使用WINDOW_UPDATE帧实现,可作用于单个流以及整个的连接。一些原则如下:
- 逐跳,具有方向性
- 不能够被禁止
- 初始窗口值为65535字节,针对单个流,以及整个连接都有效
- 基于WINDOW_UPDATE帧传输实现,接收端通告对端准备在流/连接上接收的字节数
- 接收端完全控制权限,接受端可通告针对流/连接的窗口值,发送者需要遵守
- 目前只有DATA帧可被流量控制,仅针对其有效负载计算;超出窗口值,其负载可以为空
需要注意事项:
- 流量控制是为解决线头阻塞问题,同时在资源约束情况下保护一些操作顺利进行,针对单个连接,某个流可能被阻塞或处理缓慢,但同时不会影响到其它流上正在传输的数据
- 虽然流量控制可以用来限制一个对等端消耗的内存,但若在不知道网络带宽延迟乘积的情况下可能未必能够充分利用好网络资源
- 流量控制机制很复杂,需要考虑大量的细节,实现很困难
三。小结
HTTP/2规范中所定义的流概念、属性很复杂,在请求量很大以及应对海量并发的情况下,整个连接的流量控制+单个流的流量控制+流的状态+流优先级属性+优先级的状态+流依赖树形模型等一系列新特性,可能会造成:
- 服务器端/客户端单个连接内存占用过高,维护一个长连接的成本比以往多了若干倍
- 流量控制是一个复杂功能,实现不好会导致一端流量窗口值已被耗尽,需要等待客户端发送新的流控窗口值,若有热数据进行发送,需要等待成本,无形中增加了额外的交互步骤
- 流依赖和优先级重排序等,无形中增加了程序的复杂度,处理不好触发潜在BUG
- 为了性能和内存考虑,很多知名应用不见得有动力实现全部特性,流的一些高级特性毕竟有些过于理想化,诸如当前实现列表:https://github.com/http2/http2-spec/wiki/Implementations,可以看出一二
- 实际非浏览器环境,诸如HTTP API等,实际上仅需要部分关键特性,这属于情理之中的选择
- 凡是状态皆需要维护,无论横向还是纵向的扩展都需要倍加注意;无状态才是最有利于扩展
HTTP/2笔记之流和多路复用的更多相关文章
- java 学习笔记之 流、文件的操作
ava 学习笔记之 流.文件的操作 对于一些基础的知识,这里不再过多的解释, 简单的文件查询过滤操作 package com.wfu.ch08; import java.io.File; import ...
- angular2 学习笔记 ( rxjs 流 )
RxJS 博大精深,看了好几篇文章都没有明白. 范围牵扯到了函数响应式开发去了... 我对函数式一知半解, 响应式更是第一次听到... 唉...不过日子还是得过...混着过先呗 我目前所理解的很浅, ...
- Java 学习笔记 IO流与File操作
可能你只想简单的使用,暂时不想了解太多的知识,那么请看这里,了解一下如何读文件,写文件 读文件示例代码 File file = new File("D:\\test\\t.txt" ...
- java学习笔记--IO流
第十二章大纲: I/O input/output 输入/输出 一.创建文件,借助File类来实现 file.createNewFile() : 创建文件 file.exists() : 判断文件是否存 ...
- 学习笔记 --- 最大流Dinic算法
为与机房各位神犇同步,学习下网络流,百度一下发现竟然那么多做法,最后在两种算法中抉择,分别是Dinic和ISAP算法,问过 CA爷后得知其实效率上无异,所以决定跟随Charge的步伐学习Dinic,所 ...
- javaio学习笔记-字符流类(1)
1.java.io包中的字符流类-BufferedReader和BufferedWriter: BufferedReader:缓存的输入字符流; BufferedWriter:缓存的输出字符流; In ...
- javaio学习笔记-字符流类(2)
1.java.io包中的字符流类-FileReader和FileWriter: BufferedReader:缓存的输入字符流; BufferedWriter:缓存的输出字符流; FileReader ...
- java学习笔记——IO流部分
IO流常用的有:字符流.字节流.缓冲流.序列化.RandomAccessFile类等,以上列出的都是开发中比较常用的. 1.字节流: 字节流包含:FileInputStream/FileOutputS ...
- 《css世界》笔记之流、元素与基本尺寸
1. 块级元素 基本特性:就是一个水平流上只能单独显示一个元素,多个块级元素则换行显示. 块级元素和"display 为block 的元素"不是一个概念,display:list- ...
随机推荐
- C语言 · 报时助手
基础练习 报时助手 时间限制:1.0s 内存限制:512.0MB 锦囊1 判断,字符串输出. 锦囊2 按要求输出,判断特殊情况. 问题描述 给定当前的时间,请用英文的读法 ...
- swd 适配器接口线序
1 vref 2 gnd 3 swdio FP1 4 swclk PF0 5 nrst 6 swo PF2
- cglib 动态代理基础篇
cglib 动态代理基础篇 CGlib是什么? CGlib是一个强大的,高性能,高质量的Code生成类库.它可以在运行期扩展Java类与实现Java接口. 下面我们将通过一个具体的事例来看一下CGli ...
- [Django学习]上传图片
上传图片 当Django在处理文件上传的时候,文件数据被保存在request.FILES FILES中的每个键为<input type="file" name="& ...
- LINQ教程二:LINQ操作语法
LINQ查询时有两种语法可供选择:查询表达式语法(Query Expression)和方法语法(Fluent Syntax). 一.查询表达式语法 查询表达式语法是一种更接近SQL语法的查询方式. L ...
- Linux进程同步机制
为了能够有效的控制多个进程之间的沟通过程,保证沟通过程的有序和和谐,OS必须提供一定的同步机制保证进程之间不会自说自话而是有效的协同工作.比如在共享内存的通信方式中,两个或者多个进程都要对共享的内存进 ...
- 12款优秀 jQuery Ajax 分页插件和教程
12款优秀 jQuery Ajax 分页插件和教程 在这篇文章中,我为大家收集了12个基于 jQuery 框架的 Ajax 分页插件,这些插件都提供了详细的使用教程和演示.Ajax 技术的出现使得 W ...
- thinkphp 点击分类显示分类下的文章(完整)
控制器 <?php // 本类由系统自动生成,仅供测试用途 class IndexAction extends Action { public function index(){ $cate=M ...
- 【转】 如何利用C#代码来进行操作AD
要用代码访问 Active Directory域服务,需引用System.DirectoryServices命名空间,该命名空间包含两个组件类,DirectoryEntry和 DirectorySea ...
- mysql命令行远程登录命令
mysql -u root -psalon365365 -h 192.168.1.103 -P 3 306 -D empirecms