GLB篇二: HAProxy使用multibinder实现零宕机时间、零时延重载

Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。

         最近我们部署了GLB,即Github负载均衡器为GitHub.com提供强力支持。处理TCP连接和本地终结TLS/SSL会话的 GLB代理层基于HAProxy构建, HAProxy是一个可靠而且高性能的TCPHTTP代理守护程序。我们设计GLB的初衷之一是打算解决一些在大规模使用HAProxy中遇到的常见问题。

        GLB出现之前,每台主机运行一个单独的HAProxy实例为我们所有的公共业务服务,每个实例有一个面向外部IP集的前端和一个提供后台服务的后端。随着运行服务数量的增加,系统变得很笨重,我们的配置没有模块化,有1千多行相互依赖的ACL 迁移到GLB我们决定按服务分离配置,同时支持在单台机器上运行多个独立的负载均衡程序。另外我们希望能够在无需宕机和不增加连接时延或不影响本机上的任何一个HAProxy实例情况下容易地更新一个单独的HAProxy配置。 今天我们要发布这个问题的解决方案,multibinder

HAProxy几乎安全的重载

         HAProxy采用了SO_REUSEPORT套接字选项,所以它允许多个进程创建基于同一个IP端口对的监听套接字。然后Linux内核分担在所有可用的监听套接字之间的连接负荷。 在下面图中,我们看到HAProxy重载的初始阶段启动了一个单独的进程(左图),然后又启动了另外一个进程(右图),这个进程绑定了同一个IP和端口号,但是属于不同的套接字:

blob.png

 

初始进程结束之前,一切工作的很好。 HAProxy给初始进程发送一个信号,表明新的的进程正在accept()和处理连接(左图),让它停止接受新的连接并在所有连接完成之后最终退出之前关闭自己的套接字接口(右图):

blob.png

 

不巧的是,从这个进程最后一次调用accept()到它调用close()之间这一小段时间内核仍然会把一些新的连接请求路由到这个套接字上。代码仍继续盲目地关闭这个套接字,从而所有堆积在这个监听套接字上的连接请求被丢弃(因为它们的accept()从未被调用):

blob.png

对于小规模的站点,在几毫秒的时间内到来一个新连接的概率很小。不幸的是在我们这么大的规模的HAProxy中,每次我们重载HAProxy,一个有连接冲击的客户总会遇到这个问题。以前我们采用HAProxy提供的官方解决方案,即在这个小的时间窗内丢弃SYN包,之后客户端会立刻重发SYN包。 这个问题的其他解决方案也包括用tc qdisc延迟处理进来的SYN包,然后当重载完成时再恢复队列处理。 GLB开发过程中,我们不满意这些解决方案,并试图找到一种没有队列时延和共享监听套接字的方案。

支持零宕机时间和零时延的重载

       其他服务一般支持零宕机时间重载的方法是共享监听套接字。当服务需要重载时,通常让一个父进程保持套接字开放然后fork()这个服务,新的进程继续使用这个开放的套接字。 这样产生了略微不同的情形, 内核只有一个监听套接字,客户端的请求在两个进程都可以accept()的队列里。每个进程的文件描述符可能不一样,但是它们指向同一个内核套接字结构。

        在这种场景中,一个继承同一个监听套接字的新进程被启动( 左图),当老的进程停止调用accept()时,连接请求仍然在队列里供新的进程处理,因为内核的监听套接字和队列是共享的(右图):

blob.png

 

遗憾的是,HAProxy默认不支持这种方法。我们曾考虑给HAProxy打个补丁以增加内置支持,但是发现HAProxy的架构倾向于进程隔离和非动态配置,架构会有不小的改动。相反, 我们创建了multibinder解决这个问题,它普遍适用于任何需要零宕机时间重载的进程, 并利用一些现有的HAProxy配置指令技巧把它和HAProxy集成在一起,达到了同样的目的。

         Multibinder类似于其他文件句柄共享服务比如einhorn,除了它作为系统的一个隔离的服务和进程树运行,并由任务管理器管理。 这里真正的服务HAProxy作为一个独立的服务运行,而不是一个子进程。当HAProxy启动时,一个小的封装脚本调用multibinder 并请求使用Ancillary 数据通过UNIX域套接字接口发送已有的监听套接字。流程如下图:

blob.png

一旦HAProxy 封装获得了套接字,它把监听套接字保存在文件句柄表里面,并从ERB模板生成HAProxy的配置文件,通过文件描述符绑定像fd@N(N是从multibinder接收到的文件描述符)注入文件描述符,然后调用exec()启动HAproxy,由于使用了前面提供的文件描述符而不是新建,所以继承了同一个监听套接字。 到这里我们达到了预想的设置即原先的HAProxy进程可以停止调用accept()而连接请求会在队列里供新的进程处理。

blob.png

范例&多实例

       除了发布multibinder,我们也提供了利用系统服务模板运行多个HAProxy实例和multibinder的示例。按照里面的步骤,你可以创建一组HAProxy服务器,其中每个服务器用单独的配置文件,并共用一个系统的multibinder实例用于请求绑定,实现真正的零宕机时间和零时延的重载。

 

 

 


英文原文:http://githubengineering.com/glb-part-2-haproxy-zero-downtime-zero-delay-reloads-with-multibinder/
译者:geek123
 

2月15日11:00到13:00网站停机维护,13:00前恢复
iPy智能助手 双击展开
查看更多聊天记录
(Ctrl+回车)换行