阿里云「飞天发布时刻」2024来啦!新产品、新特性、新能力、新方案,等你来探~ 了解详情
写点什么

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:361337

评论

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

大模型时代下的全新变革

九章云极DataCanvas

Centos8 stream系统编译安装Nginx1.22教程。

百度搜索:蓝易云

nginx 云计算 Linux centos 运维

极狐GitLab 3 步优化软件价值流,谨防偷走时间、制造瓶颈的“幕后黑手”

极狐GitLab

gitlab 可视化 软件研发 价值流管理 VSM

质押理财USDT系统搭建开发案例

薇電13242772558

中移链与BSN分布式云管平台集成,共同构建专属协同体系

BSN研习社

全球LED显示屏市场机遇与挑战

Dylan

封装 芯片 LED显示屏 体育 全球

Nautilus Chain 主网上线,创世 ZBC 质押即将开启

鳄鱼视界

打造数据处理新范式,DataPilot畅游数据向量海

九章云极DataCanvas

AI与数据双向赋能,DingoDB成向量海时代超强引擎

九章云极DataCanvas

探索Reactor网络模型在当今应用领域的革新

华为云开发者联盟

开发 华为云 企业号 7 月 PK 榜 华为云开发者来联盟

REST 无状态与有状态的理解与应用

Apifox

gRPC RESTful 开发 Rest REST API

用 Generative AI 构建企业专属的用户助手机器人

TiDB 社区干货传送门

阿里大牛用了一个月把Github上热度最高的Java面试八股文总结出来了,按这个学,找工作没问题

架构师之道

java面试

影响云安全的因素有哪些?如何保障云安全?

行云管家

云安全 企业上云 堡垒机 自动化运维 云管

Apache Flink 在翼支付的实践应用

Apache Flink

大数据 flink 实时计算

Spring高手之路3——揭秘Spring依赖注入和SpEL表达式

砖业洋__

spring 依赖注入 属性注入 SpEL表达式 Bean配置

# **基于TiDB Binlog架构的主备集群切换操作手册**

TiDB 社区干货传送门

实践案例 管理与运维

绘出「星辰大海」:华为云Astro轻应用新手指南-第一章

华为云PaaS服务小智

云计算 华为 开发者 华为云

模糊测试公布结果,大众漏洞被曝光

云起无垠

网络安全 模糊测试

《APaaS应用实施方法论》电子书正式发布

明道云

火山引擎DataTester:三类AB实验,让企业营销拥有灵敏“网感”

字节跳动数据平台

大数据 A/B测试 对比试验 企业号 7 月 PK 榜

全面构建AI能力,AIFS为AI产业发展按下“加速键”

九章云极DataCanvas

单一可信源代码托管平台的构建之道

极狐GitLab

gitlab 安全 高效 便捷 源代码管理

记一次sst文件损坏修复过程

TiDB 社区干货传送门

故障排查/诊断

7.25齐聚西安!助推国资国企建设一流司库管理体系

用友BIP

全球司库 国资国企

什么是MES,MES系统有哪些功能模块?MES系统概述

优秀

MES系统 mes

下载|GitLab 2023 年 DevSecOps 全球调研报告:安全左移深入人心、AI/ML 蔚然成风

极狐GitLab

gitlab DevSecOps AI/ML 安全左移 安全实践

MobPush 厂商通道申请指南

MobTech袤博科技

大数据 华为 程序员 前端 Android;

九章元识大模型加速AI产业创新发展

九章云极DataCanvas

Nautilus Chain 主网上线,创世 ZBC 质押即将开启

威廉META

pump 日志错误信息疑问之你的pump 数据被gc了嘛?

TiDB 社区干货传送门

性能调优 实践案例 集群管理 TiDB 源码解读

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