HTTP框架修炼之道
这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记
[TOC]
HTTP协议
HTTP协议是什么
超文本传输协议 Hypertext Transfer Protocol
为什么需要协议:需要一个明确的规则用于传输信息
HTTP协议里有什么
- 请求行/状态行
- 请求头/响应头
- 请求体/响应体
不足与展望
-
HTTP1
- 队头阻塞
- 传输效率低
- 明文传输不安全
-
HTTP2
- 多路复用
- 头部压缩
- 二进制协议
-
QUIC
- 基于UDP实现
- 解决队头阻塞
- 加密减少握手次数
- 支持快速启动
HTTP框架的设计与实现
分层设计
| TCP/IP 4层 | TCP/IP 5层 | OSI 7层 | 主要功能 | 传递对象 |
| 应用层 | 应用层 | 应用层 | SMTP, FTP, Telnet, DNS, TFTP, RPC etc | 报文 |
| 表示层 | ||||
| 会话层 | ||||
| 应用层 | 应用层 | 传输层 | TCP, UDP | 传输协议分组 |
| 网络层 | 网络层 | 网络层 | IP, ARP | IP数据报 |
| 网络接口层 | 数据链路层 | 数据链路层 | 链路控制和媒体访问 | 帧 |
| 物理层 | 物理层 | 以太网 |
分层设计提升了系统设计的专注性、扩展性、复用性
分层设计时需要考虑到:
- 高内聚,低耦合
- 易复用
- 高扩展性
应用层设计
应用层主要包括:Context、Request、Response、Handler
设计应用层的主要目的是提供合理的API
- 可理解性
- 简单性
常用的API放到上层,无用/低频的API放到下层
- 冗余性
不需要冗余,或能通过多个API组合实现功能
- 兼容性
尽量避免Break change
- 可测性
- 可见性
最小暴露原则
中间件设计
中间件层主要包括:Recovery、CircuitBreak、Timeout、AccessLog
中间件层需求:
- 配合Handler实现一个完整的请求处理生命周期
- 拥有预处理逻辑和后处理逻辑
- 可以注册多个中间件
- 对上层模块用户逻辑模块易用
中间件层通常采用“洋葱模型”
一个请求首先以此通过日志中间件、Metrics中间件,然后在Handler获取响应,然后经历后处理逻辑再传递出去,洋葱模型极大提高了组件的复用性
常见的适用场景:
- 日志记录
- 性能统计
- 安全控制
- 事务处理
- 异常处理
路由层设计
路由层主要包括:Add、Find、Route Tree、Route Group
框架路由实际上就是为了URL匹配对应的处理函数
| 功能点 | 实现 |
|---|---|
| 静态路由 | /a/b/c /a/b/d |
| 参数路由 | /a/:id/c /*all |
| 路由修复 | /a/b <-> /a/b/ |
| 冲突路由以及优先级 | /a/b /:id/c |
| 匹配HTTP方法 | |
| 多处理函数 | 方便添加中间件 |
使用的数据结构:前缀匹配树
协议层设计
协议层主要包括:WebSocket、HTTP1、Quic、HTTP2
协议层设计的主要方式:抽象出合适的接口
type Server interface {
Serve(c context.Context, conn network.Conn) error
}
网络层设计
网络层主要包括: netpoll, go net
BIO和NIO
BIO:Block IO 阻塞IO
go func() {
for {
conn, _ := listener.Accept()
go func () {
conn.Read(request)
// handle ...
conn.Write(response)
}()
}
}
NIO:Non-Blocking IO
go func () {
for {
readableConns, _ := Monitor(conns)
for conn := range readableConns {
go func () {
conn.Read(request)
// handle ...
conn.Write(response)
}()
}
}
}
NIO通过监听器实现不会像BIO一样阻塞
性能
针对网络库的优化
go net
- 存下全部的Header
- 减少系统调用次数
- 能够复用内存
- 能够多次读
netpoll
- 存下全部的Header
- 拷贝完整的Body
不同网络库的优势
| 网络库 | 优势 |
|---|---|
| go net | 流式友好,小包性能高 |
| netpoll | 中大包性能高,时延低 |
针对协议的优化
Header解析
- 通过Header Key首字母快速筛除完全不可能的Key
- 解析对应value到对应字段
- 用Byte slice管理对应的header存储,方便复用