NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

HTTP/2 in GO(一)

  • 2019-11-18
  • 本文字数:6359 字

    阅读完需:约 21 分钟

HTTP/2 in GO(一)

大家在平时的项目开发中,应用越来越多的使用到了 GO 语言。今天就给大家带来了一份关于 GO 结合 HTTP/2 的开发示例分享给大家。本文来自公众号“360 搜索技术团队”的投稿,作者付坤。


最近由于做一些相关项目,需要使用到 HTTP/2 的一些特性,花了两天的时间看了下 HTTP/2 的 RFC-7540 的文档,又花了一天时间看了下 go 语言中 http server 中对 HTTP/2 的实现,做一些笔记,记录一些心得。内容比较多,会分多篇写,具体是几篇,看情况定吧。

HTTP/2 RFC7540

先来看一下什么是 HTTP/2,为什么不是 HTTP/1.2?HTTP/2 没有改动 HTTP 的应用语义。HTTP 方法、状态代码、URI 等概念都跟 HTTP/1.1 一样,但是 HTTP/2 在数据传输过程中做了二进制分帧(frame)处理,这点跟之前不一样,通过分帧,HTTP/2 对我们的应用隐藏了其复杂性,达到了既能支持一些新特性,又能兼容之前的所有应用。所以,如果我们是跟之前一样,做一些普通的 web 应用,对 HTTP/2 的使用跟 HTTP/1 没有任何区别。但如果我们希望能利用到 HTTP/2 的一些新特性,就需要对它有一些更深入的了解。

HTTP/2 新增特性

  • 二进制分帧(HTTP Frames)

  • 多路复用

  • 头部压缩

  • 服务端推送(server push)

1 二进制分帧(HTTP Frames)

HTTP/2 最革命性的原因就在于这个二进制分帧了,要了解二进制分帧在客户端和服务端传输的过程,需要了解三个概念:


  • Frame,帧,HTTP/2 协议里通信的最小单位,每个帧有自己的格式,不同类型的帧负责传输不同的消息

  • Message, 消息,类似 Request/Response 消息,每个消息包含一个或多个帧

  • Stream,流,建立链接后的一个双向字节流,用来传输消息,每次传输的是一个或多个帧


HTTP/2 里边,这些概念的关系是这样的:


  • 所有的通信都在一个 tcp 链接上完成,会建立一个或多个 stream 来传递数据

  • 每个 stream 都有唯一的 id 标识和一些优先级信息,客户端发起的 stream 的 id 为单数,服务端发起的 stream id 为偶数

  • 每个 message 就是一次 Request 或 Response 消息,包含一个或多个帧,比如只返回 header 帧,相当于 HTTP 里 HEAD method 请求的返回;或者同时返回 header 和 Data 帧,就是正常的 Response 响应。

  • Frame 是最小的通信单位,承载着特定类型的数据,例如 Headers, Data, Ping, Setting 等等。 来自不同 stream 的 frame 可以交错发送,然后再根据每个 Frame 的 header 中的数据流标识符重新组装。



简言之,HTTP/2 将 HTTP 协议通信分解为二进制编码 Frame 的交换,这些 Frame 对应着特定 Stream 中的 Message。所有这些都在一个 TCP 连接内复用。这是 HTTP/2 协议所有其他功能和性能优化的基础。


下面来看下 Frame 的基础结构


+-----------------------------------------------+|                 Length (24)                   |+---------------+---------------+---------------+|   Type (8)    |   Flags (8)   |+-+-------------+---------------+-------------------------------+|R|                 Stream Identifier (31)                      |+=+=============================================================+|                   Frame Payload (0...)                      ...+---------------------------------------------------------------+
复制代码


  • Length: 表示 Frame Payload 的大小,是一个 24-bit 的整型,表明 Frame Payload 的大小不应该超过 2^24-1 字节,但其实 payload 默认的大小是不超过 2^14 字节,可以通过 SETTING Frame 来设置 SETTINGS_MAX_FRAME_SIZE 修改允许的 Payload 大小。

  • Type: 表示 Frame 的类型,目前定义了 0-9 共 10 种类型。

  • Flags: 为一些特定类型的 Frame 预留的标志位,比如 Header, Data, Setting, Ping 等,都会用到。

  • R: 1-bit 的保留位,目前没用,值必须为 0

  • Stream Identifier: Steam 的 id 标识,表明 id 的范围只能为 0 到 2^31-1 之间,其中 0 用来传输控制信息,比如 Setting, Ping;客户端发起的 Stream id 必须为奇数,服务端发起的 Stream id 必须为偶数;并且每次建立新 Stream 的时候,id 必须比上一次的建立的 Stream 的 id 大;当在一个连接里,如果无限建立 Stream,最后 id 大于 2^31 时,必须从新建立 TCP 连接,来发送请求。如果是服务端的 Stream id 超过上限,需要对客户端发送一个 GOWAY 的 Frame 来强制客户端重新发起连接。

2 Frame 定义

下面来认识下各个类型的 Frame。


DATA


DATA Frame(type=0x0),用来传输可变长度的二进制流,这部分最主要的用途就是用来传递之前 HTTP/1 中的 Request 或 Response 的 Body 部分。


DATA Frame 的 Payload 格式如下:


 +---------------+ |Pad Length? (8)| +---------------+-----------------------------------------------+ |                            Data (*)                         ... +---------------------------------------------------------------+ |                           Padding (*)                       ... +---------------------------------------------------------------+
复制代码


DATA 字段比较好解释,就是要传输的数据内容,那么 Pad Length 和 Padding 是干什么用的?HTTP/2 在设计的时候就更多的考虑了数据的安全性,所以默认使用 HTTPS,除此之外,协议本身也对传输的数据做了一些安全考虑,填充就是其中一个。填充可以模糊帧的大小,使攻击者更难通过帧的数量来猜测传输内容的长度,减少破解的可能性。


DATA 帧使用到了 Flag 字段,其中最重要的是一个 END_STREAM (0x1)Flag,这个标志用来表示 Data Frame 的传输是否结束,当该标志位为 1 时,表示 Stream 的传输结束,发起 Stream 的一方会进入 half-closed(local)或者 closed 状态,关于 Stream 状态机的问题,后边再详细说,这部分也是一个需要用心理解的点。END_STREAM 在 Header 帧中也有用到,含义一样,不再单独说明。


HEADERS


HEADERS Frame(type=0x1)用于开启一个 Stream,当然也用于传输正常 HTTP 请求中的 Header 信息。


 +---------------+ |Pad Length? (8)| +-+-------------+-----------------------------------------------+ |E|                 Stream Dependency? (31)                     | +-+-------------+-----------------------------------------------+ |  Weight? (8)  | +-+-------------+-----------------------------------------------+ |                   Header Block Fragment (*)                 ... +---------------------------------------------------------------+ |                           Padding (*)                       ... +---------------------------------------------------------------+
复制代码


HEADERS 的结构比较简单,Header Block Fragment 字段用于存储正常的 Http Header 头信息,E、Stream Dependency、Weight 字段都是用于权重控制。由于 HTTP/2 是支持多路复用,也就是多个流同时进行传输,那么这个时候哪个流更重要,应该优先传输哪个,就需要用这些字段来进行控制了。


PRIORITY


PRIORITY Frame(type=0x2)用于指定 Stream 的优先级,这个在 Stream Dependencies, Dependency Weighting 等场景下会用到,PRIORITY 帧不能在 id 为 0 的 stream 上发送。由于我这次业务需求的场景用不到这块,所以没有特别深入的了解。


 +-+-------------------------------------------------------------+ |E|                  Stream Dependency (31)                     | +-+-------------+-----------------------------------------------+ |   Weight (8)  | +-+-------------+
复制代码


RST_STREAM


RST_STREAM Frame(type=0x3)用于立即终止 Stream.主要用来取消流,或者发生异常时表明需要终止。


 +---------------------------------------------------------------+ |                        Error Code (32)                        | +---------------------------------------------------------------+
复制代码


错误包含一个 32-bit 的整型数来表示错误的原因。


SETTINGS


SETTINGS Frame(type=0x4)用来控制客户端和服务端之间通信的一些配置。SETTINGS 帧必须在连接开始时由通信双方发送,并且可以在任何其他时间由任一端点在连接的生命周期内发送。SETTINGS 帧必须在 id 为 0 的 stream 上进行发送,不能通过其他 stream 发送;SETTINGS 影响的是整个 TCP 链接,而不是某个 stream;在 SETTINGS 设置出现错误时,必须当做 connection error 重置整个链接。SETTINGS 帧带有 Ack 的 Flag,接收方必须收到 ack 为 0 的 SETTINGS 后,应马上启用 SETTING 的配置并返回一个 Ack 为 1 的 SETTINGS 帧。



Flag Ack=false



Flag Ack=true


 +-------------------------------+ |       Identifier (16)         | +-------------------------------+-------------------------------+ |                        Value (32)                             | +---------------------------------------------------------------+
复制代码


常用的 SETTINGS 有几类:


  • SETTINGS_HEADER_TABLE_SIZE (0x1): 控制每个 Header 帧中的 HTTP 头信息的大小

  • SETTINGS_ENABLE_PUSH (0x2): 是否启用服务端推送(Server Push),默认开启;不管是服务端还是客户端发送了禁用的配置,那么服务端就不应该发送 PUSH_PROMISE 帧

  • SETTINGS_MAX_CONCURRENT_STREAMS (0x3): 用来控制多路复用中 Stream 并发的数量,这个主要是用来限制单个链接对服务端的资源的占用过大,这个值默认是没有限制,如果做一个 server 服务,那么建议一定要设置这个值,RFC 文档中建议不要小于 100,那么我们设置 100 就可以了。亚马逊的 Alexa 中 HTTP/2 协议服务端设置的这个值就是 100.


…其他几个如 SETTINGS_INITIAL_WINDOW_SIZE(0x4)、SETTINGS_MAX_FRAME_SIZE(0x5)、 SETTINGS_MAX_HEADER_LIST_SIZE(0x6)就不一一介绍了。


PUSH_PROMISE


PUSH_PROMISE Frame(type=0x5)用于服务端在发送 PUSH 之前先发送 PUSH_PROMISE 帧来通知客户端将要发送的 PUSH 信息。PUSH_PROMISE 涉及到 server push 的相关信息,内容比较多,这里不展开讲了,后边单独介绍。


+---------------+ |Pad Length? (8)| +-+-------------+-----------------------------------------------+ |R|                  Promised Stream ID (31)                    | +-+-----------------------------+-------------------------------+ |                   Header Block Fragment (*)                 ... +---------------------------------------------------------------+ |                           Padding (*)                       ... +---------------------------------------------------------------+
复制代码


PING


PING Frame(type=0x6) 是用来测量来自发送方的最小往返时间以及确定空闲连接是否仍然起作用的机制。 PING 帧可以从任何一方发送。PING 帧跟 SETTINGS 帧非常类似,一个是必须在 id 为 0 的 stream 上发送,另一个就是它也包含一个 Ack 的 Flag,发送方发送 ack=0 的 PING 帧,接收方必须响应一个 ack=1 的 PING 帧,并且 PING 帧的响应 应该 优先于任何其他帧。


 +---------------------------------------------------------------+ |                                                               | |                      Opaque Data (64)                         | |                                                               | +---------------------------------------------------------------+
复制代码


GOAWAY


GOAWAY frame(type=0x7)用于关闭连接,GOAWAY 允许端点优雅地停止接受新流,同时仍然完成先前建立的流的处理。这个就厉害了,当服务端需要维护时,发送一个 GOAWAY 的 Frame 给客户端,那么发送之前的 Stream 都正常处理了,发送 GOAWAY 后,客户端会新启用一个链接,继续刚才未完成的 Stream 发送。这样就可以做到完全不影响运行中的业务而进行服务端维护。它是如何做到这一点的呢,来看下 GOAWAY 的帧结构:


 +-+-------------------------------------------------------------+ |R|                  Last-Stream-ID (31)                        | +-+-------------------------------------------------------------+ |                      Error Code (32)                          | +---------------------------------------------------------------+ |                  Additional Debug Data (*)                    | +---------------------------------------------------------------+
复制代码


最明显的就是这个 Last-Stream-ID,GOAWAY 包含在此连接中已经或可能在发送端点上处理的最后一个对等启动 Stream 的 ID 标识符.例如,如果服务器发送 GOAWAY 帧,则识别的流是客户端发起的编号最高的流。


通过这个标识,双方就知道上次传输成功的一个 Stream Id 是多少,再重新发送数据的时候,就知道从哪个数据开始发送。避免了数据的丢失或者重复。


WINDOW_UPDATE


WINDOW_UPDATE frame(type=0x8)用于流控(flow control),此次需求用不到,偷个懒,不介绍了。


 +-+-------------------------------------------------------------+ |R|              Window Size Increment (31)                     | +-+-------------------------------------------------------------+
复制代码


CONTINUATION


CONTINUATION frame(type=0x9)用于持续的发送未发送完的 HTTP header 信息.如果前边是这三个帧(HEADERS, PUSH_PROMISE, or CONTINUATION),并且未携带 END_HEADERS 的 flag,就可以继续发送 CONTINUATION 帧。


 +---------------------------------------------------------------+ |                   Header Block Fragment (*)                 ... +---------------------------------------------------------------+
复制代码

3 多路复用

在 HTTP/1 中,如果客户端要想发起多个并行请求以提升性能,则必须使用多个 TCP 连接。在单个链接中,HTTP/1 对每个请求每次交付一个响应,并且必须受到影响后,才能继续发起请求。


HTTP/2 中新的二进制分帧层突破了这些限制,实现了完整的请求和响应复用:客户端和服务器可以将 HTTP 消息分解为互不依赖的帧,通过不同的 Stream 交错发送,最后再在另一端把它们重新组装起来。所以这里也能看到,他的请求响应模型跟 HTTP/1 也是一样的,只不过在传输的内容内部做了些手脚,来实现了多路复用。



如图,在一个 TCP 链接内,客户端发送了一个 stream ID=5 的 DATA 帧的数据包,但同时服务端响应的是 Stream ID=1 和 ID=3 的一些数据包,这样,就真正做到了,在一个链接内,同时有三个流并行的传输数据。


将 HTTP 消息分解为独立的帧,交错发送,然后在另一端重新组装是 HTTP/2 最重要的一项增强。事实上,这个机制会在整个网络技术栈中引发一系列连锁反应,从而带来巨大的性能提升,让我们可以:


  • 并行交错地发送多个请求,请求之间互不影响。

  • 并行交错地发送多个响应,响应之间互不干扰。

  • 使用一个连接并行发送多个请求和响应。

  • 不必再为绕过 HTTP/1.x 限制而做很多工作(例如级联文件、image sprites 和域名分片)

  • 消除不必要的延迟和提高现有网络容量的利用率,从而减少页面加载时间。

  • 等等…


HTTP/2 中的新二进制分帧层解决了 HTTP/1.x 中存在的队首阻塞问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。应用速度更快、开发更简单、部署成本更低。


这一篇比较枯燥,讲的都是一些概念性的内容,但如果想真正能使用到 HTTP/2 的一些特性,还是需要了解这些的,这次就先到这里吧,下次继续。


本文转载自公众号 360 云计算(ID:hulktalk)。


原文链接:


https://mp.weixin.qq.com/s/5tcqd40by8GBSsnTQEo-OQ


2019-11-18 18:361343

评论

发布
暂无评论
发现更多内容

EdgeView 4 for Mac(快速图像查看器)v4.4激活版

iMac小白

云原生微服务的SWOT分析

俞凡

微服务 云原生

在Go中构建复杂对象: 构建器模式指南

俞凡

golang 设计模式

04 | 复杂度分析(下):浅析最好、最坏、平均、均摊时间复杂度

鲁米

Medis for Mac(可视化管理工具)v2.13.0激活版

影影绰绰一往直前

iZip Archiver Pro for mac(强大的解压缩软件)v4.7.47激活版

iMac小白

03 | 复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?

鲁米

Proxifier for mac(全局代理客户端)稳定版v3.11激活版

影影绰绰一往直前

Retrobatch for mac(图像批量处理软件)v2.0.3激活版

iMac小白

再也不怕面试官问缓存雪崩、缓存击穿、缓存穿透了

程序员花卷

缓存 后端 缓存雪崩 布隆过滤器 可靠性设计

iA Writer for Mac(mac好用的写作软件)v7.0.0中文激活版

影影绰绰一往直前

OmniReader Pro for Mac(图书阅读器) 2.6.2密码激活版

mac

苹果mac Windows软件 OmniReader Pro 阅读软件

SecureCRT for mac(终端SSH工具)v9.3.2激活版

影影绰绰一往直前

抖音订单接口在电商行业中的重要性及实践应用

Noah

Performance Index 64 Pro for mac(性能检测软件)v4.2.12激活版

iMac小白

MediaInfo for mac(媒体信息检测软件)v23.11免激活版

iMac小白

抖音商品详情接口在电商行业中的重要性及实时数据获取实现

Noah

JetBrains PhpStorm 2023 for Mac(PHP集成开发)v2023.2.4中文激活版

影影绰绰一往直前

架构师的三类工作

agnostic

JixiPix PuzziPix Pro for mac(强大的拼图软件)v1.0.20激活版

影影绰绰一往直前

FlowJo 10 for Mac(流式细胞分析软件)v10.4激活版

影影绰绰一往直前

GoodTask for Mac(日历任务管理工具)v7.6.4中文激活版

iMac小白

[大厂实践] Pinterest通用计算平台实践

俞凡

架构 Kubernetes 云原生 大厂实践 Pinterest

Studio One 6 Pro for mac(音乐创作编辑软件)v6.2.0永久激活版

影影绰绰一往直前

选择适合您网站的 SQL 托管:MS SQL Server、Oracle、MySQL

小万哥

MySQL 数据库 程序员 sql 后端开发

Apeaksoft iPhone Eraser for Mac(iPhone数据清除工具)v1.0.20免激活版

iMac小白

OmniReader Pro for mac(全能阅读器)v2.6.2激活版

iMac小白

JProfiler for Mac(Java开发分析软件)v14.0.0永久激活版

影影绰绰一往直前

Termius for Mac(SSH客户端)v8.4.0激活版

影影绰绰一往直前

Magic Disk Cleaner for mac(磁盘垃圾清理工具)v2.5.3激活版

iMac小白

URL Manager Pro for Mac(浏览器标签管理应用)v6.4激活版

iMac小白

HTTP/2 in GO(一)_文化 & 方法_付坤_InfoQ精选文章