HTTP协议 | 青训营

50 阅读4分钟

HTTP协议

前后端分离框架

image-20230603155721377.png

前后端分离的框架需要使用http协议进行数据的传输

协议

HTTP:超文本传输协议

超文本:除了text外还可以传输视频音频

image-20230603160037370.png

http中需要确定开始和结束时间以及携带信息等,因此需要首先确定其结构

结构

image-20230603160145614.png

image-20230603160709864.png

PATCH方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新

局部更新: 假设我们有一个UserInfo,里面有userId, userName, userGender等10个字段。可你的编辑功能因为需求,在某个特别的页面里只能修改userName,这时候的更新怎么做? 人们通常(为徒省事)把一个包含了修改后userName的完整userInfo对象传给后端,做完整更新。但是比较浪费带宽

patch诞生,只传一个userName到指定资源去,表示该请求是一个局部更新,后端仅更新接收到的字段。

而put虽然也是更新资源,但要求前端提供的一定是一个完整的资源对象,理论上说如果你用了put,但却没有提供完整的UserInfo,那么缺了的那些字段应该被清空

请求头和响应头:有逻辑相关和业务相关,比如head就是逻辑相关,业务相关自己定义 (与数据有关)

demo

image-20230603160947338.png

给sis发送数据 需要返回OK的demo的代码,以下是实现代码

image-20230603160929011.png

请求流程

image-20230603161326360.png

route:根据对应的api选择执行的handler

不足

image-20230603161415002.png

http1

后面的分片需要等前面的分片到来才能发送数据(队头阻塞,TCP问题)

http2

二进制协议传输更加高效,但是没有解决队头阻塞的问题

分层设计

网络分层

image-20230603161735672.png

http框架分层

image-20230603161844632.png

层与层之间使用接口进行解耦

Application:抽象、提供丰富的应用api 和用户直接打交道

中间件层:实现预处理和后处理的逻辑

路由层:注册寻址相关的操作

协议层:有了协议的抽象接口可以实现协议的扩展

网络层:不同网络使用场景不同

common: 放一些公共的文件

应用层

image-20230603162538873.png

可见性:

  1. 安全性:不能随意更改接口
  2. 接口使用复杂

中间件设计

image-20230603162716383.png

经典中间件模型

image-20230603162753484.png

预处理:日志、metrics(通用逻辑)

业务逻辑处理(核心逻辑)

后处理:(通用逻辑)

中间件定义

image-20230603163006323.png

调用链

image-20230603163205422.png

路由设计

image-20230603163515435.png

静态路由构建

image-20230603163634889.png

一般使用匹配树构建

参数路由构建

image-20230603163714046.png

问题

image-20230603163837487.png

通过method进行筛选

image-20230603163852941.png

如何设计

image-20230603163911325.png

协议层设计

抽象出合适的接口

image-20230603164301134.png

  1. 不要把context(上下文传递)存储在struct中,而是要通过函数的第一个参数传递进来
  2. 返回值就行需要处理的数据

传输层

BIO和NIO

image-20230603164412632.png

bio 阻塞io,必须read完成之后在业务逻辑处理,需要用户管理buffer

image-20230603165027919.png

nio 注册一个监听器,坚挺到有足够的数据之后再进行业务逻辑处理,自动管理底层的buffer

image-20230603164740705.png

性能优化

网络库

主流操作系统都有网络支持,并且提供 C 语言接口的网络 API。

但是通常来说,这些网络 API 很底层,并不是很好用。举例来说,send() 函数只能传递 char* 对象过去,如果我是 std::string,或者 std::array<unsigned char,4096>(话说我就经常用这个做buffer),那就要自己转换了。而网络库往往会帮你做好这种封装,让你调用起来可以写更少的代码,代码里的逻辑不会因为调用了这些API而显得混乱。此外还能跨平台(原生网络API是不跨平台的,每个系统都要单独写一份),能使开发、维护都更有效率。

字节跳动网络库 netpollNetpoll 是一款 Go 语言高性能、I/O 非阻塞 (NIO) 网络库,专注于 RPC 场景。

go net问题

image-20230603165101198.png

优化

image-20230603165131109.png

大部分包都是在4k以下的,使用缓冲区进行优化

netpoll 问题

image-20230603165402951.png

采用链表的方式,实现buffer的无锁化,但是链表可能会导致跨节点的问题,如图header和body都不在同一个节点上,想要进行完整解析的话 需要先将两个header拼接并拷贝到一个完整的内存中才能解析

解决

image-20230603165858248.png

直接将节点buffer变大

比较

image-20230603165943926.png

协议优化

header解析

image-20230603170404624.png

可以使用SIMD进行加速优化, SIMD 的全称是 Single Instruction Multiple Data,中文名“单指令多数据”。顾名思义,一条指令处理多个数据。

image-20230603170336372.png

热点资源池化

image-20230603170629120.png

如果对每一个请求有一个一个requestcontext,那么压力比较大,所以维护了一个requestcontext池 有一个请求就从池中拿出来一个requestcontext

image-20230603170832111.png

image-20230603170842454.png

将requestcontext放回池子中 会有一些复杂的reset操作,因为需要进行复用

如果reset超出声明周期的话 可能会带来数据不一致的问题