这是我参与「第五届青训营 」伴学笔记创作活动的第 16 天
HTTP 框架
HTTP 协议
- 超文本传输协议
为什么需要协议
- 需要明确的边界
- 能够携带信息
协议里有什么
常见方法名
- GET
- HEAD
- POST
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
状态码
- 1xx:信息类
- 2xx:成功
- 3xx:重定向
- 4xx:客户端错误
- 5xx:服务端错误
请求流程
- 业务层
- 服务治理层
- 中间件层
- 路由层
- 协议编解码层
- 传输层
区别
-
HTTP 1
- 队头阻塞
- 传输效率低
- 明文传输不安全
-
HTTP 2
- 多路复用
- 头部压缩
- 二进制协议
-
QUIC
- 基于 UDP 实现
- 解决对头阻塞
- 加密减少握手次数
- 支持快速启动
HTTP 框架的设计与实现
分层设计
- 专注性
- 扩展性
- 复用性
- 高内聚、低耦合
- 易复用
- 高扩展性
应用层设计
-
提供合理的 API
- 可理解性
- 简单性
- 冗余性
- 兼容性
- 可测性
- 可见性
中间件设计
中间件需求
- 配合 Handler 实现一个完整的请求处理生命周期
- 拥有预处理逻辑与后处理逻辑
- 可以注册多中间件
- 对上层模块用户逻辑模块易用
洋葱模型
-
适用场景
- 日志记录
- 性能统计
- 安全控制
- 事务处理
- 异常处理
调用链
- 不调用 Next:初始化逻辑并不需要在同一调用栈
- 调用 Next:后处理逻辑或许哟啊在同一调用栈上
路由设计
-
框架路由实际上就是为 URL 匹配对应的处理函数 (Handlers)
- 静态路由
- 参数路由
- 路由修复
- 冲突路由以及优先级
- 匹配 HTTP 方法
- 多处理函数:方便添加中间件
如何匹配 HTTP 方法
- 外层 Map:根据 method 进行初步筛选
如何实现添加多处理函数
- 在每个节点上使用一个 list 存储 handler
协议层设计
网络层设计
- BIO
- NIO
性能
针对网络库优化
-
go net
- 存下全部 Header
- 减少系统调用次数
- 能够复用内存
- 能够多次读
-
go net with bufio
- 绑定一块缓冲区
-
netpoll
- 存下全部 Header
- 拷贝出完成的 Body
-
netpoll with nocopy peek
- 分配足够大的 buffer
- 限制最大 buffer size
不同网络库优势
-
go net
- 流式友好
- 小包性能高
-
netpoll
- 中大包性能高
- 时延低
针对协议的优化
Headers 解析
- 找到 Header Line 边界:\r\n
-
针对协议相关的 Headers 快速解析
- 通过 Header Key 首字母快速筛选掉完全不可能的 key
- 解析对应 value 到独立字段
- 使用 byte slice 管理对应 header 存储,方便复用
-
请求体中同样处理的 Key
- User_agent
- Content-Type
- Content-Length
- Connection
- Transfer-Encoding
-
优点
- 核心字段快速解析
- 使用 byte slice 存储
- 额外存储到成员变量
-
缺点
- 普通 leader 性能较低
- 没有 map 结构
Header key 规范化
-
优点
- 超高的转化效率
- 比 net.http 提高 40 倍
-
缺点
- 额外的内存开销
- 变更困难
热点资源池化
-
优点
- 减少了内存分配
- 提高了内存复用
- 降低了 GC 压力
- 性能提升
-
缺点
- 额外的 Reset 逻辑
- 请求内有效
- 问题定位难度增加