HTTP框架修炼 | 青训营笔记

126 阅读5分钟

课程目录

  • 01 再谈HTTP协议
  • 02 HTTP框架的设计与实现
  • 03 性能修炼之道
  • 04 企业实践

01 再谈HTTP协议

1.1 HTTP协议是什么

HTTP:Hypertext Transfer Protocol 超文本传输协议

协议需要明确的边界

  • 开始
  • 结束

能够携带信息

  • 什么消息
  • 消息类型
  • ……

1.2 协议里有什么

一个常见的post请求做了什么?

request:

方法、URL 、 版本 协议头 空行 主体

response:

版本 状态码 协议头 空行 主体

  • 请求行/状态行
    • 方法名
    • URL
    • 协议版本
  • 请求头/响应头
  • 请求体/响应体

1.3 请求流程

  • 业务层
  • 服务治理、中间件层
  • 路由层
  • 协议编解码层
  • 传输层

1.4 不足与展望

HTTP1:

  • 对头阻塞
  • 传输效率低
    • 首部长
  • 明文传输不安全

HTTP2:

  • 多路复用
  • 头部压缩
  • 二进制协议

QUIC:

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

1.5 小结

  • HTTP协议出现的背景

  • 协议里有什么

  • 请求流程

  • 不足与展望

02 HTTP协议

2.1 分层设计

  • 专注性
  • 扩展性
  • 复用性

框架设计:

  • 高内聚 低耦合
  • 易复用
  • 高扩展性
   一个切实可行的复杂系统势必是从一个切实可行的简单系统发展而来的。从头开始设计的复杂系统根本不切实可行,无法修修补补让它切实可行。你必须由一个切实可行的简单系统重新开始。

2.2 应用层设计

提供合理的API

  • 可理解性
    • 如ctx.Body(), ctx.GetBody(),
    • 不要用 ctx.BodyA()
  • 简单性:
    • 如 ctx.Request.Header.Peek(key) / ctx.GetHeader(key)
  • 冗余性
    • 不要出现重复的接口
    • 可由多个接口合成的也应该避免
  • 兼容性
  • 可测性
  • 可见性

不要试图在文档里说明,很多用户不看文档

2.3 中间件层

  • 配合Handler实现一个完整的请求处理生命周期

  • 拥有预处理逻辑与后处理逻辑

  • 可以注册多中间件

  • 对上层模块用户逻辑模块易用

适用场景:

  • 日志记录

  • 性能统计

  • 安全控制

  • 事务处理

  • 异常处理

中间件设计:

  1. 既然要实现预处理和后处理,那这个就很像调用了一个函数

  2. 路由上可以注册多Middleware,同时也可以满足请求级别有效 只需要将Middleware设计为和业务和Handler相同即可。

  3. 用户如果不主动调用下一个处理函数怎么办?

    • 帮助用户去实现逻辑,index递增
  4. 出现异常想停止怎么办

    • 设置index最大值

2.4 路由设计

框架路由实际上就是为URL匹配对应的处理函数(Handlers)

  • 静态路由:/a/b/c、/a/b/d

  • 参数路由:/a/:id/c (/a/b/c,/a/d/c)、*all

  • 路由修复:/a/b <->/a/b/

  • 冲突路由以及优先级:/a/b、:id/c

  • 匹配HTTP方法

  • 多处理函数:方便添加中间件

  • ……

  • 青铜

    • map[string]handlers
    • 只对静态路由有效
  • 黄金

    • 前缀树
    • 如何处理带参数的路由?
    • 抹掉中间的差距,比如把:c/:b统一匹配到:

如何匹配HTTP方法?

graph LR
map-->method-->前缀树

外层map:根据method初筛

如何做设计?

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

2.5 协议层设计

  • 抽象出合适的接口

2.6 网络层设计

  • BIO 阻塞IO

  • NIO

    • 先收集足够的数据
  • go net

    • go net "BIO"

    • 用户管理buffer

  • netpoll

    • 公司内部
    • NIO
    • 网络库管理buffer

2.7 小结

  • API设计:可理解性、简单性

  • 中间件设计:洋葱模型

  • 路由设计:前缀匹配树

  • 协议层设计:抽象出合适的接口

  • 网络层设计:网络模型

03 性能的修炼之道

3.1 针对网络库的优化

go net

  • 存下全部Header

  • 减少系统调用次数

  • 能够复用内存

  • 能够多次读

  • BIO

  • 优化

    • go net with bufio 绑定一个缓冲区

netpoll

  • 采用 linkbuffer

  • 存下全部Header

  • 拷贝出完整的Body

  • 优化

    • netpoll with no copy peek
      • 分配足够大的buffer
      • 限制最大buffer size

不同网络库的优势:

  • go net
    • 流式友好
    • 小包性能高
  • netpoll
    • 中大包性能高
    • 时延低

3.2 针对协议的优化 - headers解析

找到header line 边界: \r\n

  • 两个连续的\r\n表示结束

  • 优化:先找到\n 再看前一个是不是\r

  • 更快 :SIMD、Sonic(bytedance/sonic)

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

  1. 通过Header key首字母快速筛除掉完全不可能的key

  2. 解析对应value到独立字段

  3. 使用byte slice管理对应header存储,方便复用

请求体中同样处理的Key: User Agent、Content Type、Content Length Connection、Transfer coding

    • 核心字段快速解析

    • 使用byte slice存储

    • 额外存储到成员变量中

    • 普通header性能较低
    • 没有map结构

3.3 header key规范化

  • 采取表映射的方式

  • aaa-bbb -> Aaa-Bbb

  • 超高的转换效率

  • 额外的内存开销

    • 比net.htp提高40倍
    • 变更困难

3.4 热点资源池化

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

3.5 总结

  • 针对网络库的优化:buffer设计
  • 针对协议的优化:header解析、热点资源池化

04 企业实践

  • 追求性能

  • 追求易用,减少误用

  • 打通内部生态

  • 文档建设、用户群建设

内部框架:Hertz

05 总结

  • HTTP协议的相关知识

  • HTTP框架的一般设计

  • HTTP框架的优化手段

  • 企业实践