HTTP框架 | 青训营

71 阅读4分钟

image.png

1 HTTP协议

1.1 HTTP协议

http:超文本传输协议(Hypertext Transfer Protocl)

发展:

1本机使用

image.png 2局域网使用

image.png 3需要更多的传输内容和方式

image.png

为什么需要协议?

需要明确的边界:

-开始

-结束

能够携带信息:

-什么消息

-消息类型

-...

image.png

1.2 协议里有什么

一个常见的POST请求在协议层究竟做了什么?

POST /sis HTTP/1.1     //格式POST URL HTTP/1.1(对当前版本的描述)
Who: Alex              //协议元数据
Content-Type: text/plain
Host: 127.0.0.1:8888
Context-Length: 28     //数据的字节总数

Let's watch a movie together         //包的部分
\n\n

回复:

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

总结格式:

请求行/状态行            
请求头/响应头
请求体/响应体

第一行:

请求行:方法名 URL 协议版本

状态行:协议版本 状态码 状态码描述

方法名:GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH

状态码:1XX:信息类;2XX:成功;3XX:重定向;4XX客户端错误;5XX:服务端错误。

第二行~第N行:

协议约定: Content-Length

业务相关: 自己定义

1.3 请求流程

image.png

1.4 不足与展望

HTTP1:队头阻塞、传输效率低、明文传输不安全

HTTP2:多路复用、头部压缩、二进制协议

QUIC:基于UDP实现、解决队头阻塞、加密减少握手次数、支持快速启动

2 HTTP框架的设计与实现

2.1 分层设计

专注性、扩展性、复用性

image.png

高内聚、低耦合、高扩展性

image.png

image.png

2.2 应用层设计

提供合理的API:

可理解性:如ctx.Body(),ctx.GetBody().

不要用ctx.BodyA()

简单性:如ctx.Request.Header.Peek(key)

/ctx.GetHeader(key)

冗余性

兼容性

可测性

可见性

2.3 中间件设计

中间件需求:

配合Hander实现一个完整的请求处理生命周期

拥有预处理逻辑与后处理逻辑

可以注册多中间件

对上层模块用户逻辑模块易用

中间件洋葱模型

image.png

核心逻辑与通用逻辑分离

使用场景:日志记录、性能统计、安全控制、事务处理、异常处理

1.既然要实现预处理和后处理,那这个就很像调用了一个函数

func Middleware(some param){
    //some logic for pre-handle
    //...
    
    nextMiddleware() / bizLogic()
    //Next()
    //some logic after-handle
    //...
}

2.路由上可以注册很多Middleware,同时也可以满足请求级别有效只需要将Middleware设计为业务和Hander相同即可。

3.用户如果不主动调用下一个处理函数怎么办?

image.png

核心:在任何场景下保持递增

4.出现异常想停止怎么办?

func (ctx *RequestContext) Abort(){
    ctx.index = IndexMax
}

调用链:

image.png 使用场景:

不调用Next:初始化逻辑且不需要在同一调用栈

调用Next:后处理逻辑或需要再同一调用栈上

2.4 路由设计

框架路由实际上就是为了URL匹配对应的处理函数(Handers)

静态路由:/a/b/c、/a/b/d
参数路由:/a/:id/c (/a/b/c, /a/b/c)、/*all
路由修复:/a/b <-> /a/b/
冲突路由以及优先级:/a/b、/:id/c
匹配HTTP方法
多处理函数:方便添加中间件
....

青铜:map[string]handers

/a/b/c、/a/b/d        /a/:id/c、/*all

黄金:前缀匹配数

/a/b/c、/a/b/d

image.png 如何处理带参数的路由注册? (处理形如:/a/:id/b类型的路由)

image.png

如何匹配HTTP方法?

image.png

外层Map:根据method进行初步筛选

实现添加多处理函数:

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

image.png

2.5 协议层

抽象出适合的接口:

type Server interface {
    Serve(c context.Context,conn network.Conn) error
}

2.6 网络层设计

BIO

image.png NIO

做一个监听器:当数据达到足够多时再去读

image.png

go net "BIO"

用户管理 buffer

type Conn interface {
    Read(b []byte) (n int,err error)
    Write(b []byte) (n int, err error)
    ...
}

netpoll

NIO

网络库管理 buffer

netpoll地址:github.com/cloudwego/n…

image.png image.png

3 性能修炼

3.1 针对网络库优化

go net

存下全部的Header

减少系统调用次数

能够复用内存

能够多次读

go net with bufio

绑定一块缓冲区

image.png netpoll

存下全部Header

拷贝出完整的Body

netpoll with nocopy peek

分配出足够大的buffer

限制最大 buffer size

image.png

不同网络库优势:

go net:流式友好、小包性能高

netpoll:中大包性能高、时延低

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

找到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

SIMD库: image.png

SIMD能够加速Go的编译速度,通过下面的Benchmark可以看出:

image.png

Header的取舍:

核心字段快速解析

使用byte slice存储

额外存储到成员变量中

普通header性能较低

没有map结构:

3.3 Header key规范化

aaa-bbb ——> Aaa-Bbb

ToUpperTable[b[i]]

image.png

超高的转换效率

比net.http提高40倍

额外的内存开销

变更困难

3.4 热点资源池化

image.png

与请求一一对应,贯穿一个请求始终

image.png image.png

减少了内存分配

提高了内存复用

降低了GC压力

性能提升

额外的Reset逻辑

请求内有效

问题定位难度增加

4 企业实践

追求性能

追求易用,减少误用

打通内部生态

文档建设,用户群建设

—————————————————————

内部 HTTP 框架:Hertz

1万+服务 3千万+QPS