针对网络库的优化
theme: smartblue
刚看到这个标题,我会想到学校设立的web信息课程,它是关于前端网页实现的。而课程是关于后端HTTP协议的,我还挺期待他们的联动的!
Look at this!
前后端通过HTTP进行联系。
HTTP框架负责对HTTP请求的解析。通过路由选择对应的后端解析。
HTTP协议
HTTP协议是什么
HTTP:超文本传输协议。(Hypertext Transfer Protocol)
超文本就是包括文字、图片、音频、视频、文档等集一身的统称。
为什么需要协议?
协议需要给我们明确的边界,告诉我们什么时候开始、什么时候结束;以及它需要携带信息,如什么消息、消息类型等。
协议里面有什么
以post协议为例子,“我”发文给小姐姐请她看电影:
POST /sis HTTP/1.1 //对HTTP版本的描述
Who: Alex //接着的这几行以key-value对表示协议的元数据
Content-Type: text/plain
Host: 127.0.0.1:8888
Content-Length: 28
//大空行
Let's watch a movies together. //真正想说的话
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
- 首先是请求行/状态行。
- 请求行
- 方法名:
GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH。 - URL
- 协议版本
- 方法名:
- 状态行
- 协议版本
- 状态码:
1xx为信息类,2xx为成功,3xx为重定向,4xx为客户端错误,5xx为服务端错误。 - 状态码描述
- 请求行
- 接着是请求头/响应头。
- 最后是请求体/响应体。
以上面的例子做一个小小的demo:
package main
import {
"context"
"code.byted.org/middleware/hertz/pkg/app"
"code.byted.org/middleware/hertz/pkg/app/server"
}
func main() {
h := server.New();
h.POST("/sis", func(c context.Context, ctx *app.RequestContext) {
ctx.Data(200, "text/plain; charset=utf-8", []byte("OK"))
})
h.Spin()
}
请求流程
业务层提供框架、提供API。
不足与展望
对于HTTP1来说,它是基于TCP的,所以可能出现队头阻塞的问题。传输效率较低,因为上面的无用信息比较多。明文传输,不安全。
对于HTTP2来说,对比HTTP1它支持多路复用和头部压缩,可以减少头部数据量。且它使用二进制协议,解析起来更方便一些。但是由于它仍然是基于TCP,所以并没有解决队头阻塞的问题。
对于QUIC来说,它是基于UDP实现的,解决了队头阻塞问题。优化了加密算法,减少握手的次数。同时,它支持快速启动。
HTTP框架的设计与实现
分层设计
分层设计注重专注性、扩展性、复用性,只需要基于上一个接口进行设计即可,不需要关注底层代码如何开发。
我们HTTP框架应该采取分层设计,注重高内聚、低耦合,易复用和高扩展性。
应用层设计
应用层设计中,需要提供合理性的API。
- 可理解性:例如我们要写
ctx.Body(),不要用ctx.BodyA(),因为我们无法知道A是什么东西。 - 简单性
- 冗余性
- 兼容性
- 可测性:保证写的接口可测试。
- 可见性
中间件设计(有点没懂)
中间件需要配合Handler实现一个完整的请求处理生命周期,拥有预处理伙计与后处理逻辑,可以注册多个中间件,且对于上层模块用户逻辑模块容易使用。
洋葱模型:
它可以将核心逻辑与通用逻辑分离。使用户日志记录、事务处理、异常处理等。
👆调用链
路由设计
是为URL匹配对应的处理函数(Handlers)
- 静态路由:/a/b/c、/a/b/d
- 参数路由:/a/:id/c、/*all 冒号形式的只匹配带冒号的中间的一段 星号形式的可以匹配星号后的任意一个路由
- 路由修复:/a/b <-> /a/b/
- 冲突路由以及优先级:/a/b、/:id/c
- ...
map[string]handlers这种方式只能处理静态路由
对于参数路由,可以采取构建路由树:
如何实现添加多处理函数?
在每个节点上使用一个list存储handler
node struct {
...
handlers app.HandlersChain
...
}
协议层设计
需要抽象出合适的接口
网络层设计
BIO:
如果读数据读到一半,卡住了,那就真的一直卡着。所以我们升级了一下,使用NIO。
需要用户去管理。
NIO:
注册了一个监听器,当监听器监听到有足够数据后,再进行唤醒,就不会出现堵塞问题。
由公司进行管理。
性能修炼之道
针对网络库的优化
网络库:go net 流式友好且小包性能高,netpoll 中打包性能高且时延低。
针对协议的优化 -- Headers 解析
找到Header Line边界为\r\n
优化后:先找到\n再看它的前一个是不是/r
常用的优化 -- 热点资源池化
但是,如果在高并发场景,我们对每一个请求都进行分配以及释放,那么会有比较大的压力。
我们可以创建一个RequestContext池,当请求来的时候从池子里面拿出对应的RequestContext,用完后再还回池子。这样内存压力降低了且提高内存的复用。但是需要额外的Reset逻辑。