第4课笔记总结:HTTP框架设计与优化|青训营

105 阅读4分钟

class 4-1

http框架修炼之道

前端和后端通过http请求交流

http:Hypertext Transfer Protocol 超文本传输协议

明确的边界:协议开始 -- text --协议结束

携带信息的类型: 协议开始 -- 协议元数据 -- text --协议结束

POST /sis HTTP/1.1   //请求行
Who: Alex   
Content-Type: text/plain
Host: 127.0.0.1:8888
Content-length: 28  //协议元数据/请求头

Let's watch a movie together  //文本/请求体
    //换行符,标志协议结束

Server回复/响应协议:

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: 1650541592984588  //响应头

OK  //响应体

请求行:方法名(常用方法名:GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH)、URL、协议版本

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

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

HTTP不足与展望

  • HTTP1:基于TCP,队头阻塞,传输效率低,明文传输不安全
  • HTTP2:基于TCP,多路复用,头部压缩,二进制协议
  • QUIC:基于UDP实现,解决队头阻塞,加密减少握手次数,支持快速启动

class4-2:HTTP框架的设计与实现

分层设计:专注性、扩展性、复用性。专注于特定层的设计,直接使用下层提供的接口

传输层:TCP,UDP

应用层:HTTP

TCP/IP四层概念模型:应用层,传输层,网络层,数据链路层

应用层(抽象请求) -- 中间件层(预处理/后处理逻辑) -- 路由层(注册/寻址) -- 协议层(通过接口,完成协议扩展) -- 网络层(灵活替换网络库)

应用层设计

提供合理的API: 可理解性、简单性、冗余性、兼容性、可测性、可见性

中间件设计

中间件需求:

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

经典中间件模型:洋葱模型(将核心逻辑与通用逻辑分离)

Request -- 日志(预处理) -- Metrics(预处理) -- Biz Handler(执行业务逻辑) -- Metrics(后处理) -- 日志(后处理) -- Response

路由设计

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

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

青铜:map[string]handlers,只适配静态路由

黄金:前缀匹配树,可以满足静态路由/参数路由

匹配HTTP方法:路由映射表:Map -- Method(string) -- 前缀树 -- 头节点(*node)

外层Map:通过Method初步筛选HTTP方法

实现添加多处理函数:在每个节点上使用list存储handler

如何设计路由

  • 明确需求
  • 业界调研
  • 方案权衡
  • 方案评审
  • 确定开发

协议层设计

抽象出合适的接口;

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

传输层:网络层设计

BIO:blockIO,用户管理buffer,go.net

go func() {
        for {
        conn, - := listener.Accept()
        go func() {
            conn.Read(request)
            
            handle...
            
            conn.Write(response)
        }()
    }
}()

NIO:注册监听器,监听到足够数据后唤醒,解决堵塞,网络库管理buffer,go.netpoll

go func() {
        for {
        readableConns, _ := Monitor(conns)
        for conn:= range readableConns {
        go func() {
            conn.Read(request)
            
            handle...
            
            conn.Write(response)
        }()
    }
}()

class4-3: 性能修炼之道

针对网络库的优化:buffer设计

  • go net:流式友好(需要用户手动调用),小包性能高 需求:存下全部Header,减少系统调用次数,能够复用内存,能够多次读

  • go net with bufio:绑定一块缓冲区(一般是4k,若超过,则需申请额外的buffer,并需完成buffer的回收)

  • netpoll:存下全部Header,拷贝出完整的Body:中大包性能高,时延低

  • netpoll with nocopy peek:分配足够大的buffer,限制最大 buffer size

针对协议的优化 - Headers解析:headers没有长度,需要进行解析

找到Header Line边界:\r\n 先找到\n,再看前面是不是\r

SIMD: 多数据流技术,可以用一组指令对多组数据进行变层操作

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

  • 通过Header key首字母快速筛除掉完全不可能的KEY
  • 解析对应value到独立字段
  • 使用byte slice管理对应header存储,方便使用

Header key 规范化 aaa-bbb --> Aaa-Bbb

热点资源池化

RequestContext:池,应用于高并发场景,Request来,从池子拿出Request Context,Response之后,再放回池子,下一次请求可以复用,减少Runtime压力

优势:减少内存分配,提高内存复用,降低GC压力,性能提升

劣势:额外的Reset逻辑,请求内有效(生命周期),问题定位难度增加

企业实践:框架设计

  • 追求性能
  • 追求易用,减少误用
  • 打通内部/外部生态
  • 文档建设,用户群建设

内部HTTP框架:Hertz