HTTP 框架修炼之道 | 豆包MarsCode AI刷题

161 阅读5分钟

针对网络库的优化


theme: smartblue

刚看到这个标题,我会想到学校设立的web信息课程,它是关于前端网页实现的。而课程是关于后端HTTP协议的,我还挺期待他们的联动的!

Look at this!

image.png

前后端通过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
  • 首先是请求行/状态行。
    • 请求行
      • 方法名:GETHEADPOSTPUTDELETECONNECTOPTIONSTRACEPATCH
      • 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()
}

请求流程

image.png

业务层提供框架、提供API。

不足与展望

对于HTTP1来说,它是基于TCP的,所以可能出现队头阻塞的问题。传输效率较低,因为上面的无用信息比较多。明文传输,不安全。

对于HTTP2来说,对比HTTP1它支持多路复用和头部压缩,可以减少头部数据量。且它使用二进制协议,解析起来更方便一些。但是由于它仍然是基于TCP,所以并没有解决队头阻塞的问题。

对于QUIC来说,它是基于UDP实现的,解决了队头阻塞问题。优化了加密算法,减少握手的次数。同时,它支持快速启动。

HTTP框架的设计与实现

分层设计

image.png

分层设计注重专注性、扩展性、复用性,只需要基于上一个接口进行设计即可,不需要关注底层代码如何开发。

image.png

我们HTTP框架应该采取分层设计,注重高内聚、低耦合,易复用和高扩展性。

应用层设计

应用层设计中,需要提供合理性的API。

  • 可理解性:例如我们要写ctx.Body(),不要用ctx.BodyA(),因为我们无法知道A是什么东西。
  • 简单性
  • 冗余性
  • 兼容性
  • 可测性:保证写的接口可测试。
  • 可见性

中间件设计(有点没懂)

中间件需要配合Handler实现一个完整的请求处理生命周期,拥有预处理伙计与后处理逻辑,可以注册多个中间件,且对于上层模块用户逻辑模块容易使用。

洋葱模型:

image.png

它可以将核心逻辑与通用逻辑分离。使用户日志记录、事务处理、异常处理等。

image.png

👆调用链

路由设计

是为URL匹配对应的处理函数(Handlers)

  • 静态路由:/a/b/c、/a/b/d
  • 参数路由:/a/:id/c、/*all 冒号形式的只匹配带冒号的中间的一段 星号形式的可以匹配星号后的任意一个路由
  • 路由修复:/a/b <-> /a/b/
  • 冲突路由以及优先级:/a/b、/:id/c
  • ...

map[string]handlers这种方式只能处理静态路由

对于参数路由,可以采取构建路由树:

image.png


如何实现添加多处理函数?

在每个节点上使用一个list存储handler

node struct {
    ...
    handlers app.HandlersChain
    ...
}

协议层设计

需要抽象出合适的接口

网络层设计

BIO:

image.png

如果读数据读到一半,卡住了,那就真的一直卡着。所以我们升级了一下,使用NIO。

需要用户去管理。


NIO:

image.png

注册了一个监听器,当监听器监听到有足够数据后,再进行唤醒,就不会出现堵塞问题。

由公司进行管理。

性能修炼之道

针对网络库的优化

网络库:go net 流式友好且小包性能高,netpoll 中打包性能高且时延低。

针对协议的优化 -- Headers 解析

找到Header Line边界为\r\n

优化后:先找到\n再看它的前一个是不是/r

常用的优化 -- 热点资源池化

image.png

但是,如果在高并发场景,我们对每一个请求都进行分配以及释放,那么会有比较大的压力。

image.png

我们可以创建一个RequestContext池,当请求来的时候从池子里面拿出对应的RequestContext,用完后再还回池子。这样内存压力降低了且提高内存的复用。但是需要额外的Reset逻辑。

参考资料

性能修炼之道