这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记
HTTP协议
HTTP协议是什么
Hypertext Transfer Protocol超文本传输协议
图片,音乐、视频、超链接等资源超出了文本范围
需要明确的边界,消息类型等
协议有什么-POST为例
请求行/状态行
请求头/响应体
请求体/相应体
常用方法名:GET\HEAD\POST\DELETE\CONNECT\OPTIONS\TRACE\PATCH
请求流程
不足和展望
HTTP1:
队头阻塞问题
传输效率低
明文传输不安全
HTTP2:
多路复用
头部压缩
二进制协议
QUIC:
基于UDP实现、解决队头阻塞
加密减少握手次数
支持快速启动
HTTP框架的设计与实现
高内聚、低耦合、易复用、高扩展性
应用层设计
提供合理的API
- 可理解性:如ctx.Body()
- 简单性:如ctx.GetHeader(key)而不是 ctx.Request.Header.Peek(key)
- 非冗余性:不要有接口冗余
- 兼容性
- 可测性:接口可测试
- 可见性:保证安全
中间件设计
需求
- 配合Handler实现一个完整的请求处理生命周期
- 拥有预处理逻辑与后处理逻辑
- 可以注册多中间件
- 对上层模块用户逻辑逻辑模块易用
设计
请求经过日志、metric、业务逻辑、metric、日志
- 实现预处理和后处理
- 路由上可以注册多Middleware,同时也可以满足请求级别有效只需要将Middleware设计为和业务和handler相同即可。
func Middleware (some param){
//pre-handle
...
Next()
...
//after-handle
}
- 帮助用户调用下一个处理函数
func (ctx *RequestContext) Next(){
ctx.index++
for ctx.index <int8(len(ctx.hanlders)){
ctx.handlers[ctx.index]()
ctx.index++
}
}
- 出现异常要停止
func(ctx *RequestContext) Abort(){
ctx.index = IndexMax
}
将index设为最大值,直接跳出循环
调用链:
路由设计
框架路由就是为URL匹配对应的处理函数(handlers)
静态路由:/a/b/c
参数路由:/a/:id/c /*all
路由修复: /a/b <->/a/b/
冲突路由以及优先级: /a/b /:id/c
匹配HTTP方法
多处理函数:方便添加中间件
- map[string]handlers但是不适合参数路由
- 前缀匹配树:
- 参数路由
如何匹配HTTP方法
多处理函数的处理
在每个节点上使用一个list存储handler
协议层设计
抽象出合适接口
网络层设计
BIO
NIO
go net
BIO 用户管理buffer
netpoll
NIO网路库管理buffer
性能优化
网络库优化
go net
存在全部header
减少系统调用次数
能够复用内存
能够多次读
go net with bufio
绑定一块缓冲区
优势
流式友好
小包性能高
netpoll
存在全部的header,拷贝出完整的Body
netpoll with nocopy peek
如下图,存在跨节点的问题
分配足够大的buffer
限制最大的buffer size
优势
中大包性能高
时延低
针对协议的优化-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,单指令多数据流,一次处理多个
核心字段快速解析、使用byte slice存储、额外存储到成员变量中
header key规范化
使用table存储大小写映射,O(1)复杂度即可转化大小写,比ASCII码加减效率提高40倍
,但是引入了额外的内存开销,且变更困难,两张表可能需要改
热点资源池化
Request Context池子,不同请求可以复用
减少了内存分配次数
降低GC压力
性能提升
但是引入额外的reset操作,防止前面请求对后面有影响
请求内有效
问题定位难度高