HAProxy是如何实现其高速的?
HAProxy是怎么避免在负载均衡时请求时间增加的呢?
我测试了HAProxy,并且为了好玩,把它和我用Twisted(Python)写的一个简单端口转发器进行了比较。在我的初步测试中,通过HAProxy负载均衡器发起HTTP请求,和直接向后端服务器发起HTTP请求相比,几乎没有增加请求时间的开销[1]。而我自己的Python脚本则增加了大约3倍的响应时间。
我的脚本是用Python写的,而HAProxy是用C语言写的,所以从理论上讲,HAProxy在调用时的开销要比Python代码少,因为Python代码需要经过一些系统调用。但这能解释性能差异这么大吗?还是说HAProxy利用了一些操作系统的技巧来进一步提升性能?我尝试分析我的Python代码,但并没有发现明显的性能瓶颈,所以我猜大部分时间都花在了没有被分析到的系统调用上。
[1]: 根据ab的报告,使用100个并发连接和10,000个总请求。HAProxy的平均时间是37毫秒,而我的Python脚本是128毫秒。
设置
这个设置是一个TCP负载均衡器,后面有两个nodejs服务器,只是提供静态文本。我特意想测试TCP负载均衡,所以测试协议就变成了HTTP。这三台机器都是来自Digital Ocean的虚拟主机,单线程,512MB内存,1个核心。 可以在这里查看Python脚本,而我的haproxy.cfg文件可以在这里找到
1 个回答
原来,HAProxy
的网站已经讲得很清楚了(我之前没注意到)。简单来说,答案就是很多底层的优化。以下是直接从HAProxy网站上复制的内容:
HAProxy使用了几种在操作系统架构中常见的技术,以达到最佳性能:
采用单进程、事件驱动的模型,这样可以大大减少上下文切换的成本和内存使用。它可以在一毫秒内处理几百个任务,而每个会话的内存使用量大约只有几千字节,而像
Apache
这样的模型每个进程的内存使用量通常在几兆字节。在支持的系统(如
Linux
和FreeBSD
)上使用O(1)
事件检查器,可以瞬间检测到成千上万条连接上的任何事件。尽可能使用单缓冲区,避免在读取和写入之间进行数据复制。这可以节省大量的
CPU
周期和有用的内存带宽。通常,瓶颈会出现在CPU
和网络接口之间的I/O
总线上。在10Gbps
的情况下,内存带宽也可能成为瓶颈。在
Linux
下使用splice()
系统调用实现零复制转发,从Linux
3.5开始,真正实现了零复制。这使得像Seagate Dockstar
这样的小型设备也能以每秒一gigabit
的速度转发HTTP
流量。使用固定大小的内存池进行
MRU
内存分配,优先分配热缓存区域的内存,而不是冷缓存区域。这大大减少了创建新会话所需的时间。工作分配,比如可以同时进行多个
accept()
操作,并且在多进程模式下能够限制每次迭代的accept()
数量,从而使负载在进程之间均匀分配。基于树的存储,充分利用我多年来开发的
Elastic Binary
树。这用于保持定时器有序,管理运行队列和轮询、最少连接队列,成本仅为O(log(N))
。优化
HTTP
头分析:头部信息在运行时被解析和解释,解析过程经过优化,避免重复读取之前已经读取的内存区域。当到达缓冲区末尾且头部不完整时,会使用检查点,这样在读取更多数据时,解析不会从头开始。解析一个平均的HTTP
请求通常只需2微秒,在Pentium-M 1.7 GHz
上。小心减少昂贵的系统调用数量。大部分工作默认在用户空间完成,比如读取时间、缓冲区聚合、文件描述符的启用/禁用。