1 HTTP协议
1.1 HTTP协议
http:超文本传输协议(Hypertext Transfer Protocl)
发展:
1本机使用
2局域网使用
3需要更多的传输内容和方式
为什么需要协议?
需要明确的边界:
-开始
-结束
能够携带信息:
-什么消息
-消息类型
-...
1.2 协议里有什么
一个常见的POST请求在协议层究竟做了什么?
POST /sis HTTP/1.1 //格式POST URL HTTP/1.1(对当前版本的描述)
Who: Alex //协议元数据
Content-Type: text/plain
Host: 127.0.0.1:8888
Context-Length: 28 //数据的字节总数
Let's watch a movie together //包的部分
\n\n
回复:
HTTP/1.1 200 OK //
Server: hertz
Date: Thu, 21 Apr 2022 11:46:32 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 2
Upstream-Caught: 1650541592984580
OK
总结格式:
请求行/状态行
请求头/响应头
请求体/响应体
第一行:
请求行:方法名 URL 协议版本
状态行:协议版本 状态码 状态码描述
方法名:GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH
状态码:1XX:信息类;2XX:成功;3XX:重定向;4XX客户端错误;5XX:服务端错误。
第二行~第N行:
协议约定: Content-Length
业务相关: 自己定义
1.3 请求流程
1.4 不足与展望
HTTP1:队头阻塞、传输效率低、明文传输不安全
HTTP2:多路复用、头部压缩、二进制协议
QUIC:基于UDP实现、解决队头阻塞、加密减少握手次数、支持快速启动
2 HTTP框架的设计与实现
2.1 分层设计
专注性、扩展性、复用性
高内聚、低耦合、高扩展性
2.2 应用层设计
提供合理的API:
可理解性:如ctx.Body(),ctx.GetBody().
不要用ctx.BodyA()
简单性:如ctx.Request.Header.Peek(key)
/ctx.GetHeader(key)
冗余性
兼容性
可测性
可见性
2.3 中间件设计
中间件需求:
配合Hander实现一个完整的请求处理生命周期
拥有预处理逻辑与后处理逻辑
可以注册多中间件
对上层模块用户逻辑模块易用
中间件洋葱模型
核心逻辑与通用逻辑分离
使用场景:日志记录、性能统计、安全控制、事务处理、异常处理
1.既然要实现预处理和后处理,那这个就很像调用了一个函数
func Middleware(some param){
//some logic for pre-handle
//...
nextMiddleware() / bizLogic()
//Next()
//some logic after-handle
//...
}
2.路由上可以注册很多Middleware,同时也可以满足请求级别有效只需要将Middleware设计为业务和Hander相同即可。
3.用户如果不主动调用下一个处理函数怎么办?
核心:在任何场景下保持递增
4.出现异常想停止怎么办?
func (ctx *RequestContext) Abort(){
ctx.index = IndexMax
}
调用链:
使用场景:
不调用Next:初始化逻辑且不需要在同一调用栈
调用Next:后处理逻辑或需要再同一调用栈上
2.4 路由设计
框架路由实际上就是为了URL匹配对应的处理函数(Handers)
静态路由:/a/b/c、/a/b/d
参数路由:/a/:id/c (/a/b/c, /a/b/c)、/*all
路由修复:/a/b <-> /a/b/
冲突路由以及优先级:/a/b、/:id/c
匹配HTTP方法
多处理函数:方便添加中间件
....
青铜:map[string]handers
/a/b/c、/a/b/d /a/:id/c、/*all
黄金:前缀匹配数
/a/b/c、/a/b/d
如何处理带参数的路由注册?
(处理形如:/a/:id/b类型的路由)
如何匹配HTTP方法?
外层Map:根据method进行初步筛选
实现添加多处理函数:
在每个节点上使用一个list存储handler
2.5 协议层
抽象出适合的接口:
type Server interface {
Serve(c context.Context,conn network.Conn) error
}
2.6 网络层设计
BIO
NIO
做一个监听器:当数据达到足够多时再去读
go net "BIO"
用户管理 buffer
type Conn interface {
Read(b []byte) (n int,err error)
Write(b []byte) (n int, err error)
...
}
netpoll
NIO
网络库管理 buffer
netpoll地址:github.com/cloudwego/n…
3 性能修炼
3.1 针对网络库优化
go net
存下全部的Header
减少系统调用次数
能够复用内存
能够多次读
go net with bufio
绑定一块缓冲区
netpoll
存下全部Header
拷贝出完整的Body
netpoll with nocopy peek
分配出足够大的buffer
限制最大 buffer size
不同网络库优势:
go net:流式友好、小包性能高
netpoll:中大包性能高、时延低
3.2 针对协议的优化-Headers解析
找到Header Line 边界:\r\n
先找到\n再看它前一个是不是\r
func index(b []byte,c byte) int {
for i := 0; i < len(b); i++{
if b[i] == c {
return i
}
}
return -1
}
那能不能更快呢? SIMD
SIMD库:
SIMD能够加速Go的编译速度,通过下面的Benchmark可以看出:
Header的取舍:
取
核心字段快速解析
使用byte slice存储
额外存储到成员变量中
舍
普通header性能较低
没有map结构:
3.3 Header key规范化
aaa-bbb ——> Aaa-Bbb
ToUpperTable[b[i]]
取
超高的转换效率
比net.http提高40倍
舍
额外的内存开销
变更困难
3.4 热点资源池化
与请求一一对应,贯穿一个请求始终
取
减少了内存分配
提高了内存复用
降低了GC压力
性能提升
舍
额外的Reset逻辑
请求内有效
问题定位难度增加
4 企业实践
追求性能
追求易用,减少误用
打通内部生态
文档建设,用户群建设
—————————————————————
内部 HTTP 框架:Hertz
1万+服务 3千万+QPS