青训营笔记(2)-HTTP框架| 青训营

70 阅读7分钟

HTTP协议

HTTP协议是什么

Hypertext Tranfer Protocal:超文本传输协议。在计算机网络的学习中都非常熟悉了,不熟悉的小伙伴可以看看《图解HTTP》这本书,讲的非常通俗易懂。

为什么需要协议?

  • 需要明确的边界:什么时候开始,什么时候结束?
  • 需要携带信息:什么消息?消息类型?

一个常见的POST请求在协议层到底做了什么?

POST /sis HTTP/1.1 —— 请求行 Who:Alex —— KV对(元数据) Content-Type:text/plain —— KV对(元数据) Host:127.0.0.1:8888 —— KV对(元数据) Content-Length:28 —— KV对(元数据),表示发送多少个字节 Let’s watch a movie together. —— 请求体

协议内有什么?

  • 请求行/状态行 请求行:方法名(GET POST PUT DELETE PATCH TRACE OPTIONS CONNECT)、URL、协议版本

顺带一提PUT和PATCH的区别: PUT是完整更新,幂等。PATCH类似PUT,部分更新,不是幂等。

状态行:协议版本、状态码(1xx/2xx/3xx/4xx/5xx)、状态描述

  • 请求头/响应头:协议约定、业务相关
  • 请求体/响应体

请求流程

image.png

不足和展望

HTTP1:

  • 基于Tcp,有队头阻塞问题,后续的分片需要等待前面分片的到来。
  • 传输效率低:无用信息非常多、不支持多路复用
  • 明文传输不安全

HTTP2:

  • 可以多路复用
  • 可以头部压缩,减少头部数据量
  • 二进制协议,解析起来更高效
  • 还是基于TCP,没有解决队头阻塞问题。
  • 握手开销没有优化。

QUIC:

  • 基于UDP实现
  • 解决队头阻塞
  • 加密减少握手次数
  • 支持快速启动

HTTP框架的设计与实现

分层设计

OSI七层模型:应用层(Application)、表示层(Presentation)、会话层(Session)、传输层(Transport)、网络层(Network)、数据链路层(Data Link)、物理层(Physical)

三大特点:专注性、扩展性、复用性

image.png

高内聚、低耦合、易复用、高扩展性

image.png

image.png 应用层:与用户打交道,对请求进行抽象,提供丰富的应用API。 中间件层:预处理和互处理的逻辑 路由层:路径映射等 协议层:支持Websocket、HTTP1、HTTP2、QUIC协议。 网络层:灵活提供网络库的能力。netpoll、go ret 然后就是一些通用的模块

应用层设计

提供合理的API:

  • 可理解性。如ctx.Body()或者ctx.GetBody(),而不是ctx.BodyA()
  • 简单性。一些高频接口就不要像Java一样搞很多API了。
    • 如ctx.Request.Header.Peek(key),就不如ctx.GetHeader(key)
  • 冗余性。做同样的事情不需要两个接口,不要有一个接口是由其它接口拼接而成
  • 兼容性。
  • 可测性。
  • 可见性:不能让用户拿到框架很核心的东西。

老师的工作感受:不要试图在文档中说明,很多用户不看文档。

中间件层

中间件可以将核心逻辑和业务逻辑分离。

中间件需求:

  • 配合Handler实现一个完整的请求处理生命周期。
  • 拥有预处理逻辑与后处理逻辑。
  • 可以注册多中间件。
  • 对上层模块用户逻辑模块易用。

洋葱模型: 适用场景:日志记录、性能统计、安全控制、事务处理、异常处理

image.png

例如,打印每个请求的request和response。

  1. 既然要实现预处理的和后期处理,那就很像调用一个函数
  2. 路由上可以注册多个中间件,同时也可以满足请求级别有效,只需要将中间件设计为和业务和Handler相同即可。统一为Next函数。
  3. 如果用户不主动调用下一个处理函数?
    1. 帮用户主动调用,内部封装一个index,核心是:在任何场景下index保持递增。类似下面的代码
  4. 出现异常想停止怎么办?index = maxIndex,跳出循环即可

image.png

调用链:

image.png

适用场景: 不调用Next的情况:初始化逻辑并且不需要在同一调用栈。 调用Next的情况:后处理逻辑或者需要在同一个调用栈上。

路由设计

框架路由实际上就是为URL匹配对应的Handlers。 功能大概可以分为:

  • 静态路由:/a/b/c
  • 参数路由:/a/:id/c
  • 路由修复:/a/b -> /a/b/
  • 冲突路由以及优先级:/a/b/c/a/:id/c
  • 匹配HTTP方法
  • 多处理函数:方便添加中间件

具体怎么设计呢?

  • 青铜:Map存储。
    • 只能支持静态路由,不支持通配符。
    • 优点是快。
  • 黄金:前缀匹配树。

image.png

如何匹配HTTP方法: 路由映射表:K为String类型的Method,V为前缀树

如何实现添加多处理函数: 每个节点用一个list存储handler。

image.png

如何做设计?

  1. 明确需求:考虑清楚要解决什么问题,有哪些需求 。
  2. 业界调研:业界都有哪些解决方案可以参考。
  3. 方案权衡:思考不同方案的取舍。
  4. 方案评审:相关同学对不同方案做评审。
  5. 确定开发:确定最合适的方案进行开发。

协议层

抽象出合适的接口:

image.png

image.png

网络层

BIO:阻塞IO。占线,卡住。

image.png

NIO:注册一个监听器,监听器监听到足够的数据之后,再去进行唤醒func,

image.png

go net 是BIO,需要用户管理buffer。 netpoll 字节公司内部自研库,是NIO,网络库管理buffer。

让框架提高性能

针对网络库的优化

go net

需求: 存下全部Header,Header有大有小 减少系统调用次数,系统调用涉及内核态和用户态的切换,开销比较大 能够复用内存 能够多次读

对此,我们可以绑定一块缓冲区:go net with bufio, Peek接口:读的时候让指针不动,下次还能在这里读。 Discard接口:让指针移动的接口。 Release接口:回收内存。

netpoll

存下全部Header 拷贝出完整的Body

netpoll with nocopy peek 分配足够大的buffer 限制最大buffer size

image.png

不同网络库的优势

go net

  • 流式友好
  • 小包性能高

netpoll

  • 中大包性能高
  • 时延低

针对协议的优化--Headers解析

Header Line边界:\r\n。KMP和BM算法针对的是一般的字符串以及很极端的字符串,针对HTTP这种有特征的数据:先找到\n,再看看其前面是否有\r

能不能更快呢?SIMD(计算机体系结构这门课的内容,跟汇编有关) SIMD技术加速后,找到\n的速度大幅提升。

针对协议相关的Headers快速解析:

  1. 通过Header key首字母快速筛掉完全不可能的key
  2. 解析对应的value到独立字段
  3. 使用byte slice管理对应Header存储,方便复用。

取舍:

  • 取:核心字段快速解析、使用byte slice存储、额外存储到成员变量中。
  • 舍:普通header性能较低、没有map结构

针对协议的优化-Header key规范化

将aaa-bbb改成Aaa-Bbb

取舍:

  • 取:超快的转换效率、比net.http提高40倍
  • 舍:额外的内存开销、变更困难

热点资源池化

image.png

取舍:

  • 取:减少内存分配、提高内存复用、降低GC压力、性能提升
  • 舍:额外的Reset逻辑、请求内有效、问题定位难度增加

企业实践

  • 追求性能
  • 追求易用,减少误用:这与性能有矛盾,例如池化会导致生命周期的问题。
  • 打通内部生态:有时候为了追求优化,会割裂社区的生态和内部的生态。
  • 文档建设、用户群建设——将常见问题沉淀下来。

字节内部Http框架:Hertz。1w+服务,3kw+QPS

总结

  1. Http协议
  2. Http框架设计
  3. Http框架优化
  4. 企业实践