一、走进HTTP协议 HTTP:超文本 传输 协议(Hypertext Transfer Protocol)
1.1、为什么需要协议 需要明确知道数据明确的边界。
一个常见的POST请求在协议层做了什么? 请求行,是由请求方法 + URL +协议版本 组成。
状态行,由协议版本 + 状态码 + 状态码描述
请求/响应头:协议约定 + 业务相关:
协议相关:Content-Type、Content-Length 业务相关:自己定义 HTTP1: 队头阻塞: 传输效率低 明文传输不安全 HTTP2: 多路复用 头部压缩 二进制协议 QUIC:
基于UDP实现 解决队头阻塞 加密减少握手次数 支持快速启动 二、HTTP 框架的设计与实现
2.1、应用层设计 Application 提供合理的API
可理解性:如ctx.Body(), 不要ctx.BodyA() 简单性:如ctx.Request.Header.Peek(key),包装一下:/ctx.GetHeader(key) 冗余性 兼容性 可测性 可见性 2.2、中间件需求 配合Handler实现一个完整的请求处理生命周期。 拥有预处理逻辑与后处理逻辑:可统计业务耗时 可以注册多个中间件 对上层模块用户逻辑块易用 经典模型:洋葱模型
适用场景:
日志记录 性能统计 安全控制 事务处理 异常处理 2.3、路由设计 框架路由实际上是为了匹配URL对应的处理函数(Handlers)
静态路由:/a/b/c、/a/b/d 参数路由:/a/:id/c、/*all 路由修复:/a/b -> /a/b/ 冲突路由以及优先级:/a/b、/:id/c 匹配HTTP方法 多处理函数:方便添加中间件 一般路由使用前缀树来设计:
那么,如何匹配HTTP方法呢? 其实可以通过构建多个前缀树:
如何实现为一个路由添加多个处理函数?
在每个节点上使用一个list存储handler
node struct{ prefix string, parent *node, children children. handlers app.HandlersChain } 2.4、协议层设计 需要抽象出来合适的接口:
type server interface{ Serve(c context.Context, conn network.Conn) err } 遵循下列规范:
不要将Context存储在接口中,放在第一个参数中传递 在Conn上读写数据 2.5、网络层设计 网络层分为阻塞式IO 与 非阻塞式IO
阻塞式IO:
传统的 Go net是阻塞式的
type Conn interface { Read(b []byte) (n int, err error) Write(b []byte) (n int, err error) // ... } go func() { for { conn, _ := listener.Accept() go gunc() { conn.Read(request)
// handler
conn.Write(response);
}()
}
}() 阻塞式IO的问题在于,Read的时候,没有数据会阻塞, Write的时候,没有要写的也会阻塞。
非阻塞式IO:
Netpoll就实现了非阻塞式IO,
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 } go func() { for { readableConns, _ := Monitor(conns) // 创建一个连接监视器,有数据了再去处理 for conn := range readableConns { go gunc() { conn.Read(request) // handler conn.Write(response); }() } } }() 三、性能修炼之道 3.1、针对网络库的优化 go net -> Netpoll
为每一个连接都绑定一个buf
采用链表实现无锁化
3.2、针对协议的优化 Header解析 热点资源池化
有点类似于 线程池。