你好!
上次,我们讨论了在
Ostrovok.ru中选择一种工具来解决将大量请求代理到外部服务的问题,而无需同时放置任何人。 文章以精选的
Haproxy结尾 。 今天,我将分享使用该解决方案时必须面对的细微差别。

代理配置
第一个困难是
maxconn
选项根据上下文而有所不同:
出于习惯,我仅调整了第一个选项(
performance tuning
)。 这是文档中关于此选项的说明:
将每个进程的最大并发连接数设置为<number>。 它
等效于命令行参数“ -n”。 代理将停止接受
达到此限制时连接。
似乎需要什么。 但是,当我发现与代理服务器的新连接并没有马上消失的事实时,我开始更仔细地阅读文档,并且在那里我已经找到了第二个参数(
bind options
):
将套接字限制为此并发连接数。 外来的
连接将保留在系统的待办事项中,直到建立连接为止
已发布。 如果未指定,则限制将与前端的maxconn相同。
所以,
frontends maxconn
吧,然后寻找
frontends maxconn
:
固定前端的最大并发连接数
...
默认情况下,此值设置为2000。
太好了,您需要什么。 添加到配置:
global daemon maxconn 524288 ... defaults mode http maxconn 524288
下一个问题是Haproxy是单线程的。 我非常习惯Nginx中的模型,因此这种细微差别一直令我沮丧。 但是请不要绝望
-Willy Tarreau(Haproxy的开发人员 )了解他在做什么,因此他添加了选项
nbproc
。
但是,直接在文档中显示:
使用多个过程
很难调试,因此非常不高兴。
如果您需要,此选项确实会使您头痛:
- 限制与服务器的请求/连接数(因为您已经没有一个进程带有一个计数器,而是很多进程,并且每个进程都有自己的计数器);
- 从Haproxy管理套接字收集统计信息
- 通过控制套接字启用/禁用后端;
- ...也许其他的东西。 ¯\ _(ツ)_ /¯
尽管如此,众神给了我们多核处理器,所以我想最大限度地使用它们。 就我而言,两个物理核心中有四个核心。 对于Haproxy,我选择了第一个核心,它看起来像这样:
nbproc 4 cpu-map 1 0 cpu-map 2 1 cpu-map 3 2 cpu-map 4 3
使用
cpu-map,我们将Haproxy进程绑定到特定的核心。 操作系统调度程序不再需要考虑在何处计划Haproxy,从而使
content switch
保持凉爽,并使CPU缓存保持温暖。
有很多缓冲区,但在我们这里没有
现在考虑下层的东西
tune.rcvbuf.client /
tune.rcvbuf.server ,
tune.sndbuf.client /
tune.sndbuf.server-该文档显示以下内容:
通常不应设置该值,默认大小(0)可使内核根据可用内存量自动调整该值。
但是对我来说,显而易见的要比隐含的好,所以我强迫这些选项的值来确保明天。
另一个与缓冲区无关但很重要的参数是
tune.maxaccept 。
设置进程可以接受的最大连续连接数
切换到其他工作之前 在单进程模式下,更高的数字
在高连接速率下提供更好的性能。 但是在多进程中
模式,一般来说,保持流程之间的公平性更好
提高性能。
在我们的例子中,生成了很多代理请求,因此我提高了此值以一次接受更多请求。 但是,正如文档所述,值得测试的是,在多线程模式下,负载应尽可能均匀地分布在各个进程之间。
所有参数一起:
tune.bufsize 16384 tune.http.cookielen 63 tune.http.maxhdr 101 tune.maxaccept 256 tune.rcvbuf.client 33554432 tune.rcvbuf.server 33554432 tune.sndbuf.client 33554432 tune.sndbuf.server 33554432
永远不会发生超时。 没有他们我们该怎么办?
- 超时连接 -与后端建立连接的时间。 如果与后端的连接不是很好,那么最好在此超时之前将其禁用,直到网络恢复正常。
- 超时客户端 -传输数据的第一个字节的超时。 它有助于断开那些“保留”请求的人。
有关Go中HTTP客户端的KulstoryGo具有一个常规HTTP客户端,该客户端能够保留与服务器的连接池。 因此发生了一个有趣的故事,其中HTTP客户端中的上述超时和连接池参与其中。 一旦开发人员投诉他定期从代理收到408错误。 我们查看了客户端代码,并在其中看到了以下逻辑:
- 我们正在尝试从池中建立免费的已建立连接;
- 如果无法解决,请在goroutine中开始安装新连接;
- 再次检查游泳池;
- 如果池中有可用的东西-我们将其拿走,然后将新的放入池中;如果没有,请使用新的。
已经了解盐是什么?
如果客户端已建立新连接但尚未使用,则服务器在五秒钟后关闭它,并且情况已结束。 客户端仅在它已经从池中获取连接并尝试使用它时才捕获此连接。 值得牢记这一点。
所有超时一起:
defaults mode http maxconn 524288 timeout connect 5s timeout client 10s timeout server 120s timeout client-fin 1s timeout server-fin 1s timeout http-request 10s timeout http-keep-alive 50s
记录中 为什么这么难?
如我先前所写,在我的决策中,大多数时候我都使用Nginx,因此,它的语法和修改日志格式的简单性让我着迷。 我特别喜欢杀手级功能-以json的形式格式化日志,然后将其与任何标准库一起解析。
在Haproxy我们有什么? 有这样的机会,只有您才能以syslog独占方式编写,并且配置语法要稍微包装一些。
我将为您提供一个示例配置,并提供注释:
特殊的疼痛是由以下时刻引起的:
- 短变量名,尤其是它们的组合,例如%HU或%fp
- 格式不能分为几行,因此您必须在一行中写上脚印。 难以添加/删除新的/不必要的项目
- 为了使某些变量起作用,必须通过捕获请求标头明确声明它们
结果,要获得一些有趣的东西,您就必须拥有这样的鞋履:
log-format '{"status":"%ST","bytes_read":"%B","bytes_uploaded":"%U","hostname":"%H","method":"%HM","request_uri":"%HU","handshake_time":"%Th","request_idle_time":"%Ti","request_time":"%TR","response_time":"%Tr","timestamp":"%Ts","client_ip":"%ci","client_port":"%cp","frontend_port":"%fp","http_request":"%r","ssl_ciphers":"%sslc","ssl_version":"%sslv","date_time":"%t","http_host":"%[capture.req.hdr(0)]","http_referer":"%[capture.req.hdr(1)]","http_user_agent":"%[capture.req.hdr(2)]"}'
好吧,似乎没什么,但是很好
我在上面描述了日志的格式,但不是那么简单。 在其中存放一些元素,例如:
- http_host
- http_referer,
- http_user_agent,
首先,您需要从请求中捕获此数据(
capture )并将其放入捕获值数组中。
这是一个例子:
capture request header Host len 32 capture request header Referer len 128 capture request header User-Agent len 128
结果,我们现在可以通过这种方式访问所需的元素:
%[capture.req.hdr(N)]
,其中N是捕获组定义的序列号。
在上面的示例中,Host标头将位于数字0,而User-Agent将位于数字2。
Haproxy具有一个特殊性:它在启动时解析后端的DNS地址,并且,如果它不能解析任何地址,则将失去勇气。
在我们的情况下,这不是很方便,因为后端很多,我们不对其进行管理,因此从Haproxy获取503最好比整个代理服务器由于一个提供程序而拒绝启动更好。 以下选项可以帮助我们:
init-addr 。
直接从文档中摘录的一行使我们可以研究所有可用的方法来解析地址,对于文件,只需将其推迟到以后再继续:
default-server init-addr last,libc,none
最后,我最喜欢的是:后端选择。
每个人都熟悉Haproxy后端选择配置的语法:
use_backend <backend1_name> if <condition1> use_backend <backend2_name> if <condition2> default-backend <backend3>
但是,正确的说,不是很正确。 我已经以自动化的方式描述了所有后端(请参阅
上一篇文章 ),也可以在这里生成
use_backend
,不好的事情并不棘手,但我不想这么做。 结果,找到了另一种方法:
capture request header Host len 32 capture request header Referer len 128 capture request header User-Agent len 128 # host_present Host acl host_present hdr(host) -m len gt 0 # , use_backend %[req.hdr(host),lower,field(1,'.')] if host_present # , default_backend default backend default mode http server no_server 127.0.0.1:65535
因此,我们标准化了后端和URL的名称,您可以通过它们来访问它们。
好了,现在将以上示例编译成一个文件:
完整版本的配置 global daemon maxconn 524288 nbproc 4 cpu-map 1 0 cpu-map 2 1 cpu-map 3 2 cpu-map 4 3 tune.bufsize 16384 tune.comp.maxlevel 1 tune.http.cookielen 63 tune.http.maxhdr 101 tune.maxaccept 256 tune.rcvbuf.client 33554432 tune.rcvbuf.server 33554432 tune.sndbuf.client 33554432 tune.sndbuf.server 33554432 stats socket /run/haproxy.sock mode 600 level admin log /dev/stdout local0 debug defaults mode http maxconn 524288 timeout connect 5s timeout client 10s timeout server 120s timeout client-fin 1s timeout server-fin 1s timeout http-request 10s timeout http-keep-alive 50s default-server init-addr last,libc,none log 127.0.0.1:2514 len 8192 local1 notice emerg log 127.0.0.1:2514 len 8192 local7 info log-format '{"status":"%ST","bytes_read":"%B","bytes_uploaded":"%U","hostname":"%H","method":"%HM","request_uri":"%HU","handshake_time":"%Th","request_idle_time":"%Ti","request_time":"%TR","response_time":"%Tr","timestamp":"%Ts","client_ip":"%ci","client_port":"%cp","frontend_port":"%fp","http_request":"%r","ssl_ciphers":"%sslc","ssl_version":"%sslv","date_time":"%t","http_host":"%[capture.req.hdr(0)]","http_referer":"%[capture.req.hdr(1)]","http_user_agent":"%[capture.req.hdr(2)]"}' frontend http bind *:80 http-request del-header X-Forwarded-For http-request del-header X-Forwarded-Port http-request del-header X-Forwarded-Proto capture request header Host len 32 capture request header Referer len 128 capture request header User-Agent len 128 acl host_present hdr(host) -m len gt 0 use_backend %[req.hdr(host),lower,field(1,'.')] if host_present default_backend default backend default mode http server no_server 127.0.0.1:65535 resolvers dns hold valid 1s timeout retry 100ms nameserver dns1 127.0.0.1:53
感谢那些读到最后的人。 但是,这还不是全部。
下次,我们将研究与优化系统本身相关的低级事物,Haproxy可以在其中进行工作,以使其与他的操作系统配合使用,并且每个人都有足够的动力。
待会见!