1HTTP框架
1.1HTTP协议
HTTP: 超文本传输协议(Hypertext Transfer Protocol)
作用
- 需要明确的边界
- 能够携带信息
协议内容
-
请求行
- 方法名
- URL
- 协议版本
-
状态行
- 协议版本
- 状态码
- 状态码描述
-
请求头
- 协议约定
- 业务相关
-
响应头
- 协议约定
- 业务相关
-
请求体/响应体
常见方法名 GET、HEAD、POST、PUT、DEL ETE、CONNECT、OPTIONS、TRACE、PATCH
状态码 1 xx:信息类 2xx:成功 3xx:重定向 4xx:客户端错误 5xx:服务端错误‘
请求流程
不足
-
HTTP1
- 队头阻塞
- 传输效率低明文
- 传输不安全
-
HTTP2
- 多路复用
- 头部压缩
- 二进制协议
-
QUIC
- 基于UDP实现
- 解决队头阻塞
- 加密减少握手次数
- 支持快速启动
1.2HTTP框架设计与实现
分层设计
-
专注性
-
扩展性
-
复用性
-
高内聚低耦合
-
易复用
-
高扩展性
应用设计
-
可理解性:如ctx. Body (),ctx. GetBody ()
不要用ctx. BodyA ()
-
简单性:如ctx. Request. Header. Peek (key)
/ctx. GetHeader (key)
- 冗余性
- 兼容性
- 可测性
- 可见性
中间件设计
需求
- 配合Handler 实现一个完整的请求处理生命周期
- 拥有预处理逻辑与后处理逻辑
- 可以注册多中间件
- 对上层模块用户逻辑模块易用
洋葱模型
适用场景
- 日志记录
- 性能统计
- 安全控制
- 事务处理
- 异常处理
调用链
适用场景
- 不调用Next: 初始化逻辑且不需要在同一调用栈
- 调用Next: 后处理逻辑或需要在同一调用栈上
路由设计
- 静态路由: /a/b/c、/a/b/d
- 参数路由: /a/:id/c (/a/b/c, /a/d/c)、 /*alI
- 路由修复: /a/b <-> /a/b/
- 冲突路由以及优先级: /a/b、 /:id/c
- 匹配HTTP方法
- 多处理函数:方便添加中间件
处理路由
- 青铜: map [str ing]handlers /a/b/c、/a/b/d /a/:id/c、/*alI
- 黄金:前缀匹配树 /a/b/c、/a/b/d
协议层设计
抽象出适合的接口
type Server interface {
Serve(c context.Contextconn network.Conn) error
}
网络层
BIO
go func() {
for {
conn := listener.Accept
go func(){
conn. Read(request)
handle...
conn. Write(response)
}()
}
}()
NIO
go func() {
for {
readableConns, _ := Monitor( conns )
for conn := range readableConns {
go func(){
conn. Read(request)
handle...
conn. Write(response)
}()
}
}
}()
//BIO
//用户管理库
type Conn interface {
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error )
}
//NIO
//网络管理库
type Reader interface {
Peek(n int) ([]byte, error)
}
type Writer interface {
Malloc(n int) (buf []byte,err error )
Flush() error
}
type Conn interface {
net.Conn
Reader
Writer
}
1.3性能优化
网络库
go net
-
go net
存下全部Header 减少系统调用次数 能够复用内存 能够多次读
-
go net with bufio
绑定块缓冲区
type Reader interface {
Peek(n int) ([]byte, error)
Discard(n int) (discarded int, err error )
Release() error
Size() int
Read(b []byte) (l int, err error)
}
type Writer interface {
Write(p []byte)
Size() int
Flush() error
}
那基于此,我们可以对勾在勾标准库的接口上面封装一层 buffer 说也就是用一块用一个常用的一种优化手段。就是绑定在这个连接上面,绑定一块缓冲区。那根据我们在内部的一个调研,也发现大部分的包都是在 4k 以下的,所以我们可以绑定一块大小为 4k 左右的一个缓冲区,这样对内存的压力也不是很大。那这个还那我们再设计接那我们这个再设计接口。那首先需要一个我在读的时候让读指针不动,我下次还能够在这里进行读,也就是 Peek;以及说我们既然就能够让读指针不动,那我们就需要一个接口,让读指针进行一个移动,也就是 Discard。最后呢我们还需要回收这块内存,希望下一次请求能够复用之前的空间,也就是 Release 接口。
netpoll
- netpoll 存下全部Header 拷贝出完整的Body
- netpol | with nocopy peek 分配足够大的buffer 限制最大buffer size
- go net 流式友好 小包性能高
- netpoll 中大包性能高 时延低
hearder解析
找到Header Line 边界:\r \n 先找到\n再看它前一个是不是\r
针对协议相关的Headers 快速解析:
- 通过Header key首字母快速筛除掉完全不可能的key
- 解析对应value到独立字段
- 使用byte slice管理对应header 存储,方便复用
- 取 核心字段快速解析。使用byte slice存储。额外存储到成员变量中
- 舍 普通header 性能较低。没有map 结构
hearder key规范
aaa-bbb->Aaa-Bbb
- 取 超高的转换效率比net. http提高40倍
- 舍 额外的内存开销变更困难
热点资源池化
与请求一-对应,贯穿一个请求始终
- 取 减少了内存分配,提高了内存复用,降低了GC压力 性能提升
- 舍 额外的Reset逻辑 请求内有效问题定位难度增加