Http协议框架 | 青训营笔记

42 阅读4分钟

http协议

再谈http协议

HTTP协议

超文本(jpg、mp3、avi)传输协议

为什么需要协议?

byteDance13-2.png

  1. 需要明确的边界

    -开始

    -结束

  2. 能够携带信息

    -什么消息

    -消息类型

协议有什么

byteDance13-3.png

byteDance13-4.png

请求流程

byteDance13-5.png

不足与展望

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

分层设计

专注性

扩展性

复用性

byteDance13-6.png

设计框架需要满足

  • 高内聚 低耦合

  • 易复用

  • 高扩展性

byteDance13-7.png

应用层设计

提供合理的API

  • 可理解性:如ctx.Body(),ctx.GetBody(),不要使用ctx.BodyA()
  • 简单性:如ctx.Reequest.Header.Peek(key),/ctx.GetHeader(key)
  • 冗余性
  • 兼容性
  • 可测性
  • 可见性

中间件设计

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

洋葱模型:

byteDance13-8.png

byteDance13-9.png

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

    func Middware(params) {
        // some logic for pre-handle
        Next()
        // some logic for after-handle
    }
    
  2. 路由上可以注册多Middleware,同时也可以满足请求级别有效,只需要将Middleware设计为和业务handler相同即可。

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

    func (ctx *RequestContext) Next() {
        ctx.index ++ 
        for ctx.index < int8(len(ctx.handlers)){
            ctx.handlers[ctx.index]()
            ctx.index++
        }
    }
    

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

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

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

调用链:

byteDance13-10.png 适用场景:

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

路由设计

byteDance13-11.png

青铜:

map[string]handlers

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

黄金:

前缀匹配树(/a/b/c /a/b/d)

byteDance13-12.png

对于参数路由处理方法:

byteDance13-13.png

如何匹配HTTP方法?

byteDance13-14.png

如何实现多处理函数?

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

node struct {
    prefix string
    parent *node
    children children]
    handlers app.HandlersChain
}

如何做设计

  1. 明确需求:考虑清楚要解决哪些问题、有哪些需求
  2. 业界调研:业界都有哪些解决方案可供参考
  3. 方案权衡:思考不同方案的取舍
  4. 方案评审:相关同学对不同方案做评审
  5. 确定开发:确定最合适的方案进行开发

协议层设计

抽象出合适的接口

type Server interface {
    Serve(c context.Context,conn network.Conn) error
}
  1. 不要把context放到结构体中,而是通过函数传递的第一个参数进行传递
  2. 需要在连接上读写数据

网络层设计

同步IO和异步IO编程

byteDance13-15.png 下面分别是用同步IO和异步IO实现的两种通信方式:

byteDance13-16.png

byteDance13-18.png

定义接口如下:

type Conn interface {
    net.Conn
    Reader
    Writer
}

性能修炼之道

针对网络库的优化:

go net 使用"BIO"模型:

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

希望优化的地方:

  1. 存下全部Header
  2. 减少系统调用次数
  3. 能够复用内存
  4. 能够多次读

解决方法:

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 {
	Writer(p []byte)
	Size() int
	Flush() error
	...
}

netpoll:

  1. 存下全部Header
  2. 拷贝出完整的Body

byteDance13-20.png

解决方法:

netpoll with nocopy peek (分配足够大的buffer 限制最大buffer size)

byteDance13-19.png

type Conn interface{
    net.Conn
    Reader
    Writer
}

对比:

go netnetpoll
流式友好中大包性能高
小包性能高时延低

针对协议的优化

找到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技术更能提高性能

byteDance13-21.png

使用前后使用benchmark对比:

byteDance13-22.png

Headers解析

byteDance13-23.png

核心字段快速解析普通header性能较低
使用byte slice存储没有map结构
额外存储到成员变量中

header key 规范化

byteDance13-24.png

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

热点资源池化

byteDance13-25.png

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

企业实战

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