HAProxy 1.9发布,支持端到端 HTTP/2,改进缓冲区和连接管理(52)

发布于2019-04-21 20:40:32

HAProxy Technologies正式宣布推出HAProxy 1.9。新版本带来了基于原生HTTP表示(HTX)的端到端HTTP/2支持,并为未来的创新——如HTTP/3(QUIC)——铺平了道路。它还带来了缓冲区和连接管理方面的改进,包括到后端的连接池、线程优化、Runtime API,等等。

HAProxy是世界上速度最快、使用最为广泛的软件负载均衡器,于2001年12月首次发布。从那时起,负载均衡器格局发生了重大变化。17年来,HAProxy一直在演化和创新,现在终于迎来了HAProxy 1.9。

新版本的重点是为我们以后能够继续提供一流的性能和交付尖端功能奠定基础。得益于1.9版本的核心改进,一些令人兴奋的功能包括Layer 7重试机制、回路断路、gRPC、新的数据平面API,等等。

我们还发现需要更频繁地发布更新。在未来,HAProxy将从一年发布一次改为一年两次。之前,主要版本会在每年的11月或12月份左右发布,但从现在开始,我们将每年发布两次。请注意,这次的版本向后兼容旧的配置。

接下来,我们将深入探讨新版本带来的改进。

缓冲区改进

HAProxy已经支持到客户端的HTTP/2。新版本的主要目标是支持端到端的HTTP/2,包括到后端服务器的HTTP/2。我们还希望能够支持未来版本的HTTP,例如HTTP/3(QUIC)。

我们的研发团队付出了巨大的努力来实现这一目标,其中一个非常重要的方面涉及HAProxy处理缓冲区的方式。

缓冲区是一个存储区域,分为两部分:输入数据和输出数据。缓冲区可以在任何地方开始和结束。在之前版本的HAProxy中,有22种可能的缓冲区,如下图所示:

上图显示了1.9版本之前的各种缓冲区及其分配方式。我们决定重写缓冲区处理逻辑并简化缓冲区分配。下图显示了最新的缓冲区变更:

最新的变更将缓冲区的数量减少到7个,只需维护一个版本的代码。

除了这些重构之外,缓冲区的标头(即描述缓冲区状态的附加字段)已从存储区域中分离出来。这意味着对于相同的数据不再强制要求使用单一的表示,多个参与者可以在不同的状态下使用相同的存储。这通常被用在底层的多路复用中,通过读取器和写入器对相同数据块附加别名来避免内存复制(也即“零复制”),从而提升了HTTP/2的性能。

实现这一点并非易事,但却带来了很多好处。它为更容易实现端到端HTTP/2铺平了道路。它还简化了其他一些东西,比如内部API、错误消息处理和渲染统计页面。

连接管理

HAProxy 1.9中的连接管理获得了一些重大改进。新的实现已从面向回调的模型转变为异步事件模型(包含了完成回调)。这种新设计将会非常有用,并减少连接层中可能出现的bug数量。

新设计的一些好处包括:较低的send()延迟(几乎从不进行轮询)、更少的层间往返(更好的I-cache效率)、在上层内直接使用、消除代码重复,以及提供较低层的细粒度错误报告。它还为不同协议提供了重试失败连接的能力(例如,如果ALPN表明同时支持HTTP/2和HTTP/1,那么在其中一个发生故障时,会自动切换到另一个)。

如果没有设置,http-reuse指令的默认值为safe。这意味着到后端服务器的会话的第一个请求始终通过自己的连接发送,后续请求可以重用其他现有的空闲连接。这是几年来一直推荐的方式,现在是时候将其变为默认设置。

此外,HAProxy现在也提供了连接池。当前端连接消失后,HAProxy和服务器之间的空闲连接并不会立即被关闭,它们在服务器端保持打开状态,以供其他请求使用。

原生HTTP表示(HTX)

在研究支持未来版本HTTP所需的路由时,我们决定对HTTP消息的内部处理机制进行重新设计。之前,HTTP消息作为字节流进行传输,数据的分析和处理任务被混合在同一个阶段中进行。

HTTP信息作为字节流被收集起来,并通过偏移量来操作它们。主结构包含了两个通道:请求/响应和HTTP事务。第一个通道对请求和响应消息进行缓冲,将它们视为字符串。HTTP事务通道有两种状态:一个是响应/请求,另一个包含了标头的偏移量。

因为所有东西都是以偏移量的方式进行保存,所以添加、删除和重写HTTP数据就会变得很麻烦,需要不断移动到标头的末尾,在处理响应消息时甚至需要移动到HTTP正文的末尾。随着时间的推移,cookie、keep-alive、压缩和缓存都需要操作标头,这项任务因此变得十分昂贵。

新的设计(我们称之为HTX)为HTTP协议创建了内部的原生表示。它提供了一系列强类型、精心设计的标头字段,支持间隙和无序。现在修改标头很简单,只需要将旧标头删除,并在末尾添加新标头即可。

这让针对HTTP协议的操作变得更加简单,我们可以维护端到端的HTTP传输和语义,并在将HTTP/2转换为HTTP/1.1或将HTTP/1.1转换为HTTP/2时获得更高的性能。它将处理和分析相分离,现在分析和格式化发生在连接层,而处理发生在应用层。


由于我们还在进行测试,所以默认情况下尚未启用HTX。你可以在defaults、frontend、backend、listen部分添加下面的选项来启用它:

option http-use-htx

在启用后,你就可以在后端服务器上使用HTTP/2。将alpn h2添加到服务器配置行中(或者如果你希望让HAProxy与服务器协商协议,可以添加alpn h2,http/1.1)。

server server1 192.168.1.16:443 ssl verify none  alpn h2

这是一个端到端HTTP/2的完整示例(frontend+backend):

frontend fe_main
    mode http
    option  http-use-htx 
    bind *:80 
    bind *:443 ssl crt /etc/hapee-1.8/certs/www.example.com.pem alpn h2,http/1.1
    default_backend be_main

backend be_main
    mode http
    option http-use-htx
    server server1 192.168.1.13:443 ssl verify none alpn h2

HAProxy 1.9还支持proto h2指令,这个指令让HAProxy可以使用没有TLS的HTTP/2与支持HTTP/2的后端(如Varnish和H2O)通信。你可以使用以下服务器配置启用这项功能:

server server1 192.168.1.13:80 proto h2

多线程改进

1.9版本对线程进行了重大改进。这些改进使得HAProxy能够提供卓越的性能。为实现这一目标,我们对任务调度程序进行了重新设计。它现在的工作被分为三个级别:

现在大多数调度可以在没有锁的情况下进行,可以更好地扩展。此外,我们对调度程序的等待队列进行了优化。它们现在大部分都是无锁的。内存分配器是无锁的,并使用了热对象线程缓存,从而带来更快的结构初始化。文件描述符事件缓存基本上也是无锁的,允许更快的并发I/O操作。最后,文件描述符的锁已更新,因此使用的频率较低。总的来说,HAProxy 1.9的线程性能提升了差不多60%。

缓存改进

我们在HAProxy 1.8中引入了小对象缓存(Small Object Cache)。当时,很多人希望获得这些功能,而我们也知道这只一个开始:代理层中的缓存。在内部,我们将其称为favicon缓存,因为它仅限用于缓存小于tune.bufsize的对象,默认为16KB。此外,在第一个版本中,它只能缓存返回HTTP 200 OK响应码的对象。

而在HAProxy 1.9中,你可以缓存最大为2GB的对象,使用max-object-size进行设置。total-max-size用于指定缓存的总大小,最大为4095MB。HAProxy现在可以缓存返回以下状态码的响应:204、404、405、414和501。

HTTP 103

HAProxy现在支持HTTP 103状态码,也称为Early Hints(RFC8297),它允许你在服务器做出响应之前发送一系列对象链接到客户端。这项特性仍处在早期采用阶段,不过Early Hints看起来可能会取代HTTP/2服务器推送。

因为以下的一些因素,Early Hints会比服务器推送更好:

要启用Early Hints,请在HAProxy配置文件中添加类似以下这样的内容:

http-request early-hint Link "</style.css>; rel=preload; as=style"
http-request early-hint Link "</script.js>; rel=preload; as=script"

Runtime API改进

Runtime API也得到了更新。首先是修改了master/worker模型,简化worker的交互,并获得更好的进程可观察性。master现在有自己的套接字,可以直接用这个套接字与它进行通信。而且套接字可以管理与每个worker之间的通信,甚至是那些正在退出的worker。

要使用这个新功能,需要使用-W和-S选项启动HAProxy。

$ haproxy -W -S /var/run/haproxy-master-socket  -f /etc/haproxy/haproxy.cfg

然后通过master套接字连接到Runtime API,如下所示:

新的show proc命令显示了每个进程的正常运行时间。

新的reload命令重新加载HAProxy,并加载了新的配置文件。它与向master进程发送SIGUSR2信号是完全一样的效果,只是可以在上传新配置文件后由外部程序触发。

命令可以通过master套接字发给worker,在命令前面加上@符号,加上worker的编号。以下是向第一个worker发出show info命令的示例:

我们还添加了有效载荷支持,可以使用Runtime API插入多行文本。在更新map文件时这会非常有用。目前还不支持通过Runtime API更新TLS证书,但HAProxy 2.0有可能会支持!

要使用有效载荷更新map文件,你需要先map的ID,然后使用add map命令添加新行,行间用\n分隔:

$ echo "show map" | socat /var/run/haproxy.sock stdio
# id (file) description
-1 (/etc/haproxy/maps/hosts.map) pattern loaded from file '/etc/haproxy/maps/hosts.map' used by map at file '/etc/hapee-1.8/hapee-lb.cfg' line 94

$ echo -e "add map #-1 <<\ntest1 be_one\ntest2 be_two\n" | socat  /var/run/haproxy.sock stdio

$ echo "show map #-1" | socat /var/run/haproxy.sock stdio
0x217aa80 test1 be_one
0x2190010 test2 be_two

你也可以附加文件内容,比如:

$ echo -e "add map #-1 <<\n$(cat data.map)\n" | socat /var/run/haproxy.sock stdio

Runtime API中还新增了一个新的show activity命令。它可用于显示每个线程被系统抢占的总CPU时间,以及所有任务所经历的平均处理延迟。

同样,如果在global中配置启用或通过Runtime API启用了profiling,那么就可以在日志中看到CPU时间和延迟。要在global中启用profiling,你需要添加:

global
    profiling.tasks on

通过Runtime API启用:

$ echo "set profiling tasks on" |socat /var/run/haproxy.sock -

验证是否已启用:

$ echo "show profiling" |socat /var/run/haproxy.sock -

服务器队列优先级控制

HAProxy 1.9允许你对队列中的连接进行优先级排序。例如,你可以让JavaScript或CSS文件优先于图像传给客户端。或者,你可以用它改善高级客户的加载时间。另外,可以用它给机器人安排较低的优先级。

通过添加http-request set-priority-class指令为JS或CSS文件设置更高的服务器队列优先级。为了避免由连续高优先级请求导致的饥饿等待,可以通过set-priority-offset指令为某些请求设置等待时间上限。将这项功能与ACL规则结合使用,你就可以灵活地决定何时以及如何安排连接的优先级。

acl is_jscss path_end .js .css
acl is_image path_end .png .jpg .jpeg
http-request set-priority-class 1 if is_jsscss
http-request set-priority-class 10 if is_image
http-request set-priority-class 100 if !is_jscss !is_image

数字越小,优先级越高。因此,在这里,JavaScript和CSS文件被赋予最高优先级,其次是图像,然后是其他资源。

随机负载均衡算法

我们添加了一个新的随机负载均衡算法。这个算法会选择一个随机数作为一致哈希函数的键。在这个模式下,会用到服务器权重。动态权重更改会立即生效,新添加的服务器也会立即生效。随机负载均衡算法对于大型服务器机群或经常需要添加和删除服务器的场景来说非常有用。使用多个负载均衡器可以降低所有流量被重定向到同一服务器的风险。

hash-balance-factor指令通过保持分配给服务器的负载接近于平均值来进一步提高负载均衡的公平性,当服务器的响应时间高度可变时,这会非常有用。

要启用随机负载均衡算法,请在backend中将balance设置为random。

balance random

云原生日志

HAProxy已经具备了将日志写到syslog服务器的能力。但是,在使用Docker的微服务架构中,将syslog安装到容器中是一种反模式。用户要求使用其他方法来发送日志。我们已经从用户那里收到了相当多这类请求,所以花了一些时间选择了一种最佳的实现方式——非阻塞——我们很高兴已经找到了解决方案!

HAProxy 1.9提供了三种发送日志的新方法:将它们发送到文件描述符、stdout或stderr。可以使用标准的log语句添加这些新方法。

要将日志记录到stdout,请使用stdout参数:

log stdout local0

对于stderr也类似。另一种方法是将日志发送到文件描述符,如下所示:

log fd@1 local0

fd@1参数是stdout的别名,fd@2是stderr的别名。它们还有两种新的日志格式:raw和short。

log stdout  format raw  local0

新的Fetch

HAProxy中的Fetch提供了来自内部状态或L4、L5、L6和L7的信息。新版本新增了date_us、cpu_calls、lat_ns_avg等Fetch。

新的转换器

转换器可用于在HAProxy中转换数据,通常用在Fetch之后。新版本新增了strcmp、concat、length、crc32c等转换器。

杂项改进

新版本的HAProxy还添加了其他各种改进,包括:

回归测试套件

Varnish附带了一个叫作varnishtest(https://varnish-cache.org/docs/trunk/reference/varnishtest.html)的工具,用于对整个Varnish代码库进行回归测试。我们发现它也是用来测试HAProxy特定用例的完美工具。我们与Varnish团队合作,为varnishtest提供补丁,对其进行扩展,并用它来测试HAProxy。

我们还开始创建和发布可在用户环境中运行的测试代码。如果你了解HAProxy,编写测试代码就很容易。所以,如果你有兴趣为HAProxy做贡献但不知道从哪里开始,你可以尝试先拉取HAProxy的代码,然后自己编写测试!

要使用回归测试套件,需要安装varnishtest,它已经包含在Varnish软件包中。安装完成后,你需要创建一个测试vtc文件。这是一个示例:

varnishtest "Stick Table: Crash when accessing unknown key."
feature ignore_unknown_macro

server s0 {
    rxreq
    txresp
} -start

haproxy h0 -conf {
    defaults
        timeout connect 5000ms
        timeout client 50000ms
        timeout server 50000ms

    frontend test
        mode http
        bind "fd@${fe1}"
        stick-table type ip size 1m expire 1h store gpc0
        http-request deny if { src,table_trackers(test) eq 1 }
        http-request deny if { src,in_table(test) }
        http-request deny deny_status 200
} -start

client c0 -connect ${h0_fe1_sock} {
    txreq -url "/"
    rxresp
    expect resp.status == 200
} -run

要运行它,需要将HAPROXY_PROGRAM环境变量设置为你要测试的二进制文件的路径。然后调用varnishtest。

export HAPROXY_PROGRAM=$PWD/haproxy 
varnishtest /home/user/haproxy/reg-tests/stick-table/b00000.vtc
#    top  TEST /home/user/haproxy/reg-tests/stick-table/b00000.vtc passed (0.112)

HAProxy 2.0预览

以下是将在HAProxy 2.0中带来的功能,计划于2019年5月发布:

结论

在开源社区和HAProxy Technologies的大力支持下,HAProxy始终处于性能和创新的最前沿。我们很高兴为你带来1.9版本!它开启了一个新的篇章,你将看到更频繁的版本更新频率。它将支持端到端HTTP/2,改进了缓冲区和连接管理,更新了Runtime API和小对象缓存,提供了新的随机负载均衡算法,甚至通过Runtime API和新的Fetch获得更好的可观察性。

英文原文:https://www.haproxy.com/blog/haproxy-1-9-has-arrived/