发布时间:2023-12-20 分类: 电商动态
最近重建了一辆去年推出的Vino车轮。 Vino旨在提供轻量级且有保证的Web服务器,该服务器专注于Web Server的基本部分。在重构过程中,Vino借鉴了许多优秀的开源项目,如Nginx,Mongoose和Webbench。因此,与之前版本的Vino相比,目前的Vino不仅提升了性能,而且还设计得更加优雅和健壮: D.
本文将介绍Vino目前的主要功能并总结开发过程。
单线程+非阻塞
Vino使用事件驱动的单线程+非阻塞模型。单线程模型避免了线程之间系统分配的多线程和通信的开销,同时减少了内存消耗。由于单线程模型,为了提高线程利用率,Vino将默认阻塞I/O设置为非阻塞I/O,即,如果在线程读/写数据期间缓冲区为空。缓冲区已满,线程不会阻塞,但会立即返回并设置errno。
Vino最初的灵感来自计算机系统: A Programmer's Perspective,这是一个在网络编程期间实现的简单Web服务器。每次请求进入时,Web Server都会分叉一个进程。显然,这种模型在高并发场景中是不合理的。每个fork进程都会带来很大的开销,并且系统中的进程数量有限。同时,与多个进程关联的进程调度的开销不容低估,并且CPU花费大量时间来决定调用哪个进程。由进程调度引起的进程上下文之间的切换也需要相当多的资源。
很容易想到采用多线程模型而不是多进程模型。与多进程模型相比,多线程模型占用的系统资源将大大减少,但实质上,线程调度引起的开销并没有减少。为了减少线程调度造成的开销,我们可以使用线程池模型,即固定线程的数量,但问题仍然存在:因为Linux默认I/O是阻塞,如果线程中的所有线程池被同时阻止对于正在处理的请求,没有线程来处理新的传入请求。因此,如果我们用非阻塞I/O替换默认的阻塞I/O,则线程不会阻止数据的读写,问题可以解决。
HTTP Keep-Alive
Vino支持持久连接,这意味着多个请求可以重用相同的TCP连接,从而降低建立/断开TCP的性能开销。每次请求到达时,Vino都会解析请求以确定请求标头中是否存在连接:保持活动请求标头。如果存在,它将在处理请求后保持连接,并重置数据缓冲区(用于保存请求内容,响应内容)和状态标志,否则,关闭连接。
关于HTTP Keep-Alive的优点,RFC 2616有一个更全面的摘要,如下所述。
通过打开和关闭较少的TCP连接,CPU时间将保存在路由器和主机(客户端,服务器,代理,网关,隧道或高速缓存)中,并且用于TCP协议控制块的内存可以保存在主机中。
HTTP请求和响应可以在连接上流水线化。流水线技术允许客户端在不等待每个响应的情况下发出多个请求,从而允许更有效地使用单个TCP连接,并且使用时间更短。
通过减少TCP打开引起的数据包数量,并允许TCP有足够的时间来确定网络拥塞状态,从而减少网络拥塞。
由于没有花费在TCP连接打开握手上的时间,因此减少了后续请求的延迟。
HTTP可以更优雅地发展,因为可以报告错误而不会因为关闭TCP连接而受到惩罚。使用未来版本的HTTP的客户端可能会乐观地尝试新功能,但如果与旧服务器通信,则在报告错误后重试旧语义。
定时器定时器
如果请求在建立连接后没有发送数据,或者另一方突然断电,我该怎么办?我们需要实现一个计时器来处理超时请求。 Vino计时器的实现是指Nginx的设计。 Nginx使用红黑树来存储各种定时事件。每次事件循环时,都会从红黑树中不断找到最小(早期)事件。如果超时,则触发超时处理。 。为了简化实现,在Vino中,我实现了一个小的顶层堆来存储定时事件。如果处理的定时事件同时支持长连接,则在处理请求之后将更新与请求相对应的定时器,即定时。请参阅vn_event_timer.h和vn_event_timer.c的与计时器相关的代码。
HTTP解析器
由于网络的不确定性,我们无法保证一次读取所有请求数据。因此,对于每个请求,我们将打开一个缓冲区来保存已读取的数据。同时,我们需要同时解析读取数据,以确保读取数据是合理的数据。例如,如果当前缓冲区中的数据是GET /index.html HTT,则下一个读取是字符必须为P.否则,它应立即检测到当前请求是异常请求并主动关闭当前连接。
基于以上分析,我们需要实现HTTP状态机(Parser)来维护当前的解析状态。 Vino状态机的实现是指Nginx的设计,简化了Nginx的实现。有关HTTP Parser相关代码,请参阅vn_http_parse.h和vn_http_parse.c。
记忆池
我们通常使用malloc/calloc/free来分配/释放内存,但这些函数对于需要长时间运行的程序有一些缺点。经常使用这些函数来分配和释放内存会导致内存碎片,系统不容易直接回收内存。一个典型的例子是在大并发中频繁分配和回收内存,这可能导致进程的内存被分段并且不会立即被系统恢复。
使用内存池分配内存可以在一定程度上提高内存分配的效率,而无需每次都调用malloc/calloc函数。同时,使用内存池可以更轻松地进行内存管理。在Vino中,对于每个请求,Vino分配一个或多个内存池(每个内存池形成一个链表),并在处理请求后释放所有内存。
对于Nginx实现,仍然引用和简化了Vino内存池的实现。有关内存池的相关代码,请参阅vn_palloc.h和vn_palloc.c。
其他
在Vino的开发中有许多事情需要考虑和考虑。在响应请求时,如果用户请求大文件,导致写缓冲区已满,那么我们如何才能更好地设计响应缓冲区?如何更有效地设计底层数据结构(如字符串,链表,小顶层堆栈等)如何更优雅地解析命令行参数?如何处理特定信号?如何更有力地处理错误信息?当代码数量达到一定水平时,如何更快地定位异常代码?
Vino的发展与发展重构即将结束,源代码在GitHub上。当然,Vino有许多不足之处,以及未实现的功能。
仅支持HTTP GET方法,目前不支持其他HTTP方法。
目前不支持处理动态请求。
支持的HTTP/1.1功能有限。
...
写这篇文章,希望对初学者有所帮助。