HTTP 框架 | 青训营笔记

79 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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 逻辑
    • 请求内有效
    • 问题定位难度增加