HTTP框架及性能优化 | 豆包MarsCode AI刷题

60 阅读6分钟

HTTP框架及性能优化

Hypertext Transfer Protocol

协议内容概述

常见方法名

  • GETHEADPOSTPUTDELETECONNECTOPTIONSTRACEPATCH

请求和响应结构

  1. 请求行 / 状态行
    • 包含方法名、URL 和协议版本。
    • 响应中包含协议版本、状态码和状态码描述。
  2. 请求头 / 响应头
    • 协议约定的各类信息,业务相关的元数据。
  3. 请求体 / 响应体
    • 请求或响应中的具体数据内容。

状态码分类

  • 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()
}

请求流程

image.png

不足

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 快速解析方法

  1. 通过 Header key 首字母快速筛除:通过检查 Header key 的首字母来快速筛选,剔除不可能的 key,提升解析效率。
  2. 解析对应 value:将解析后的 value 存储到独立的字段中,便于后续处理。
  3. 使用 byte slice 管理 Header 存储:利用字节切片(byte slice)来存储和管理 Header 值,方便复用和内存管理。

请求体中常用的 Header key 列表

  • User-Agent
  • Content-Type
  • Content-Length
  • Connection
  • Transfer-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 树路由:通过前缀树进行路径层级匹配,高效支持动态路由和路径参数。