HTTP协议| 青训营

走进 HTTP 协议

HTTP 协议是什么

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

HTTP是一种应用层协议,用于在客户端和服务器之间传输超文本文档,如HTML、CSS和JavaScript等。它是现代Web通信的基础,通过明确的边界来标识请求和响应的开始和结束,以及携带请求和响应所需的信息。

HTTP协议能够携带各种类型的消息,包括请求和响应消息。请求消息由客户端发起,包括方法名、URL和协议版本,而响应消息由服务器返回,包含协议版本、状态码和状态码描述。

协议里有什么

HTTP协议由以下主要部分组成:

  1. 请求行/状态行: 请求行包含请求方法(如GET、POST等)、URL和协议版本;状态行包含协议版本、状态码和状态码描述。
  2. 请求头/响应头: 请求头包含客户端发送给服务器的额外信息,如User-Agent、Cookie等;响应头包含服务器发送给客户端的额外信息,如Content-Type、Set-Cookie等。
  3. 请求体/响应体: 请求体包含客户端向服务器发送的数据,通常用于POST请求;响应体包含服务器返回给客户端的数据,通常是请求的结果,如HTML文档、JSON数据等。

请求流程

HTTP请求的处理涉及多个层次,如下所示:

  1. 业务层: 位于最上层,负责处理具体的业务逻辑。
  2. 服务治理层/中间件层: 用于处理一些通用的逻辑,如权限控制、日志记录等。
  3. 路由层: 根据请求的URL找到对应的处理函数或中间件。
  4. 协议编(解)码层: 负责将HTTP请求和响应进行编码和解码。
  5. 传输层: 负责实际的数据传输,通常使用TCP协议。

不足与展望

尽管HTTP协议在现代Web通信中发挥着重要作用,但它也存在一些不足之处。例如:

  1. HTTP1: 在HTTP1中存在队头阻塞的问题,请求响应的传输效率较低,而且明文传输不安全。

为了解决这些问题,新的协议不断出现:

  1. HTTP2: 引入多路复用机制,允许在单个连接上同时传输多个请求和响应,从而减少了队头阻塞的问题。它还引入了头部压缩和二进制协议,进一步提高了传输效率。
  2. QUIC: 基于UDP实现,解决了队头阻塞问题,同时支持快速握手和加密传输,进一步提高了性能和安全性。

框架的设计与实现

分层设计

image.png

框架设计采用分层结构,以便实现专注性、扩展性和复用性。不同层次之间的职责清晰分离,使得代码易于维护和扩展。

image.png

应用层设计:提供合理的 API

在应用层设计中,关键是提供合理的API,以便开发者能够轻松地使用框架。

  • 可理解性: 提供直观、易懂的API,避免使用过于复杂的命名,例如使用ctx.Body()ctx.GetBody()而不是ctx.BodyA()
  • 简单性: API设计应简洁明了,避免冗余和复杂性,例如使用ctx.Request.Header.Peek(key)ctx.GetHeader(key)来获取Header信息。
  • 冗余性: 消除不必要的冗余功能和接口,保持API的精简性。
  • 兼容性: API设计应当考虑到向后兼容,不轻易修改或废弃已有的API。
  • 可测性: API应当容易进行单元测试,以保证代码的质量和稳定性。
  • 可见性: 合理的API设计应当使得框架的使用和功能清晰可见,避免使用过于隐晦的接口。

中间件设计:洋葱模型

中间件在框架中扮演着重要角色,它们用于包裹请求处理的过程,允许在请求处理之前和之后执行一些预处理和后处理逻辑。

  • 中间件需求: 中间件应与Handler配合,形成完整的请求处理生命周期。它们可以拥有自己的预处理逻辑和后处理逻辑,并且支持多个中间件的注册。
  • 洋葱模型: 中间件的执行顺序类似于洋葱的层层剥离,即从外到内执行预处理逻辑,再从内到外执行后处理逻辑。
  • 适用场景: 中间件适用于各种需要在请求处理前后执行的逻辑,如日志记录、性能统计、安全控制、事务处理和异常处理等。
  • 核心: 中间件的核心在于保证在任何场景下,请求处理的顺序得保证递增,即确保每个中间件都能按照正确的顺序执行,并且必须调用下一个处理函数,否则请求处理将被中断。

在中间件的设计中,常见的方式是使用一个索引(通常命名为index)来跟踪当前执行到的中间件位置。每个中间件都可以通过该索引来判断是否继续调用下一个中间件或者结束请求处理。

这种洋葱模型的设计使得中间件的添加和调整非常灵活,而且可以在不修改现有中间件的情况下,轻松地扩展新的中间件,以满足不同的业务需求。

路由设计:前缀匹配树

框架的路由设计负责将客户端请求的URL映射到对应的处理函数或中间件,以完成请求处理的流程。

  • 静态路由: 静态路由是指URL路径是固定的,不包含参数或通配符。例如,/a/b/c/a/b/d都属于静态路由。
  • 参数路由: 参数路由允许在URL中使用参数或通配符。例如,/a/:id/c表示/a/b/c/a/d/c都将匹配到同一个处理函数。
  • 路由修复: 路由修复是指处理URL中可能出现的一些问题,如添加或删除末尾斜杠(/a//a应该匹配到同一个处理函数)。
  • 冲突路由与优先级: 当多个URL匹配规则存在冲突时,框架需要有一套优先级规则来确定应该使用哪个规则。
  • 匹配HTTP方法: 框架还需要考虑请求的HTTP方法(GET、POST、PUT等),以确保正确匹配到对应的处理函数。
  • 多处理函数: 为了方便添加中间件,框架支持将多个处理函数绑定到同一个URL路径,从而实现在请求处理过程中串行执行多个处理函数或中间件。

青铜级路由一般采用map来存储URL路径和对应的处理函数,而黄金级路由通常采用前缀匹配树的结构,以实现更高效的路由查找。

协议层设计:抽象出合适的接口

协议层设计的目标是抽象出合适的接口,以便在协议编码和解码过程中,能够处理各种HTTP请求和响应的数据。

协议层需要定义一套接口,用于处理HTTP请求的编码和解码。这些接口可以根据不同的HTTP协议版本,采用不同的实现方式,以确保框架能够适应未来可能出现的新协议。

传输层设计

传输层负责实际的数据传输,通常采用TCP协议。在传输层的设计中,需要考虑以下因素来优化性能:

  • 网络库优化: 使用高性能的网络库,如go net,它具有流式友好和小包性能高的特点,可以减少系统调用次数、复用内存以及支持多次读取。
  • netpoll优化: 采用netpoll技术可以实现中大包性能高和时延低的优势,能够存储全部Header,并且在需要时拷贝出完整的Body数据。
  • netpoll with nocopy peek: 使用netpoll配合nocopy peek技术,可以分配足够大的buffer,限制最大buffer size,从而进一步优化性能。

网络层设计:网络模型

网络层设计涉及到框架的网络模型,即如何处理网络请求和响应。在网络层设计中,需要考虑并发处理、连接管理和调度策略,以提高网络性能和可靠性。

性能修炼之道

针对网络库的优化

针对网络库的优化主要包括两个方面:

  1. go net优化: go net是Go语言原生的网络库,优化重点在于存储全部Header、减少系统调用次数以及能够多次读取。
  2. netpoll优化: 使用netpoll技术可以提高中大包的性能,同时保持较低的时延。
  3. netpoll with nocopy peek优化: 进一步优化netpoll的性能,通过合理地分配buffer大小和限制最大buffer size来提高性能。

针对协议的优化

针对协议的优化主要集中在Headers解析方面:

  1. 找到Header Line边界: 通过快速定位\r\n,再检查前一个字符是否为\r,可以更快地找到Header Line的边界。
  2. SIMD加速: 使用SIMD指令集可以进一步提高Headers解析的性能。

针对协议相关的Headers,可以采取以下优化措施:

  1. 首字母快速筛选: 可以通过Header key的首字母进行快速筛选,将不可能的key排除。
  2. 解析value到独立字段: 可以将解析后的Header value存储到独立的字段中,方便后续的处理和复用。
  3. 使用byte slice管理Header存储: 使用byte slice来管理Header的存储,能够提高性能和内存利用率。在处理Header时,可以避免频繁的内存分配和拷贝,从而提高解析性能。

通过以上优化措施,可以显著提高协议解析的性能,从而加快请求处理速度,提高系统的吞吐量。

热点资源池化

在框架设计中,可以采用热点资源池化的方式来优化性能。热点资源是指在请求处理过程中频繁使用的资源,例如数据库连接、文件句柄等。为了避免频繁的资源分配和释放带来的性能损耗,可以将这些资源事先预分配,并以池化的方式管理。

热点资源池化的核心思想是在框架启动时,预先创建一定数量的资源对象,并将它们放入资源池中。在请求处理过程中,需要使用资源时,可以从资源池中获取,而不是每次都新建一个资源对象。请求处理完毕后,再将资源对象归还给资源池,以便复用。

通过热点资源池化,可以避免频繁的资源分配和释放,减少系统开销,提高系统性能和稳定性。

企业实践

在企业实践中,性能优化是一项重要的工作。追求性能优化的同时,也需要关注框架的易用性和可维护性。

  1. 追求性能: 企业实践中,需要不断优化框架的性能,采用各种技术手段,如并发优化、资源池化、网络模型优化等,以提高系统的吞吐量和响应速度。
  2. 追求易用: 在框架设计中,需要提供合理的API和接口,以便开发者能够轻松地使用框架。同时,需要提供清晰的文档和示例,帮助开发者快速上手和解决问题。
  3. 减少误用: 框架应当设计得尽可能简单易懂,避免过于复杂的使用方式,以减少开发者的误用和出错。
  4. 打通内部生态: 在企业实践中,框架可能需要与其他系统或组件进行集成,因此需要打通内部的生态,以便实现更丰富的功能和扩展性。
  5. 文档建设、用户群建设: 提供完善的文档建设和用户群支持,帮助开发者更好地使用框架,解决问题,同时也能收集用户的反馈和需求,以不断改进和优化框架。
posted @ 2023-07-31 22:43  LucianaiB  阅读(59)  评论(0)    收藏  举报  来源