这是我参与「第三届青训营 -后端场」笔记创作活动的的第10篇笔记
课程背景
HTTP协议
HTTP协议是什么
HTTP:超文本传输协议(Hypertext Transfer Protocol)
为什么需要协议
需要明确的边界
- 开始
- 结束
能够携带信息
- 什么消息
- 消息类型
协议里面有什么
请求流程
不足与展望
HTTP 框架的设计与实现
分层设计
专注性
扩展性
复用性
高内聚低耦合
易复用
高扩展性
一个切实可行的复杂系统势必是从一个切实可行的简单系统发展而来的。从头开始设计的复杂系统根本不切实可行,无法修修补补让它切实可行。你必须由一个切实可行的简单系统重新开始。
--- 盖尔定律
应用层设计
提供合理的 API
- 可理解性:如ctx.Body (), ctx.GetBody(),
- 不要用ctx.BodyA()
- 简单性:如ctx.Request.Header.Peek(key)
- /ctx.GetHeader (key)
- 冗余性
- 兼容性
- 可测性
- 可见性
不要试图在文档中说明,很多用户不看文档
中间件设计
中间件需求:
- 配合Handler实现一个完整的请求处理生命周期
- 拥有预处理逻辑与后处理逻辑
- 可以注册多中间件
- 对上层模块用户逻辑模块易用
打印每个请求的request和 response
既然要实现预处理和后处理,那这个就很像调用了一个函数
路由上可以注册多Middleware,同时也可以满足请求级别有效,只需要将Middleware设计为和业务和 Handler相同即可。
用户如果不主动调用下一个处理函数怎么办?
出现异常想停止怎么办?
调用链
适用场景:
- 不调用Next:
- 初始化逻辑且不需要在同一调用栈
- 调用Next:
- 后处理逻辑或需要在同一调用栈上
路由设计
框架路由实际上就是为URL匹配对应的处理函数(Handlers)
- 静态路由: /a/b/c、 /a/b/d
- 参数路由: /a/:id/c (/a/b/c. /a/d/c)、 /*all
- 路由修复: /a/b <-> /a/b/
- 冲突路由以及优先级: /a/b、 /:id/c
- 匹配HTTP方法
- 多处理函数:方便添加中间件
青铜: map[string]handlers
/a/b/c、/a/b/d /a/:id/c、 /*all
黄金:前缀匹配树
/a/b/c、/a/b/d
如何处理带参数的路由注册?
(处理形如: /a/: id/b类型的路由)
如何匹配HTTP方法?
路由映射表
外层Map:根据method进行初步筛选
如何实现添加多处理函数? 在每个节点上使用一个list 存储handler
如何做设计
- 明确需求:考虑清楚要解决什么问题、有哪些需求
- 业界调研:业界都有哪些解决方案可供参考
- 方案权衡:思考不同方案的取舍
- 方案评审:相关同学对不同方案做评审
- 确定开发:确定最合适的方案进行开发
协议层设计
抽象出合适的接口
网络层设计
BIO
NIO
go get "BIO" 用户管理 buffer
netpoll NIO 网络库管理buffer
性能修炼之道
针对网络库的优化
go get
- 存下全部Header
- 减少系统调用次数
- 能够复用内存
- 能够多次读
go net
- 存下全部Header
- 减少系统调用次数
- 能够复用内存
- 能够多次读 go net with bufio
- 绑定一块缓冲区
netpoll
- 存下全部Header
- 拷贝出完整的Body
netpoll with nocopy peek
- 分配足够大的buffer
- 限制最大buffer size
不同网络库优势
针对协议的优化 -- Headers 解析
- 找到Header Line边界: \r\n
- 先找到\n再看它前一个是不是\r
针对协议相关的Headers 快速解析:
- 通过Header key 首字母快速筛除掉完全不可能的key
- 解析对应value 到独立字段 3.使用byte slice管理对应header 存储,方便复用
请求体中同样处理的Key: User--Agent、Content--Type、 Content--Length、 Connection、Transfer-Encoding