Yelp 础设施团队的主要目标之一就是为了尽可能接近零停机时间。那也就是说当用户访问www.yelp.com作出动作的时候,网站的响应速度必须尽可能的快。一种方法是使用 HAProxy 负载均衡能够保持 www.yelp.com 网站的响应速度。通常我们在任何地方都使用 HAProxy 来保持网站的外部负载均衡、内部负载均衡,甚至运用到构建面向服务的架构中。我们发现在 Yelp 的每台机器上运行 HAProxy,均可作为 SmartStack 的一部分。 我们喜欢在发展 SOA 的时候使用 SmartStack 给我们带来的灵活性,但这种灵活性是有代价的。通常当服务或在服务后端执行增加或永久删除命令的的时候,整个基础设施不得不重新加载 HAProxy。这种重载方式会导致可靠性问题,因为 HAProxy 一旦运转起来就一直保持在工作状态且不会中断流量,但在重载的时候它会中断流量。 HAProxy 重载丢流量 HAProxy 的 1.5.11 版本不支持重启或重新加载配置时的零停机时间。相反,它支持快速重载——当一个新的 HAProxy 实例启动时,它尝试使用 SO_REUSEPORT 去绑定老 HAProxy 监听的相同端口并给老 HAProxy 实例发送信号去关闭。这种技术非常接近现代 Linux 内核的零停机时间,但这仍旧会经历一个短暂的时隙——当两个进程都绑定到端口时。在这个关键的时隙中,由于Linux内核自身(丢弃)处理多个接受进程的方式可能有流量会被丢掉。特别要提出来的,这个问题会潜在导致从 HAProxy 过来的新连接有一个 RST。这个问题是 SYN 包在老 HAProxy 被调用关闭前会被先放进其套接字队列中,这导致了这些连接的 RST。 这个问题有许多解决办法。例如,Willy Tarreau,HAProxy 的主要维护者,建议用户在重启 HAProxy 时丢掉 SYN 包,利用 TCP 的自动修复。不幸的是,RFC 6298 规定的初始 SYN 的超时时间是1s,Linux 内核坚守此规定。 因此,丢弃 SYN 意味着任何试图在 HAProxy 加载的 20-50ms 之间重建的连接将会遭受一个额外的秒级或更长的延迟。确切的时间依赖于客户端的 TCP 实现,而一些移动设备重试时间为 200ms,很多设备只在 3S 后重试。鉴于 HAProxy 重载的次数和和 Yelp 的流量,这成为了我们服务可靠性的一个障碍。 让HAProxy重载不丢包 为了避免延迟,我们采用了Willy提议的方案。他的方案实际上在不丢弃数据包上表现很好,但是额外的一秒延时是个问题。我们更好的解决方案是延迟SYN包直到重载已经完成,因为这样做只会对新的连接产生HAProxy重载所需要的延迟。 为了实现这种方案,我们求助于Linux队列原则(qdiscs)。Linux队列原则是用来管理Linux内核处理网络数据包的方式。具体地说就是你可以控制数据包是如何入队和出队,这提供了速率限制,优先或指定输出数据包的能力。有关更多qdiscs的详情, 我极力推荐lartc howto以及相关的man页面。 我们的一个SRE(网站可靠性工程师),Josh Snyder花了一些晚上的时间阅读了Linux内核源代码,发现了一个文档记录很少的工具:plug排队原则(qdisc),qdisc是从Linux 3.4以来就存在了。使用plug qdisc和以下标准Linux技术, 我们可以实现HAProxy重载零宕机:
SmartStack客户端连接到loopback接口向HAProxy请求,HAProxy幸好将进入的包变成为输出包。这意味着我们可以在loopback接口上建立如图1的队列原则。
Figure 1: Queueing Discipline 该设置的一个分类实现了使用 prio qdisc 队列规定的标准 pfifo_fast, 但只是使用了第四个“plug“通道。plug qdisc 并没有使他们退出队列,而是拥有队列数据包的性能。与一个 iptables 命令相结合的性能来允许我们在整个 HAProxy 重载期间重新定向,然后拔掉后重载的 SYN 插头的数据包。该控键(‘1:1’, ‘30:’等等)是允许我们一起连接 qdiscs,并且使用过滤器发送特定 qdiscs 的数据包。有关更多的信息,请查阅 lartc howto上面所引用的。 然后,我们把我们调用 qdisc_tool 的这个功能编进脚本。该工具允许我们的基础设施“保护”HAProxy 重载我们 plug 流量,重启haproxy,然后松开这个插头,交付延迟所有的 SYN 数据包。这个调用看起来像: qdisc_tool protect <normal HAProxy reload command> 由 GitHub 托管的 qdisc 命令 我们可以轻易地在诸如 Ubuntu Trusty 的 linux 发行版本上使用标准的用户空间实用工具来复制该技术。如果你的设置并没有 nl-qdisc-add,但有3.4+ Linux 内核,那么你可以通过 netlink 来手动地操纵该插头。