文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

每天我们在 GitHub 处理成千上万的HTTP请求, Git 请求 和 SSH 连接. 为了达到最高的性能我们使用我们将其运行在 bare metal hardware (裸机硬件?)之上. 以前,我们使用一个更复杂的组件作为我们的负载均衡层. 后来我们进行了深度扩展, 使用了一组非常大的机器来运行 haproxy, 并且使用非常精细的硬件配置用来允许专用10G链路故障转移。 最后,我们需要一种可扩展的解决方案,于是我们着手创建一个将在我们的典型数据中心配置在商用硬件上运行一个负载均衡解决方案.

第 1 段(可获 1.18 积分)

在过去的一年中我们开发了新的负载均衡器, 它的名字叫 GLB (GitHub 负载均衡器). 未来几周内,我们会分享它的设计架构并且把它的组建作为开源软件发布出来.

除旧迎新

我们需要一种新方法来应对 GitHub 快速又庞大的发展速度,需要用垂直扩展方法来匹配负载均衡器。我们最初的设计围绕几台非常大的机器,每台机器使用专线连接到网络主干线. 这种设计束缚了网络组件,负载均衡服务器和配置项以这样的一种方式放在一起,很难进行横向扩展 . 于是我们打算寻找更好的方法.

第 2 段(可获 1.49 积分)

我们首先确定了新的目标系统,新系统选型的经验灵感来自现有系统的技术缺陷和设计缺陷。过了一段时间之后,我们确定使用下面的清单内容来构建一个负载均衡器:

  • 在商业硬件上运行
  • 支持横向扩展
  • 支持高可用,故障转移时能够避免TCP连接断开
  • 支持长链接
  • 支持为每个服务使用负载均衡
  • 可以像普通软件那样进行迭代部署
  • 支持所有层均可测试,不能仅仅是集成测试
  • 内置多个 POPs 和数据中心
  • 能够抵御弹性DDoS攻击,并提供发现新攻击的工具
第 3 段(可获 1.48 积分)

设计

要达到这些目标我们需要重新考虑IP 和 服务器之间的关系,我们的负载均衡层是如何处理、控制和终止连接的。

弹性 IP

典型的设置中,你会给一台独立的物理机器分配一个单独的公网IP. DNS就可以使用它在多台服务器上均分流量(降低带宽负载?). 但不幸的是, DNS 具有缓存功能(经常忽略 TTL), 并且有些用户使用白名单功能或者直接使用IP地址访问. 还有, 我们为网页服务提供一组ip,这样用户就可以直接使用他们直接访问顶级域名. 而不是依靠增加额外的IP来增加容量, 并且使用一个IP的话,服务器down掉ip就不能用了,我们想要一种能够把一个IP绑定到多台物理机的解决方案.

第 4 段(可获 1.91 积分)

路由器有一个叫做等价路由(Equal-Cost Multi-Path,ECMP)的特性,用于将具有同样目的IP地址的流量分到多个等价链路上。ECMP通过把报文的某些组件,如源IP地址、目的IP地址和端口值进行哈希来进行分流。通过使用一致哈希的方法,同一个TCP流的后续报文还会被哈希到同一条路径上,避免了报文乱序,并保证了TCP会话的亲缘性。

这种方法能很好地将报文路由到不同路径上,发送到同一个目的物理服务器。有趣的是,用ECMP来分配流量到共享IP地址的多个物理服务器,这些服务器会终止TCP连接,但不共享任何状态,就像在负载均衡器中一样。若其中一个服务器出现故障或不再提供服务,我们将它从ECMP服务器列表中移除时,一个 重哈希 事件就会发生。1/N的连接会重新分配到剩下的服务器中。由于这些服务器不共享连接状态,这些连接会被终止掉。不幸的是,这些连接可能不是分配到故障服务器的那1/N的链接。另外,我们无法在不影响1/N活跃连接的情况下,优雅地移除和维护一台服务器。

第 5 段(可获 2.65 积分)

4层 / 7层分流的设计

一种被其他项目使用的模式是将负载均衡器分成4层(网络层)和7层(应用层)。在4层,路由器使用ECMP,通过一致哈希,将流量分配到一组4层负载均衡器上——4层负载均衡器通常使用像ipvs/LVS这样的软件。LVS维护连接状态,并选择性地与其他4层节点通过多播来同步连接状态,并且将流量发送到运行haproxy等软件的7层负载均衡器上。我们称4层为“导流器”,因为他们引导了流量,称7层为“代理”,因为他们将连接代理到后端服务器上。

第 6 段(可获 1.33 积分)

这种4层/7层的分离有一个有趣的优点:代理层节点现在能够通过优雅地等待现有连接结束然后被移除掉,因为在引导节点维护的连接状态会保证现有的连接发送到他们绑定的代理节点上,即便这些节点已经被移除掉了。另外,代理层相对需要更多保养,因为它会被我们频繁地修改配置,更新以及扩展。因此,这种方式对我们有利。

如果使用了多播进行连接同步的功能,4层负载均衡器节点能够更优雅地处理故障,因为一旦一个连接已经同步到了其他4层节点上,这个连接就永远不会被中断了。没有连接同步的话,假设引导节点用同样的方式对连接进行哈希,并发送到同样的后端节点上,即使一个引导节点故障,这个连接仍可能会继续。在实践中,大多数这种分层设计的实现仅仅是在节点故障或节点维护时接受连接的中断。

第 7 段(可获 2 积分)

不幸的是,在引导层使用LVS会有巨大的问题。首先,我们不希望支持多播,因此我们可能依赖引导节点具有同样的视角,并且对后端节点有一致的哈希。没有连接同步,类似于计划中的节点维护的事件可能会导致连接中断。我们希望避免连接中断,因为连接中断会导致git无法重试或恢复。最后,引导层需要连接状态的事实为DDoS防御增加了额外的复杂度——如synsanity——来避免资源耗尽,syscookie现在需要在引导层生成,尽管连接本身在代理节点被终止。

第 8 段(可获 1.63 积分)

设计一个更好的引导器

我们在设计负载均衡器的最初阶段就决定了,我们要在引导层的常用模式基础上进行优化。我们着手设计一个新的无状态的引导层,来允许尽量无扰地移除引导层以及代理节点。GitHub的用户很多都生活在网络连接很差的国家,当进行节点维护时,做到这件事情对我们来说很重要:用户能在一个合理的时间内,无故障地完成对一个适当大小的代码仓库的克隆。

第 9 段(可获 1.18 积分)

我们着手进行的,并且现在在生产环境中使用的设计,是一种支持限时查找的 Rendezvous hashing 的变体。我们首先存储各个代理节点,并分配一个状态。这些状态处理了我们设计目标中连接结束的方面,我会在后续的博文中讨论这一点。然后我们生成了一个固定大小的转发表,并利用Rendezvous hasing的排序组件将一系列代理服务器填满表格的每一行。随着代理节点的启用和移除,这个表格和代理状态被同步发送到所有引导服务器。当一个TCP报文到达引导器,我们将其源IP地址进行哈希来生成一个指向转发表的一致的哈希。然后我们将报文封装到另一个指向代理服务器的内部IP的IP报文里面(实际上是 Foo-over-UDP),然后通过网络发送新报文。代理服务器收到了封装后的报文,将之解封装,并在本地处理原始报文。任何从GitHub发出的报文都采用服务器直接返回策略(Direct Server Return),就是说发给客户端的报文会直接发出到客户端,完全绕过引导层。

第 10 段(可获 2.3 积分)

敬请关注

通过本文,你已经大致了解了我们的系统如何处理和路由用户请求。我们希望你持续关注后续文章系列:它们深入描述了我们引导器的设计,我们如何改进haproxy的热配置重载,以及我们怎样静默迁移到了新系统。

第 11 段(可获 0.66 积分)

文章评论