go语言HTTP协议 | 青训营

65 阅读4分钟

1HTTP框架

1.1HTTP协议

HTTP: 超文本传输协议(Hypertext Transfer Protocol)

作用

  1. 需要明确的边界
  2. 能够携带信息

协议内容

  • 请求行

    • 方法名
    • URL
    • 协议版本
  • 状态行

    • 协议版本
    • 状态码
    • 状态码描述
  • 请求头

    • 协议约定
    • 业务相关
  • 响应头

    • 协议约定
    • 业务相关
  • 请求体/响应体

常见方法名 GET、HEAD、POST、PUT、DEL ETE、CONNECT、OPTIONS、TRACE、PATCH

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

请求流程

不足

  • HTTP1

    • 队头阻塞
    • 传输效率低明文
    • 传输不安全
  • HTTP2

    • 多路复用
    • 头部压缩
    • 二进制协议
  • QUIC

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

1.2HTTP框架设计与实现

分层设计

  1. 专注性

  2. 扩展性

  3. 复用性

  4. 高内聚低耦合

  5. 易复用

  6. 高扩展性

应用设计

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

    不要用ctx. BodyA ()

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

    /ctx. GetHeader (key)

  1. 冗余性
  2. 兼容性
  3. 可测性
  4. 可见性

中间件设计

需求

  1. 配合Handler 实现一个完整的请求处理生命周期
  2. 拥有预处理逻辑与后处理逻辑
  3. 可以注册多中间件
  4. 对上层模块用户逻辑模块易用
洋葱模型

适用场景

  1. 日志记录
  2. 性能统计
  3. 安全控制
  4. 事务处理
  5. 异常处理
调用链

适用场景

  1. 不调用Next: 初始化逻辑且不需要在同一调用栈
  2. 调用Next: 后处理逻辑或需要在同一调用栈上

路由设计

  • 静态路由: /a/b/c、/a/b/d
  • 参数路由: /a/:id/c (/a/b/c, /a/d/c)、 /*alI
  • 路由修复: /a/b <-> /a/b/
  • 冲突路由以及优先级: /a/b、 /:id/c
  • 匹配HTTP方法
  • 多处理函数:方便添加中间件
处理路由
  • 青铜: map [str ing]handlers /a/b/c、/a/b/d /a/:id/c、/*alI
  • 黄金:前缀匹配树 /a/b/c、/a/b/d

协议层设计

抽象出适合的接口
type Server interface {
    Serve(c context.Contextconn network.Conn) error
}

网络层

image-20230728212701506转存失败,建议直接上传图片文件

BIO
go func() {
    for {
        conn := listener.Accept
        go func(){
            conn. Read(request)
            handle...
            conn. Write(response) 
        }()
    }
}()
NIO
go func() {
    for {
        readableConns, _ := Monitor( conns )
        for conn := range readableConns {
            go func(){
                conn. Read(request)
                handle...
                conn. Write(response) 
            }()
        }
    }
}()
//BIO
//用户管理库
type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error )
}
//NIO
//网络管理库
type Reader interface {
    Peek(n int) ([]byte, error)
}
type Writer interface {
    Malloc(n int) (buf []byte,err error )
    Flush() error
}
type Conn interface {
    net.Conn
    Reader
    Writer
}

1.3性能优化

网络库

go net
  • go net

    存下全部Header 减少系统调用次数 能够复用内存 能够多次读

  • go net with bufio

    绑定块缓冲区

type Reader interface {
    Peek(n int) ([]byte, error)
    Discard(n int) (discarded int, err error )
    Release() error
    Size() int
    Read(b []byte) (l int, err error)
}
type Writer interface {
    Write(p []byte)
    Size() int
    Flush() error
}

那基于此,我们可以对勾在勾标准库的接口上面封装一层 buffer 说也就是用一块用一个常用的一种优化手段。就是绑定在这个连接上面,绑定一块缓冲区。那根据我们在内部的一个调研,也发现大部分的包都是在 4k 以下的,所以我们可以绑定一块大小为 4k 左右的一个缓冲区,这样对内存的压力也不是很大。那这个还那我们再设计接那我们这个再设计接口。那首先需要一个我在读的时候让读指针不动,我下次还能够在这里进行读,也就是 Peek;以及说我们既然就能够让读指针不动,那我们就需要一个接口,让读指针进行一个移动,也就是 Discard。最后呢我们还需要回收这块内存,希望下一次请求能够复用之前的空间,也就是 Release 接口。

netpoll
  • netpoll 存下全部Header 拷贝出完整的Body
  • netpol | with nocopy peek 分配足够大的buffer 限制最大buffer size
  1. go net 流式友好 小包性能高
  2. netpoll 中大包性能高 时延低

hearder解析

找到Header Line 边界:\r \n 先找到\n再看它前一个是不是\r

针对协议相关的Headers 快速解析:

  1. 通过Header key首字母快速筛除掉完全不可能的key
  2. 解析对应value到独立字段
  3. 使用byte slice管理对应header 存储,方便复用
  • 取 核心字段快速解析。使用byte slice存储。额外存储到成员变量中
  • 舍 普通header 性能较低。没有map 结构

hearder key规范

aaa-bbb->Aaa-Bbb

  • 取 超高的转换效率比net. http提高40倍
  • 舍 额外的内存开销变更困难

热点资源池化

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

  • 取 减少了内存分配,提高了内存复用,降低了GC压力 性能提升
  • 舍 额外的Reset逻辑 请求内有效问题定位难度增加