课程背景
Http是前端客户端与服务端通讯地基础协议之一
1.再谈 HTTP 协议
HTTP 协议是什么
HTTP: 超文本传输协议(Hypertext Transfer Protocol)
为什么需要协议
需要明确的边界:开始与结束 。描述携带信息的类型
协议里有什么
请求
回复
请求行:方法名,URL,协议版本
常见方法名包括:GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH,
请求头:协议约定,业务相关
状态行:
协议版本, 状态码, 状态码描述
状态码
1xX:信息类, 2xX:成功, 3xX:重定向, 4xX:客户端错误, 5xx:服务端错误
响应头:协议约定,业务相关
请求流程
业务层:使用框架提供的API处理业务逻辑
服务治理层:依托于中间件层, 中间件层对每一个请求有先后处理的逻辑,和请求级别绑定的
不足与展望
HTTP1:队头阻塞,
传输效率低,
明文传输不安全
HTTP2:多路复用,
头部压缩,
二进制协议
QUIC:基于UDP实现,
解决队头阻塞,
加密减少握手次数,
支持快速启动
2.HTTP 框架的设计与实现
分层设计
分层设计可以简化系统的设计。
优点:专注性, 扩展性, 复用性
http主要聚焦于第四层,
在弄分层设计时应该关注:高内聚, 低耦合 ,易复用, 高扩展性 这几点
Application:应用层,处理用户的数据,对请求进行抽象,提供API
middleware:中间件层,对用户有预处理,和后处理的逻辑
route:路由层,有原生的路由事件,类似于注册,寻址
codec:协议层
transport:传输层
应用层设计
提供合理的 API
可理解性: 如 ctx.Body(),ctx.GetBody(),不要用 ctx.BodyA()
简单性: 如 ctx.Request.Header.Peek(key)/ctx.GetHeader(key)
冗余性:不要有功能重复户的API,不要有一个API是由两个API组成的
兼容性:在开发中尤为重要,要保证所有的API可用
可测性:写出来的API是可测试的
可见性:首先是为了安全性,不让用户拿到核心的代码,方便使用人员
不要试图在文档中说明,很多用户不看文档
中间件设计
中间件需求
配合 Handler 实现一个完整的请求处理生命周期
拥有预处理逻辑与后处理逻辑
可以注册多中间件
对上层模块用户逻辑模块易用
核心逻辑与通用逻辑分离
洋葱模型
Request请求首先经过日志中间件的预处理,然后经过Metrics中间件的预处理,然后进行业务逻辑,之后分别在经过Metrics中间件的后处理,日志中间件的后处理,然后返回一个Response给用户。
适用场景:
日志记录,
性能统计,
安全控制,
事务处理,
异常处理
中间件设计
1.既然要实现预处理和后处理, 那这个就很像调用了一个函数。
2.路由上可以注册多 Middleware,同时也可以满足请求级别有效 只需要将 Middleware 设计为和业务和 Handler 相同即可。
3.用户如果不主动调用下一个处理函数怎么办
在任何场景下index保证递增
4.出现异常想停止怎么办
调用链
适用场景:
不调用 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 方法
多处理函数:方便添加中间件
前缀匹配树
/a/b/c、/a/b/d
如何处理带参数的路由注册?
(处理形如: /a/:id/b类型的路由)
/a/b/c
/a/b/d
/a/:b/d
/a/:c/f
匹配 HTTP 方法
外层 Map: 根据 method 进行初步筛选
如何实现添加多处理函数?
在每个节点上使用一个 list 存储handler
如何做设计
1 .明确需求: 考虑清楚要解决什么问题、有哪些需求
2,业界调研:业界都有哪些解决方案可供参考
3.方案权衡:思考不同方案的取舍
4.方案评审: 相关同学对不同方案做评审
5.确定开发:确定最合适的方案进行开发
协议层设计
抽象出合适的接口:
需要在连接上读写数据
网络层设计
BIO
NIO
no net
BI0, 用户管理 buffer
netpoll
NIO, 网络库管理 buffer
netpoll 地址
github.com/cloudwego/n…
AP设计:可理解性、简单性
中间件设计:洋葱模型
路由设计:前缀匹配树
协议层设计: 抽象出合适的接口
网络层设计: 网络模型
3.性能修炼之道
针对网络库的优化
**go net ** 用的都是BIO,让用户进行管理
go net with bufio 绑定一块缓冲区
那基于此,我们可以对勾在勾标准库的接口上面封装一层 buffer 说也就是用一块用一个常用的一种优化手段。就是绑定在这个连接上面,绑定一块缓冲区。那根据我们在内部的一个调研,也发现大部分的包都是在 4k 以下的,所以我们可以绑定一块大小为 4k 左右的一个缓冲区,这样对内存的压力也不是很大。那这个还那我们再设计接那我们这个再设计接口。那首先需要一个我在读的时候让读指针不动,我下次还能够在这里进行读,也就是 Peek;以及说我们既然就能够让读指针不动,那我们就需要一个接口,让读指针进行一个移动,也就是 Discard。最后呢我们还需要回收这块内存,希望下一次请求能够复用之前的空间,也就是 Release 接口。
netpoll
存下全部 Header, 拷贝出完整的 Body
netpoll with nocopy peek
分配足够大的 buffer, 限制最大 buffer size
针对网络库接口
不同网络库优势
go net:
流式友好
小包性能高
netpoll:
中大包性能高,
时延低
针对协议的优化
Headers 解析
找到 Header Line 边界: \r\n
先找到\n再看它前一个是不是\r
SIMD
Sonic: github.com/bytedance/s…
针对协议相关的 Headers 快速解析:
1.通过 Header key 首字母快速筛除掉完全不可能的 key
2.解析对应 value 到独立字段
3.使用 byte slice 管理对应 header 存储,方便复用
取:
核心字段快速解析,
使用byte slice存储,
额外存储到成员变量中,
舍:
普通 header 性能较低,
没有 map 结构
Header key 规范化
aaa-bbb -> Aaa-Bbb
取:超高的转换效率比 net.http提高 40倍
舍:
额外的内存开销,变更困难
热点资源池化
取:
减少了内存分配,
提高了内存复用,
降低了GC压力,
性能提升
舍:
额外的 Reset 逻辑,
请求内有效,
问题定位难度增加