http协议
再谈http协议
HTTP协议
超文本(jpg、mp3、avi)传输协议
为什么需要协议?
-
需要明确的边界
-开始
-结束
-
能够携带信息
-什么消息
-消息类型
协议有什么
请求流程
不足与展望
HTTP1 | HTTP2 | QUIC |
---|---|---|
对头阻塞 | 多路复用 | 基于UDP实现 |
传输效率低 | 头部压缩 | 解决队头阻塞 |
明文传输不安全 | 二进制协议 | 加密减少握手次数 |
支持快速启动 |
分层设计
专注性
扩展性
复用性
设计框架需要满足
-
高内聚 低耦合
-
易复用
-
高扩展性
应用层设计
提供合理的API
- 可理解性:如ctx.Body(),ctx.GetBody(),不要使用ctx.BodyA()
- 简单性:如ctx.Reequest.Header.Peek(key),/ctx.GetHeader(key)
- 冗余性
- 兼容性
- 可测性
- 可见性
中间件设计
- 配合Handler实现一个完整的请求处理声明周期
- 拥有预处理逻辑与后处理逻辑
- 可以注册多中间件
- 对上层模块用户逻辑模块易用
洋葱模型:
-
既然要实现预处理和后处理,那这个就很像调用了一个函数
func Middware(params) { // some logic for pre-handle Next() // some logic for after-handle }
-
路由上可以注册多Middleware,同时也可以满足请求级别有效,只需要将Middleware设计为和业务handler相同即可。
-
用户如果不主动调用下一个处理函数怎么办?
func (ctx *RequestContext) Next() { ctx.index ++ for ctx.index < int8(len(ctx.handlers)){ ctx.handlers[ctx.index]() ctx.index++ } }
核心:在任何场景下index保持递增
-
出现异常想停止怎么办?
func (ctx *RequestContext) Abort() { ctx.index = IndexMax }
调用链:
适用场景:
- 不调用Next:初始化逻辑且不需要在同一调用栈
- 调用Next:后处理逻辑或需要在同一调用栈上
路由设计
青铜:
map[string]handlers
/a/b/c、/a/b/c /a/:id/c、/*all
黄金:
前缀匹配树(/a/b/c /a/b/d)
对于参数路由处理方法:
如何匹配HTTP方法?
如何实现多处理函数?
在每个节点上使用一个list存储handler
node struct {
prefix string
parent *node
children children]
handlers app.HandlersChain
}
如何做设计
- 明确需求:考虑清楚要解决哪些问题、有哪些需求
- 业界调研:业界都有哪些解决方案可供参考
- 方案权衡:思考不同方案的取舍
- 方案评审:相关同学对不同方案做评审
- 确定开发:确定最合适的方案进行开发
协议层设计
抽象出合适的接口
type Server interface {
Serve(c context.Context,conn network.Conn) error
}
- 不要把context放到结构体中,而是通过函数传递的第一个参数进行传递
- 需要在连接上读写数据
网络层设计
同步IO和异步IO编程
下面分别是用同步IO和异步IO实现的两种通信方式:
定义接口如下:
type Conn interface {
net.Conn
Reader
Writer
}
性能修炼之道
针对网络库的优化:
go net 使用"BIO"模型:
type Conn interface {
Read(b []byte) (n int,err error)
Write(b []byte) (n int,err error)
...
}
希望优化的地方:
- 存下全部Header
- 减少系统调用次数
- 能够复用内存
- 能够多次读
解决方法:
go net with bufio (绑定一块缓冲区)
type Reader interface {
Peek(n int) ([]byte,error)
Discard(n int) (discarded int ,err error)
Release() error
Size() int
Read(b []byte) (l int,err error)
...
}
type Writer interface {
Writer(p []byte)
Size() int
Flush() error
...
}
netpoll:
- 存下全部Header
- 拷贝出完整的Body
解决方法:
netpoll with nocopy peek (分配足够大的buffer 限制最大buffer size)
type Conn interface{
net.Conn
Reader
Writer
}
对比:
go net | netpoll |
---|---|
流式友好 | 中大包性能高 |
小包性能高 | 时延低 |
针对协议的优化
找到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技术更能提高性能
使用前后使用benchmark对比:
Headers解析
取 | 舍 |
---|---|
核心字段快速解析 | 普通header性能较低 |
使用byte slice存储 | 没有map结构 |
额外存储到成员变量中 |
header key 规范化
取 | 舍 |
---|---|
超高的转换效率 | 额外的内存开销 |
比net.http提高40倍 | 变更困难 |
热点资源池化
取 | 舍 |
---|---|
减少了内存分配 | 额外的Reset逻辑 |
提高了内存复用 | 请求内有效 |
降低了GC压力 | 问题定位难度增加 |
性能提升 |
企业实战
- 追求性能
- 追求易用,减少误用
- 打通内部生态
- 文档建设、用户群建设