HTTP协议
前后端分离框架
前后端分离的框架需要使用http协议进行数据的传输
协议
HTTP:超文本传输协议
超文本:除了text外还可以传输视频音频
http中需要确定开始和结束时间以及携带信息等,因此需要首先确定其结构
结构
PATCH方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新
局部更新: 假设我们有一个UserInfo,里面有userId, userName, userGender等10个字段。可你的编辑功能因为需求,在某个特别的页面里只能修改userName,这时候的更新怎么做? 人们通常(为徒省事)把一个包含了修改后userName的完整userInfo对象传给后端,做完整更新。但是比较浪费带宽
patch诞生,只传一个userName到指定资源去,表示该请求是一个局部更新,后端仅更新接收到的字段。
而put虽然也是更新资源,但要求前端提供的一定是一个完整的资源对象,理论上说如果你用了put,但却没有提供完整的UserInfo,那么缺了的那些字段应该被清空
请求头和响应头:有逻辑相关和业务相关,比如head就是逻辑相关,业务相关自己定义 (与数据有关)
demo
给sis发送数据 需要返回OK的demo的代码,以下是实现代码
请求流程
route:根据对应的api选择执行的handler
不足
http1
后面的分片需要等前面的分片到来才能发送数据(队头阻塞,TCP问题)
http2
二进制协议传输更加高效,但是没有解决队头阻塞的问题
分层设计
网络分层
http框架分层
层与层之间使用接口进行解耦
Application:抽象、提供丰富的应用api 和用户直接打交道
中间件层:实现预处理和后处理的逻辑
路由层:注册寻址相关的操作
协议层:有了协议的抽象接口可以实现协议的扩展
网络层:不同网络使用场景不同
common: 放一些公共的文件
应用层
可见性:
- 安全性:不能随意更改接口
- 接口使用复杂
中间件设计
经典中间件模型
预处理:日志、metrics(通用逻辑)
业务逻辑处理(核心逻辑)
后处理:(通用逻辑)
中间件定义
调用链
路由设计
静态路由构建
一般使用匹配树构建
参数路由构建
问题
通过method进行筛选
如何设计
协议层设计
抽象出合适的接口
- 不要把context(上下文传递)存储在struct中,而是要通过函数的第一个参数传递进来
- 返回值就行需要处理的数据
传输层
BIO和NIO
bio 阻塞io,必须read完成之后在业务逻辑处理,需要用户管理buffer
nio 注册一个监听器,坚挺到有足够的数据之后再进行业务逻辑处理,自动管理底层的buffer
性能优化
网络库
主流操作系统都有网络支持,并且提供 C 语言接口的网络 API。
但是通常来说,这些网络 API 很底层,并不是很好用。举例来说,send() 函数只能传递 char* 对象过去,如果我是 std::string,或者 std::array<unsigned char,4096>(话说我就经常用这个做buffer),那就要自己转换了。而网络库往往会帮你做好这种封装,让你调用起来可以写更少的代码,代码里的逻辑不会因为调用了这些API而显得混乱。此外还能跨平台(原生网络API是不跨平台的,每个系统都要单独写一份),能使开发、维护都更有效率。
字节跳动网络库 netpoll,Netpoll 是一款 Go 语言高性能、I/O 非阻塞 (NIO) 网络库,专注于 RPC 场景。
go net问题
优化
大部分包都是在4k以下的,使用缓冲区进行优化
netpoll 问题
采用链表的方式,实现buffer的无锁化,但是链表可能会导致跨节点的问题,如图header和body都不在同一个节点上,想要进行完整解析的话 需要先将两个header拼接并拷贝到一个完整的内存中才能解析
解决
直接将节点buffer变大
比较
协议优化
header解析
可以使用SIMD进行加速优化, SIMD 的全称是 Single Instruction Multiple Data,中文名“单指令多数据”。顾名思义,一条指令处理多个数据。
热点资源池化
如果对每一个请求有一个一个requestcontext,那么压力比较大,所以维护了一个requestcontext池 有一个请求就从池中拿出来一个requestcontext
将requestcontext放回池子中 会有一些复杂的reset操作,因为需要进行复用
如果reset超出声明周期的话 可能会带来数据不一致的问题