HTTP协议
HTTP是应用层协议,用于前后端通信。他的Hypertext意思就是不止发送文本、而是能兼容图片、视频等格式文件的传输。
在协议中需要规定报文的边界(开始与结束两个特殊标记)、元数据、数据体。在HTTP中就是请求行、请求头和请求体。
- 请求行指明了method、url和协议及版本。
- 请求头给出一些辅助信息,比如Content-Length让接收方知道要拿多少数据、Content-Type让接收方知道解析成什么类型的数据等。
- 请求体就是我们实际要发的数据。
对应请求的响应也大致是这些,不过换成了状态行、响应头和响应体等
HTTP后端框架设计
如下图,左边是一个前后端通信的过程,右边是后端的分层结构。
这里主要想记录下中间件和路由、网络库部分。
中间件
这里的中间件不是说redis那些在项目之外独立运行的,而是在后端框架中类似插件的模块。可以参考Django和Gin、以及springboot的拦截器。这个是基于“洋葱模型”,即最里层是真正的后端模块,其外部通过层层的中间件,将请求和响应层层处理并转发或者拦截。
其实现,我大概理解为维护了一个中间件数组,框架会将请求和响应按数组顺序主动发给当前下标的中间件,如果通过,就发给index+1的中间件,不通过就直接让下标到数组末尾。这样也能在用户不主动调Next()时让请求正常通过。
视频还讲了一种用法,上面的多个中间件是串行的,像一个栈式(或链式)结构。我们可以通过goroutine异步调用其他不需要继续Next的中间件,比如初始化一些资源等操作就可以这样。
路由匹配
每个method对应一个树,每个树都是前缀树,框架在注册函数的路由时建树。
网络库
这里的网络库主要在区别BIO和NIO。这个例子很形象,看上去代码差不多,实际上原理差得远。
BIO:监听到来的请求,来一个就对他进行处理。监听和处理是串行。对应标准库net
而NIO就是一个监听请求线程和N个处理线程,每次收一个请求后开一个goroutine去做处理,然后继续监听。吞吐量会高一些。字节中采用netpoll。
优化
针对网络库
这里对于net和netpoll的特点分别做了优化,我没仔细了解所以听不太懂,大概意思是增加缓存来减少系统调用、存下全部header加速解析。
视频也指出了net和netpoll的优缺点:
- net:流式友好、小包性能高
- 中大包性能好、时延低
针对协议
行结束标记匹配
http协议使用\n\r作为行的结尾,为了获得边界我们要做字符串匹配。对于通用字符串的匹配常用的是朴素匹配和KMP,但考虑到只要针对这两个字符,而且出现频率低,直接搜索\r,再看前面是不是\n就行。时间复杂度是On,空间复杂度是O1,性能好。
进一步的,可以采用SIMD并行比较多个字符。
header解析
用map[首字母]筛选和查找核心header,舍弃了对一些普通header处理的性能。
协议规范化
规范化是一个常规操作,需要做字符大小写的转换。这里是讲的优化,其实就是提前做了一个字符映射表,比如toUpper['a']='A',这样就不需要通过'a'+32得到大写A了。
Context池化
在http框架中,每次请求都会一路带着一个Context,处理完之后又会销毁。这里和连接池是一个道理,不将Context直接回收,而是放在池里等待下次复用。
减少了内存分配和GC。但是需要对Context增加reset()操作
实践
- 追求性能的同时,要保持良好的易用性,减少误用的情况
- 建立框架的全套生态
- 建立完善的文档,维护活跃友好的用户群、社区环境