HTTP框架及性能优化
Hypertext Transfer Protocol
协议内容概述
常见方法名
GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH
请求和响应结构
- 请求行 / 状态行
- 包含方法名、URL 和协议版本。
- 响应中包含协议版本、状态码和状态码描述。
- 请求头 / 响应头
- 协议约定的各类信息,业务相关的元数据。
- 请求体 / 响应体
- 请求或响应中的具体数据内容。
状态码分类
1xx:信息类2xx:成功3xx:重定向4xx:客户端错误5xx:服务端错误
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()
}
请求流程
不足
HTTP1
- 队头阻塞:请求和响应的顺序性导致阻塞。
- 传输效率低:每个请求需要新建连接,资源利用率低。
- 明文传输不安全:缺少加密机制,存在安全隐患。
HTTP2
- 多路复用:允许多请求并行发送,共享一个连接,避免队头阻塞。
- 头部压缩:减少头部信息的冗余,提升传输效率。
- 二进制协议:基于二进制传输,解析效率更高。
QUIC
- 基于 UDP 实现:使用 UDP 协议实现快速传输。
- 解决队头阻塞:独立的流控制,减少阻塞。
- 加密减少握手次数:内建加密机制,减少建立连接的耗时。
- 支持快速启动:实现了更高效的连接建立和数据传输。
HTTP设计
分层设计:高内聚,低耦合,易复用,高扩展性
应用层设计
提供合理的API,可理解/简单,减少冗余,兼容,可测可见 ctx.Body(), ctx.GetHeader(key)
中间件设计(洋葱模型)
- 配合 Handler 实现完整的请求处理生命周期:中间件与处理器协作,涵盖请求的预处理、处理、响应等全生命周期。
- 拥有预处理逻辑与后处理逻辑:中间件可以在请求到达处理器前执行预处理逻辑,在处理后执行后处理逻辑。
- 支持注册多个中间件:可以按顺序执行多个中间件,以便实现复杂的请求流程。
- 对上层模块友好:为用户模块提供便捷的接口,以方便用户在模块中调用或管理中间件。
中间件设计注意是否在同一个调用栈上
路由设计
- 静态路由:匹配具体路径,如
/a/b/c,/a/b/d。 - 参数路由:支持动态参数,例如
/a/:id/c,可匹配/a/b/c、/a/d/c,以及通配符/*all。(前缀匹配树) - 路由修复:允许小幅度的路径修正,如将
/a/b视为等同于/a/b/。 - 冲突路由及优先级:如
/a/b和/:id/c,按优先级处理匹配冲突。 - 匹配 HTTP 方法:如 GET、POST 等,确保不同方法的请求可以映射到不同的处理函数。
- 多处理函数支持:方便添加中间件,为路径添加前置或后置处理逻辑。
匹配HTTP方法
路由映射表:Map || Method || 前缀树 || 头节点
协议层设计
抽象出合适的借口
网络层设计
BIO:block IO (go net)
NIO: 注册监听器,然后唤醒(netpoll)
性能优化
针对网络库优化
go net: 存下全部Header,减少系统调用次数,能够服用内存,能够多次读 —>绑定一块缓冲区 || go net with bufio
netpoll: 存下全部Header,拷贝出完整的Body —> 分配足够大的buffer,限制最大的buffer size || go with nocopy peek
针对协议的优化
Headers 快速解析方法
- 通过 Header key 首字母快速筛除:通过检查 Header key 的首字母来快速筛选,剔除不可能的 key,提升解析效率。
- 解析对应 value:将解析后的 value 存储到独立的字段中,便于后续处理。
- 使用 byte slice 管理 Header 存储:利用字节切片(byte slice)来存储和管理 Header 值,方便复用和内存管理。
请求体中常用的 Header key 列表
User-AgentContent-TypeContent-LengthConnectionTransfer-Enoding
热点资源池化
Q&A
1. 为什么 HTTP 框架要进行分层设计?分层设计的优势与劣势。
- 优势:
- 模块化:每一层只关注自己特定的职责,易于维护和扩展。
- 解耦:分层设计隔离了业务逻辑和底层实现,使各模块独立发展,方便测试和调试。
- 重用性:通用的逻辑可以封装成独立模块,被多处使用。
- 劣势:
- 性能开销:分层增加了调用的栈深度,可能增加性能开销。
- 复杂性增加:更多的模块和接口设计增加了代码的复杂度,维护成本上升。
2. 现有开源社区 HTTP 框架的优势与不足。
- 优势:
- 活跃社区支持:开发者可以获取社区的支持,持续更新和问题解决。
- 插件生态丰富:提供大量的中间件和插件,扩展方便。
- 成熟性:具备丰富的功能和优化,适用于多种场景。
- 不足:
- 性能瓶颈:一些通用框架在性能上可能不如特定业务场景的专用框架。
- 难以定制:部分框架封装较多,导致修改和定制成本较高。
- 代码复杂度:大型框架代码量较大,理解和调试难度较高。
3. 中间件的其他实现方式示例(伪代码说明)。
- 实现方式:基于链式调用的中间件设计,利用递归或循环来逐步调用各中间件。
type Middleware func(Handler) Handler
func MiddlewareChain(handler Handler, middlewares ...Middleware) Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
4. 基于前缀路由树的注册与查找功能(伪代码说明)。
- 实现方式:前缀树节点存储路径的每一部分,支持逐层匹配和递归查找。
type Node struct {
path string
handler Handler
children map[string]*Node
}
func (n *Node) Insert(path string, handler Handler) {
parts := strings.Split(path, "/")
for _, part := range parts {
if _, ok := n.children[part]; !ok {
n.children[part] = &Node{path: part, children: make(map[string]*Node)}
}
n = n.children[part]
}
n.handler = handler
}
func (n *Node) Search(path string) Handler {
parts := strings.Split(path, "/")
for _, part := range parts {
if node, ok := n.children[part]; ok {
n = node
} else {
return nil
}
}
return n.handler
}
5. 路由的其他实现方式。
- 正则表达式路由:通过正则匹配 URL 路径,灵活性较高。
- 哈希表路由:对于固定路径,使用哈希表存储路径与处理器的映射,查找速度快,但不支持动态路径。
- 分段匹配路由:将路径分段,逐段匹配以实现动态路由和路径参数。
- Trie 树路由:通过前缀树进行路径层级匹配,高效支持动态路由和路径参数。