HTTP 框架修炼之道 | 青训营笔记

86 阅读5分钟

HTTP协议介绍

协议的含义

超文本传输协议(Hypertext Transfer Protocol)

协议的作用

  • 举个栗子🍘:人在说话的时候需要一定的语法、主谓宾去造句,这样对方才能理解说话人想要表达的含义。同理,在网线上传输的都是比特流(0/1数据流),需要使用一定的规则才能转化为可理解的数据并在网络间传播。
  • 所以协议需要:
  1. 明确的边界:开始、结束
  2. 能够携带信息:什么消息、消息类型

协议的内容

  • 请求行/状态行:
  1. 请求行:方法名(如GET、HEAD、POST、PATCH等)、URL、协议版本
  2. 状态行:协议版本、状态码(1xx:信息类、2xx:成功、3xx:重定向、4xx:客户端错误、5xx:服务端错误)、状态码描述
  • 请求头/响应头:协议相关(指定字段有多少个字节等)+业务相关(源数据)
  • 请求体/响应体:具体业务内容

请求流程

类似OSI七层网络模型,可将请求流程分为以下五层(从左到右依次深入): 业务层->服务层+中间件层->路由层->协议编(解)码层->传输层

不足与展望

  • HTTP1:基于TCP实现,会出现队头阻塞、传输效率低、明文传输不安全的缺点
  • HTTP2:多路复用、头部压缩、二进制协议,但由于仍基于TCP,没有解决队头阻塞的问题
  • QUIC:基于UDP实现,可解决队头阻塞,加密减少握手次数,支持快速启动

HTTP框架的设计与实现

分层设计

  • 专注性:高内聚、低耦合
  • 扩展性:高扩展性
  • 复用性:易复用

分层实现——实例

分为五层(还有一层是并行的),层与层之间使用接口解耦,从上到下分别是

  • Application(应用)层:提供应用API,直接与用户交互
  • middleware(中间件)层:对用户进行预处理和后处理的逻辑
  • route(路由)层:原生路由实现
  • codec(协议)层:实现通信
  • transport(网络)层:不同网络的使用场景互不相同 (commons层:放置一些公共逻辑,每一层都会调用)

应用层设计

提供合理的API:可理解性、简单性,去除冗余性、兼容性

中间件设计

中间件需求:

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

经典中间件模型

洋葱模型:日志->Metrics->Biz Handler(预处理和后处理,处理请求与发送回复)

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

路由设计

框架路由实际上就是为URL匹配对应的处理函数(Handlers),包括静态路由(/a/b/c)、参数路由(/a/:id/c)等。

  • 使用map[string]handlers:快和简单,但只能匹配静态路由
  • 前缀匹配树:可以匹配静态路由,参数路由

匹配HTTP方法

外层Map:在路由映射表中,根据method进行初步筛选

添加多处理函数

在每个节点上使用一个list存储handler

查找路由

明确需求、业界调研、方案权衡、方案评审、确定开发

协议层设计

核心是抽象出合适的接口。

网络层设计

BIO:一旦建立连接就启动方法读取数据,读数据读到一半卡住了就会暂停直至超时。 NIO:注册一个监听器,监听器读取到一定的数据后就会唤醒相应的方法。(整个流程没有阻塞)

Golang的标准库——go net

是一个典型的BIO,有两个关键接口Read和Write。需要由用户管理Buffer。

netpoll网络库

采用NIO模式,有两个关键接口Reader和Writer,Writer底下也包括两个接口Malloc(开辟空间,拷贝数据)和Flush(数据发送出去,清理空间)。需要网络库管理Buffer。

性能修炼之道

针对网络库的优化

❓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,时间复杂度:O(n) 👉提升速度方法:SIMD:单指令流多数据流(一个指令能够同时处理多个数据)
  • 针对协议相关的Headers快速解析
  1. 通过Header key首字母快速筛除掉完全不可能的key
  2. 解析对应value到独立字段
  3. 使用byte slice管理对应header存储,方便复用
  • 优点:核心字段快速解析;使用byte slice存储;额外存储到成员变量中
  • 缺点:普通header性能较低;没有map结构

Header key规范化

采用表映射的方式,将对应的字节转化为ASCII码,在对应的表里进行匹配查找并返回。

  • 优点:超高的转换效率;比net.http提高40倍
  • 缺点:额外的内存开销;变更困难

热点资源池化

RequestContext(包括Request、Response和Data)和请求一一对应、贯穿一个请求始终。 👉 将所有RequestContext放入RequestContext池里。

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

企业实践

  • 追求性能
  • 追求易用,减少误用
  • 打通内部生态
  • 文档建设、用户群建设(问题沉淀)