这是我参与「第三届青训营 -后端场」笔记创作活动的的第6篇笔记
01 再谈HTTP协议
1.1 HTTP协议是什么
超文本传输协议:超体现在不仅能传输文字还能传输图片,视频和连接。
为什么要有协议? 协议就是一个说话方式的规定,大家按这个方式交流,就可以相互接收到消息了。
1.2 协议里面有什么
请求行、请求头、请求体
put方法和patch方法的区别:put的语义是完整更新,patch是部分更新,并且put是幂等的,而patch不是幂等的
1.3 请求的流程
1.4 不足与展望
- HTTP1
- 队头阻塞
- 传输效率低
- 明文传输不安全
- HTTP2
- 多路复用
- 头部压缩
- 二进制协议
- QUIC
- 基于udp实现
- 解决队头阻塞的问题
- 加密减少握手次数
- 支持快速启动
02 HTTP框架的设计与实现
2.1 分层设计
在分层之后,大家只需要使用下一层提供的接口,专注于特定层的开发就可以了。并且分层框架可以让我们更容易做横向扩展。并且分层设计也可以做到很高的复用。
- 专注性
- 扩展性
- 复用性
- 高内聚低耦合
- 易复用
- 高扩展性
2.2 应用层设计
提供合理的API
- 可理解性
- 简单性
- 冗余性
- 兼容性
- 可测性
- 可见性
2.3 中间层设计
中间件需求:
- 配合handler实现一个完整的请求处理生命周期
- 拥有预处理逻辑和后处理逻辑
- 可以注册多个中间件
- 对上层模块用户逻辑模块易用
洋葱模型
适用场景:
- 日志记录
- 性能统计
- 安全控制
- 事务处理
- 异常处理
- 既然要实现预处理和后处理,那这个就很像调用了一个函数
2. 路由上可以注册多个middkeware,同时也可以满足请求级别有效,只需要将middleware设计为和handler形同即可。
3. 用户如果不主动调用下一个处理函数怎么办?(核心:在任何场景下保证index递增)
4. 出现异常想停止怎么办?
调用链
适用场景:
- 不调用next 初始化逻辑且不需要在同一调用栈
- 调用next 后处理逻辑或需要在同一调用栈上
2.4 路由设计
框架路由其实是为url匹配对应的处理函数(handler)
- 静态路由:a/b/c a/b/d
- 参数路由: a/:id/c
- 路由修复: /a/b/ <-> /a/b
- 冲突路由以及优先级
- 匹配HTTP方法
- 多处理函数:方便添加中间件
青铜 : map[string]handlers
黄金:前缀匹配数(如何处理带参数的路由注册?)
如何匹配HTTP方法?
如何实现添加多个处理函数? 在每个节点上使用一个list存储handler
2.5 协议层设计
抽象出合适的接口:
Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first paramter.
需要在连接上读写数据
2.6 网络层设计
- BIO
- NIO
go net bio用户用户管理buffer
netpoll nio 网络库管理buffer
03 性能修炼之道
3.1 针对网络库优化
go net 存下全部header 减少系统调用次数 能够复用内存 能够多次读
go net with bufio 绑定一块缓冲区,因为大部分时候包都是在4k以下,所以可以给每一个链接绑定一个4k大小的缓冲区,然后再提供一些方法来操作这个缓冲区。
peek不移动指针,discard移动指针,release复用内存
go net 流式友好,小包性能高 netpoll 中大包性能高,时延低
3.2 针对协议的优化
3.2.1 Headers解析
找到header line边界 用SIMD
针对协议相关的Headers解析
- 可以通过首字母进行快速的过滤
- 解析对应value到独立字段
- 使用 byte slice进行header存储和管理,方便复用
取:
- 核心字段快速解析
- 使用byte slice存储
- 额外存储到成员变量中 舍:
- 普通header性能低下
- 没有map结构
3.2.2 Header key规范化
空间换时间的一个优化,把字母表提前存好。
取:
- 超高的转换效率
- 比net.http提高40倍
舍:
- 额外的内存开销
- 变更困难
3.3 热点资源池化
取:
- 减少了内存分配
- 提高了内存复用
- 降低了gc压力
- 性能提升
舍:
- 额外的Reset逻辑
- 请求内有效
- 问题定位难度增加
04 企业实践
- 追求性能
- 追求易用,减少误用
- 打通内部生态
- 文档建设、用户群建设