什么是HTTP
HTTP(Hypertext Transfer Protocol)是一种用于在网络上传输超文本的协议。它是Web应用程序通信的基础,用于在客户端和服务器之间传输数据。
HTTP将传输当中的消息进行边界明确以及携带超文本文件(jpg,mp3,avi)。
一个post当中包含了什么?
post消息请求由以下几部分组成:
- 请求行:包含HTTP方法(POST)、请求目标的URL和HTTP协议版本。
- 请求头:包含请求相关的元数据
- 请求体:主要是发生给服务器的数据,请求体可以是任意类型的数据,如表单数据、JSON数据、XML数据等。 下面是一个请求的流程图:
HTTP不同版本的不足与展望
HTTP1.0:
- 每个请求/响应都需要建立和关闭连接,容易使对头堵塞,导致性能较低。
- 不支持持久连接,每次请求都需要重新建立连接,增加了延迟和资源消耗。
- 没有对请求进行流水线化处理,一个请求必须等待前一个请求的响应返回后才能发送,影响并发性能。
- 都是进行明文传输,消息传递是很不安全的。
HTTP2.0:
- 引入了多路复用,允许多个请求在同一个连接上并发发送和接收,提高了性能和效率。
- 支持头部压缩,减少了请求头和响应头的大小,节省了带宽。
- 支持服务器推送,服务器可以主动向客户端推送资源,减少了客户端的请求次数。
HTTP3.0:
- 使用QUIC协议替代TCP,提供更快的连接建立和更可靠的数据传输。
- 支持0-RTT连接,减少了连接建立的延迟。
- 支持无序的数据传输,避免了TCP的队头阻塞问题。
- 引入了数据流优先级,提供更好的资源调度和带宽利用率。
HTTP框架的设计与实现
为什么要使用HTTP框架:
- 分层设计提高了模块化和可扩展性:将HTTP框架分成不同的组件或层,每个组件或层专注于特定的功能,使得整个框架更加模块化和可扩展。这样可以更方便地对框架进行扩展和定制,同时也便于开发人员在不同层级上进行更精细的调整和优化。
- 提高代码复用性和维护性:分层设计使得HTTP框架中的一些通用功能可以被多个组件或层共享和复用,避免了重复编写代码,减少了代码冗余。同时,每个组件或层的功能更加清晰明确,便于维护和修改。
- 提高开发效率和可读性:分层设计使得HTTP框架的代码更加组织有序,逻辑清晰,便于开发人员理解和阅读。开发人员可以更容易地定位和解决问题,提高开发效率。
- 提供更好的抽象和封装:分层设计使得HTTP框架可以将底层实现细节隐藏起来,提供更高级别的抽象接口给上层组件使用,提高了框架的易用性和稳定性。
分层设计的基本理念是高内聚,低耦合,易复用,高拓展性 高内聚指的是当中的组件相关功能应该高度的紧密联系在一起,低耦合指的是不同模块应该是相互独立的,关系相对松散的,易复用指的是组件应该是可以方便重复使用的,高拓展是指设计的组件可以方便的进行组件拓展。
应用层设计
后端在应用层需要提供合理的API:
可理解性:如ctx.Body(),ctx.GetBody),不要用ctx.BodyA()
简单性:如ctx.Request.Header. Peek(key)
/ctx.GetHeader (key)
冗余性
兼容性
可测性
可见性
中间件设计
中间件需求:
- 配合Handler实现一个完整的请求处理生命周期
- 拥有预处理逻辑与后处理逻辑
- 可以注册多中间件
- 对上层模块用户逻辑模块易用
洋葱模型
中间件在洋葱模型当中起着下面这些作用:
- 请求到达:当客户端发送请求时,请求首先经过基础设施层和应用层,然后到达中间件层。
- 中间件处理:中间件可以对请求进行一些处理,例如记录日志、鉴权、身份验证、请求转换等。每个中间件只关心自己的处理逻辑,不关心请求的最终目的地。
- 传递给领域层:经过中间件的处理后,请求会被传递给领域层,由领域层负责具体的业务逻辑处理。
- 响应返回:领域层处理完业务逻辑后,将响应传递给中间件层。中间件可以对响应进行一些处理,例如添加响应头、修改响应内容等。
- 响应返回客户端:经过中间件的处理后,响应最终返回给客户端。
路由设计
路由设计实际上是为了URL可以匹配对应的的处理函数
- 静态路由:/a/b/c、/a/b/d
- 参数路由:/a/ : id/c (/a/b/c,/a/d/c)、 /*all
- 路由修复:/a/b<->/a/b/
- 冲突路由以及优先级:/a/b、/:id/c
- 匹配HTTP方法
- 多处理函数:方便添加中间件
前缀匹配树
前缀路由匹配树在Web框架中广泛应用于路由分发和URL路径匹配。它可以快速地找到与请求URL匹配的处理函数或控制器,从而实现请求的分发和处理。前缀路由匹配树的使用可以提高路由匹配的效率,避免遍历全部路由规则来寻找匹配的处理函数。
例如,对于以下路由规则:
- /users/:id -> 处理用户信息
- /posts/:id -> 处理文章信息
- /about -> 处理关于页面
如何设计:
- 明确需求:考虑清楚要解决什么问题、有哪些需求
- 业界调研:业界都有哪些解决方案可供参考
- 方案权衡:思考不同方案的取舍
- 方案评审:相关同学对不同方案做评审
- 确定开发:确定最合适的方案进行开发
协议层设计
抽象出合适的接口:
- Do not store Contexts inside a struct type;instead, pass a Context explicitly to eachfunction that needs it. The Context should be the first parameter.(不要将上下文存储在结构类型中;相反,将上下文显式传递给需要它的每个函数。上下文应该是第一个参数。)
- 需要在连接上读写数据
网络层设计
BIO和NIO BIO(Blocking I/O):
BIO是传统的I/O模型,也称为同步I/O。- 在
BIO模型中,I/O操作是阻塞的,即当应用程序发起一个I/O请求时,它会一直等待直到数据可用或操作完成。 - 在进行网络通信时,BIO通常采用阻塞套接字来进行连接处理,当有新的连接请求时,线程会被阻塞,直到有新的连接到达或连接被关闭。
- 由于BIO模型中每个I/O操作都需要独立的线程进行处理,当有大量的并发连接时,会导致线程数增加,从而影响系统性能。
- NIO(Non-blocking I/O):
NIO是一种更为高级的I/O模型,也称为异步I/O。- 在
NIO模型中,I/O操作是非阻塞的,即当应用程序发起一个I/O请求时,它可以继续执行其他任务,而不需要等待数据就绪。 NIO模型通过使用选择器(Selector)来监控多个通道的状态,当有通道就绪时,可以进行读写操作。NIO提供了缓冲区(Buffer)来优化数据读写操作,减少了数据拷贝的次数。NIO模型中,可以使用单个线程来处理多个连接,从而减少了线程数量,提高了系统的并发能力和性能。
性能修炼之道
网络库优化
get net和netpoll
getnet它是指从网络轮询器中获取网络事件的过程。在Go语言中,网络轮询器是一个基于epoll、kqueue等系统调用实现的事件驱动机制,用于监听和处理网络I/O事件。当Go程序中的goroutine需要进行网络I/O操作时,它会进入到网络轮询器,并在get net 阶段等待网络事件的到来。一旦网络事件到来,调度器会将goroutine从网络轮询器中唤醒,然后继续执行。
netpoll 是Go语言运行时调度器的一个组件,它负责管理网络轮询器和goroutine之间的交互。在get net 阶段,goroutine会被放置在netpoll 组件中,等待网络事件的到来。一旦网络事件到来,netpoll 组件会负责将goroutine从netpoll 中唤醒,然后将其重新放回调度器中执行。netpoll 组件的设计允许在一个系统线程上运行多个goroutine,并通过事件驱动的方式进行调度,从而实现高效的网络I/O处理。
我个人对于这个的理解可以用一个例子解释,get net可以认为是一个街边的小餐馆,当店里来人的,店中的厨师就会进行做菜,而NETPOLL则可以被认为高端私房菜,要是想去那里吃饭,前台会接受顾客的菜单,服务员再把菜单给后厨,厨师才是开始进行做菜。
协议优化
Header 解析 取舍:
取:核心字段快速解析使用byte slice存储额外存储到成员变量中
舍:普通header 性能较低没有map结构
热化资源池: 热化资源池的工作原理如下:
- 初始化阶段:在应用程序启动时,预先创建并初始化一定数量的资源,比如数据库连接、网络连接、线程等。
- 资源池管理:这些资源被保存在资源池中,以便在需要时快速获取。
- 资源复用:当应用程序需要使用资源时,它会从资源池中获取一个可用的资源。如果资源池中没有空闲资源,则等待直到有资源可用。
- 资源回收:当资源使用完毕后,将资源返回给资源池,使其可以被其他请求复用。
热化资源池的优点:
- 提高性能:由于资源在应用程序启动时已经被初始化,可以避免在运行时动态创建资源的开销,从而提高系统的性能和响应速度。
- 资源复用:热化资源池可以实现资源的复用,避免频繁地创建和销毁资源,减少了资源管理的开销。
- 控制资源数量:通过控制资源池中资源的数量,可以有效地管理系统的资源使用,避免资源过度占用。
- 避免资源争用:资源池可以在资源被多个请求同时需要时避免资源争用,通过等待或限制资源获取来解决资源争用问题。