写点什么

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

评论

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

对话吴军:人工智能如何推动金融行业的数字化转型

索信达控股

人工智能 大数据 金融科技 数字化转型 金融

阿里最新发布的 Spring Cloud ALiBaBa 全解第三版,一睹庐山真面目!

Java 程序员 架构 微服务

揭秘视频千倍压缩背后的技术原理之预测技术

拍乐云Pano

竞赛|数据竞赛Top解决方案开源整理

不脱发的程序猿

开源 数据竞赛

百度大规模Service Mesh落地实践

百度Geek说

Service Mesh 软件架构

基于 Flink 打造的伴鱼实时计算平台 Palink 的设计与实现

Apache Flink

flink

奇亚矿机系统,Bzz分币系统,云算力APP开发

QCon演讲| 从团伙到团队,PingCode研发团队敏捷实践血泪史

易成管理学

敏捷 研发管理 研发效能 开发

👋 Agora Flat 开源教室 Web 版上线啦~

千竹

开源 音视频 在线教育 互动白板 互动课堂

我的编辑器能玩贪吃蛇,一起玩不?

华为云开发者联盟

大前端 编辑器 贪吃蛇 Blot Quill

并发王者课-青铜10:千锤百炼-如何解决生产者与消费者经典问题

MetaThoughts

Java 多线程 并发

【布道API】API端点/资源命名最佳实践

devpoint

RESTful Rest API 6月日更

网络攻防学习笔记 Day40

穿过生命散发芬芳

网络攻防 6月日更

6月18日华为云携手中科院上海药物所,深度解读AI药物研发技术

华为云开发者联盟

AI 华为云 药物 TechWave EIHealth

并发王者课-青铜9:防患未然-如何处理线程中的异常

MetaThoughts

Java 多线程 并发

JAVA中的浮点数与二进制

加百利

Java 后端 二进制 6月日更

【FlinkSQL】Flink Table & SQL 时间属性

Alex🐒

flink 翻译 FlinkSQL flink1.13

「免费开源」基于Vue和Quasar的前端SPA项目crudapi后台管理系统实战之EXCEL数据导入(九)

crudapi

Vue crud crudapi qusar 数据导入

淘宝“618”双11系统架构是如何设计的呢?这份Java千亿级并发系统架构设计笔记告诉你答案

Java 程序员 架构 计算机

从零开始学习3D可视化之场景层级(1)

ThingJS数字孪生引擎

大前端 物联网 可视化 3D可视化 数字孪生

爱奇艺于首届MediaAIAC与首届高新视频创新应用大赛斩获三项权威大奖,技术实践领跑行业创新

爱奇艺技术产品团队

2021年马士兵老师1000道Java大厂面试真题视频解析+笔记+源码

Java架构追梦

Java 架构 面试 马士兵

5分钟带你玩转国内首款研发自动化工具PingCode Flow

PingCode研发中心

研发管理 研发效能 自动化管理 研发工具

【FlinkSQL】Flink Table & SQL 数据类型

Alex🐒

flink 翻译 FlinkSQL flink1.13

液体测量技术:从水到血液

不脱发的程序猿

物联网 液体测量技术 测量技术 ADI

教你三种jQuery框架实现元素显示及隐藏动画方式

华为云开发者联盟

jquery 动画 元素 JQuery框架 网页

一文你带快速认识Vue-Router路由

华为云开发者联盟

html Vue vue-router 路由 路由管理器

MTU带来的问题

BUG侦探

网络技术 网络运维 GRE 隧道

都啥年代了,求你别再说Redis是单线程了!

Java redis 编程 程序员

36氪企服点评|中国企服软件金榜-项目管理系列榜单揭晓,Worktile夺魁!

易成管理学

项目管理 研发管理 研发工具 项目管理工具

汽车之家:基于 Flink + Iceberg 的湖仓一体架构实践

Apache Flink

flink

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