负载均衡:流量如何被分配到服务器
当一个服务的访问量超出单台服务器的处理能力时,最直接的扩展方式是部署多台服务器共同承担。但随之而来的问题是:客户端的请求应该发送给哪一台服务器?如何确保各台服务器的负载大致均衡?某台服务器出现故障时,如何让请求自动绕开它?
负载均衡(Load Balancing)就是解决这些问题的技术。它位于客户端与后端服务器之间,按照一定的策略将请求分发到多台服务器上,实现水平扩展、提高可用性,并为服务的运维提供灵活性。
本文介绍负载均衡的作用、不同网络层级的负载均衡方式、常见的调度算法、健康检查与故障处理机制、会话保持问题,以及在全局流量调度、服务网格等现代架构中的负载均衡实践。
一、负载均衡的作用
水平扩展
单台服务器的处理能力受限于其 CPU、内存、网络带宽等硬件资源。通过负载均衡将请求分散到多台服务器上,系统的总处理能力可以随服务器数量的增加而近似线性地提升。与不断升级单台服务器的硬件相比,增加服务器数量通常成本更低,也没有明显的上限。
高可用
任何一台服务器都可能因硬件故障、软件缺陷、资源耗尽等原因而不可用。负载均衡器能够检测出故障的服务器,并停止向其发送请求,由其他正常的服务器继续提供服务。只要还有足够的服务器正常运行,整个服务对用户来说就仍然是可用的。
运维灵活性
负载均衡将客户端与具体的后端服务器解耦。客户端只需知道负载均衡器的地址,而不必关心后端有哪些服务器。这使得运维操作变得更加灵活:
- 滚动发布:逐台将服务器从负载均衡中摘除,升级后再加入,整个过程不影响服务;
- 弹性伸缩:根据负载自动增加或减少服务器,负载均衡器自动感知变化;
- 灰度发布:将一小部分流量导向新版本的服务器,验证无误后再逐步扩大比例;
- 维护隔离:将需要维护的服务器摘除,维护完成后再恢复。
安全防护
负载均衡器作为流量的统一入口,还可以承担一些安全职责:隐藏后端服务器的真实地址,集中处理 TLS 加密与解密,抵御部分类型的拒绝服务攻击,实施访问控制和限流策略。
二、负载均衡的层级
根据工作在网络协议栈中的哪一层,负载均衡大致可以分为四层负载均衡和七层负载均衡两类。
四层负载均衡
四层负载均衡工作在传输层,根据网络数据包的IP 地址和端口号等信息进行转发决策,不解析数据包中的应用层内容。
四层负载均衡的特点:
- 性能高:只需处理数据包的头部信息,不需要解析应用协议,处理开销很小,单台设备可以处理每秒数百万个数据包;
- 协议无关:可以负载均衡任何基于 TCP 或 UDP 的协议,例如数据库连接、消息队列连接、游戏服务器连接等;
- 连接级别的分发:同一个 TCP 连接中的所有数据包,都会被发送到同一台后端服务器。负载均衡的粒度是连接,而非请求;
- 功能有限:由于看不到应用层内容,无法根据请求的路径、请求头、内容等进行路由,也无法修改请求和响应。
四层负载均衡的转发模式
四层负载均衡器转发数据包的方式有多种,各有不同的特点。
网络地址转换模式。 负载均衡器接收到客户端的数据包后,将其目标地址修改为选中的后端服务器地址,再转发出去。后端服务器的响应也必须经过负载均衡器,由它将源地址改回负载均衡器的地址,再返回给客户端。这种模式部署简单,后端服务器无需特殊配置,但所有的请求和响应流量都要经过负载均衡器,负载均衡器容易成为带宽瓶颈。
完全地址转换模式。 在上一种模式的基础上,同时修改数据包的源地址为负载均衡器的地址。这样后端服务器的响应自然会返回给负载均衡器,对网络拓扑没有特殊要求。代价是后端服务器看到的请求来源都是负载均衡器,无法直接获取客户端的真实地址,需要通过其他方式传递,例如在连接建立时附加一个携带原始地址信息的协议头。
直接路由模式。 负载均衡器只修改数据包的目标 MAC 地址,将其直接转发给后端服务器,IP 层的信息保持不变。后端服务器上配置了与负载均衡器相同的虚拟 IP 地址,处理完请求后,直接将响应发送给客户端,不再经过负载均衡器。
在大多数网络应用中,响应的数据量远大于请求。例如,一个网页请求可能只有几百字节,而响应可能有几百 KB。直接路由模式让响应流量绕过负载均衡器,极大地减轻了负载均衡器的带宽压力,使其能够支撑非常大的流量。这种模式要求负载均衡器与后端服务器位于同一个二层网络中。
隧道模式。 负载均衡器将原始数据包封装在一个新的 IP 数据包中,发送给后端服务器。后端服务器解封装后处理请求,同样直接将响应返回给客户端。与直接路由模式相比,隧道模式不要求负载均衡器与后端服务器位于同一个二层网络,可以跨网段甚至跨机房部署。
七层负载均衡
七层负载均衡工作在应用层,它会完整地解析应用层协议,例如 HTTP,根据请求的内容进行转发决策。
七层负载均衡通常作为一个反向代理运行:它与客户端建立一个连接,与后端服务器建立另一个连接,在两个连接之间转发请求和响应。
七层负载均衡的能力要丰富得多:
- 基于内容的路由:根据请求的域名、路径、请求头、查询参数、Cookie 等信息,将请求分发到不同的后端服务组。例如,将以某个路径开头的请求发送到 API 服务,将静态资源请求发送到文件服务;
- 请求级别的分发:在 HTTP 长连接或多路复用的协议中,同一个客户端连接上的多个请求,可以被分发到不同的后端服务器,负载分布更加均匀;
- TLS 终结:在负载均衡器上完成 TLS 的握手和解密,后端服务器只需处理明文请求,减轻了后端的计算负担,也便于集中管理证书;
- 请求和响应的修改:添加、删除或修改请求头和响应头,重写请求路径,对响应进行压缩等;
- 缓存:缓存静态内容或可缓存的响应,直接返回给客户端,减少对后端的请求;
- 更精细的健康检查:可以检查应用层的状态,例如请求一个专门的健康检查接口,判断其返回的状态码和内容;
- 重试与熔断:在请求失败时自动重试其他后端服务器,或者在某个后端持续出错时暂时将其隔离。
七层负载均衡的代价是性能开销较大:需要完整地解析协议、维护两端的连接、处理 TLS 加解密,单位资源能处理的流量比四层负载均衡少得多。
两者的组合
在大规模系统中,四层和七层负载均衡通常组合使用:
- 最外层使用四层负载均衡,以极高的性能承接全部入口流量,并将其分发给一组七层负载均衡器;
- 七层负载均衡器负责 TLS 终结、内容路由和精细的流量控制,再将请求分发给具体的应用服务器。
这样,四层负载均衡解决了七层负载均衡器本身的扩展和高可用问题,七层负载均衡则提供了丰富的应用层功能。
三、调度算法
负载均衡器需要决定将每个请求或连接发送到哪一台后端服务器,这由调度算法决定。不同的算法适用于不同的场景。
轮询
轮询(Round Robin)是最简单的算法:按照固定的顺序,依次将请求分配给每一台服务器,到达最后一台后再从第一台开始。
轮询实现简单,在所有服务器性能相同、所有请求的处理开销相近时,能够实现很好的负载均衡。但它没有考虑服务器的实际负载情况:如果某些请求的处理时间远长于其他请求,或者某台服务器由于其他原因变慢,轮询仍然会继续给它分配同样多的请求。
加权轮询
当后端服务器的性能不同时,例如部分服务器的配置更高,可以为每台服务器设置一个权重,权重越高的服务器分配到的请求越多。
一个直接的实现方式是,权重为 3 的服务器在一轮中连续获得 3 个请求。但这会导致请求在短时间内集中发往同一台服务器。平滑加权轮询算法改进了这一点:每次选择时,为所有服务器的当前值加上各自的权重,选出当前值最大的服务器,然后将其当前值减去所有权重之和。这种方法能够在保证总体比例正确的前提下,让请求在各服务器之间尽可能均匀地交错分配。
随机
随机算法为每个请求随机选择一台服务器。当请求数量足够大时,根据大数定律,各服务器分到的请求数量会趋于均衡。随机算法的优点是不需要维护任何状态,在多个负载均衡器实例并行工作时,无需协调。
加权随机则按照权重比例进行随机选择。
最少连接
最少连接(Least Connections)算法将新的连接分配给当前活跃连接数最少的服务器。
这种算法考虑了服务器的实时负载:处理较慢的服务器会积累更多的活跃连接,从而自动减少分配给它的新连接。在请求处理时间差异较大的场景下,最少连接比轮询能更好地均衡负载。
加权最少连接将活跃连接数除以权重,选择比值最小的服务器,以适应服务器性能不同的情况。
最短响应时间
最短响应时间算法综合考虑服务器的响应时间和当前负载,选择预期响应最快的服务器。一种常见的方式是维护每台服务器响应时间的指数加权移动平均值,结合其当前正在处理的请求数,估算新请求在该服务器上的预期等待时间。
这类算法能够快速感知服务器性能的变化,例如某台服务器因为垃圾回收、资源争用或硬件问题而变慢,会自动减少发往它的流量。但它对测量的准确性依赖较大,并且如果所有负载均衡器都同时将流量转向同一台"最快"的服务器,可能导致该服务器迅速过载,形成负载的来回振荡。
两次随机选择
在大规模分布式系统中,常常有许多个负载均衡器实例同时工作,每个实例只能看到自己发出的请求,无法获取全局的负载信息。如果每个实例都选择"负载最低"的服务器,由于信息存在延迟,所有实例可能在同一时刻都选中同一台服务器,造成羊群效应。
两次随机选择(Power of Two Choices)提供了一个简单而高效的解决方案:每次随机选出两台服务器,然后选择其中负载较低的那一台。
这个看似简单的改动,带来了显著的效果。理论研究表明,与纯随机选择相比,两次随机选择能够使最大负载呈指数级下降。它既利用了负载信息,又通过随机性避免了所有实例同时选中同一台服务器。这种方法被许多现代负载均衡器和服务网格采用,作为默认的调度算法。
哈希
哈希算法根据请求的某个属性计算哈希值,再映射到某台服务器。常用的属性包括客户端 IP 地址、请求路径、用户标识、会话标识等。
哈希算法的特点是:相同属性的请求总是被发送到同一台服务器。这在以下场景中很有用:
- 会话保持:同一用户的请求总是到达同一台服务器,可以利用服务器本地保存的会话数据;
- 缓存亲和:相同资源的请求总是到达同一台服务器,提高各服务器本地缓存的命中率,避免每台服务器都缓存全部数据。
简单的取模哈希在服务器数量变化时,会导致几乎所有请求的映射关系都发生改变。因此,实际中通常使用一致性哈希,使得增加或移除一台服务器时,只有一小部分请求需要重新映射。对于需要在保持亲和性的同时避免单台服务器过载的场景,还可以使用有界负载的一致性哈希,为每台服务器设置负载上限,超过上限时将请求转发到环上的下一台服务器。
四、健康检查
负载均衡器必须知道哪些后端服务器是健康的,才能避免将请求发送给故障的服务器。
主动健康检查
主动健康检查由负载均衡器定期向每台后端服务器发送探测请求,根据响应判断其健康状态:
- TCP 检查:尝试与服务器的端口建立 TCP 连接,连接成功即认为健康。这只能说明服务器上有进程在监听该端口,无法判断应用是否真正可用;
- HTTP 检查:向服务器的某个健康检查接口发送 HTTP 请求,检查返回的状态码,甚至检查响应内容是否符合预期;
- 自定义协议检查:针对数据库、缓存等服务,发送特定的协议命令进行检查。
为了避免因为偶发的网络抖动而误判,健康检查通常会设置阈值:连续若干次检查失败,才将服务器标记为不健康;连续若干次检查成功,才将其恢复为健康。
被动健康检查
被动健康检查不发送额外的探测请求,而是观察实际业务请求的结果。如果发往某台服务器的请求出现连接失败、超时或返回错误状态码的比例超过阈值,就暂时将其标记为不健康,经过一段时间后再尝试恢复。
被动检查能够更快地发现问题,因为它基于实际的流量而非定期的探测;但它只能在已经有请求失败之后才能做出反应。实践中,主动检查和被动检查通常结合使用。
健康检查接口的设计
后端服务的健康检查接口的设计需要仔细权衡:
- 检查过浅:如果健康检查接口只是简单地返回成功,那么即使应用的核心功能已经无法工作,例如数据库连接已经断开,负载均衡器仍然会认为它健康,继续发送请求;
- 检查过深:如果健康检查接口会检查所有的下游依赖,那么当某个共享的下游依赖出现故障时,所有后端服务器的健康检查都会同时失败,负载均衡器会将它们全部摘除,导致服务完全不可用。而实际上,那些不依赖该下游的功能本来仍然可以正常工作。
一个常见的做法是区分两种检查:
- 存活检查:只检查进程本身是否正常运行,例如能否响应请求、是否出现死锁。存活检查失败时,通常应当重启服务;
- 就绪检查:检查服务是否已经准备好接收流量,例如是否完成了初始化、是否加载了必要的配置和数据、本地资源是否充足。就绪检查失败时,应当暂停向其发送流量,但不一定需要重启。
此外,就绪检查一般只应检查服务自身独占的依赖,而不应因为共享依赖的故障而失败。
全部不健康时的处理
当所有后端服务器都被判定为不健康时,负载均衡器应该怎么做?
如果严格地拒绝所有请求,服务将完全中断。但所有服务器同时故障的情况,往往是健康检查本身出了问题,例如检查接口依赖的某个组件出现故障,或者检查的超时时间设置得过于严格。
因此,许多负载均衡器采用一种失效开放的策略:当健康的服务器比例低于某个阈值时,忽略健康检查的结果,将流量发送给所有服务器。这样做的理由是,与其完全中断服务,不如尝试让请求有机会被处理。
五、连接与流量管理
连接排空
当需要将一台服务器从负载均衡中摘除时,例如进行版本升级,如果立即切断它的所有连接,正在处理中的请求就会失败。
连接排空(Connection Draining)机制可以优雅地处理这一过程:
- 负载均衡器停止向该服务器发送新的请求或连接;
- 已经建立的连接和正在处理中的请求继续进行,直到完成;
- 等待一段时间,所有连接都已结束,或者达到超时上限后,再将服务器真正移除。
后端服务本身也需要配合:在收到停止信号后,先停止接受新的请求,等待正在处理的请求完成,然后再退出。
慢启动
新加入负载均衡的服务器,或者刚刚从故障中恢复的服务器,往往还没有完成预热:缓存是空的,即时编译器还没有优化热点代码,连接池还没有建立。如果立即按照正常的权重为其分配流量,它可能因为处理能力暂时不足而响应缓慢,甚至再次失败。
慢启动(Slow Start)机制让新服务器的权重在一段时间内从一个较低的值逐渐增加到正常值,给它足够的时间完成预热。
长连接的负载均衡
在使用长连接的协议中,例如 HTTP/2、gRPC、WebSocket 等,一个连接可能持续很长时间,并在其上发送大量的请求。这给负载均衡带来了挑战:
- 如果使用四层负载均衡,负载均衡的粒度是连接。一旦连接建立,其上的所有请求都会发往同一台服务器。当新的服务器加入时,已有的长连接不会自动迁移过去,新服务器可能长时间得不到足够的流量;
- 客户端如果只建立少数几个长连接,大部分流量会集中在少数几台服务器上。
应对方法包括:
- 使用七层负载均衡,在请求级别进行分发,而不是连接级别;
- 在客户端维护到多台服务器的连接池,在客户端进行请求级别的负载均衡;
- 为长连接设置最长存活时间,定期关闭并重建连接,使连接能够在服务器之间重新分配。
重试
当请求失败时,负载均衡器可以自动将其重试到另一台服务器。这能够隐藏单台服务器的偶发故障,提高请求的成功率。
但重试也需要谨慎对待:
- 只重试安全的请求:对于非幂等的请求,例如创建订单或扣款,如果第一次请求实际上已经被处理,只是响应丢失,重试可能导致操作被重复执行;
- 限制重试次数:避免在大面积故障时,重试导致请求量成倍增长,形成重试风暴,进一步加剧过载。一种常见的做法是设置重试预算,例如重试请求的数量不超过正常请求的一定比例;
- 对冲请求:对于延迟敏感的只读请求,一种变体是在第一个请求经过一段时间仍未返回时,向另一台服务器发送一个相同的请求,采用先返回的结果。这可以有效降低尾部延迟,但会增加额外的负载。
六、会话保持
问题的来源
许多 Web 应用会在服务器的内存中保存用户的会话数据,例如登录状态、购物车内容。如果同一个用户的连续请求被发送到不同的服务器,后一台服务器上没有该用户的会话数据,用户可能会被要求重新登录,或者看到空的购物车。
会话保持的实现
会话保持(Session Persistence,也称会话粘滞)是指让同一个用户的请求始终发送到同一台服务器,常见的实现方式有:
- 基于源地址的哈希:根据客户端的 IP 地址选择服务器。实现简单,但当大量用户通过同一个网络出口访问时,他们的请求会全部集中到同一台服务器;而当用户的 IP 地址发生变化时,例如移动网络切换,会话保持就会失效;
- 基于 Cookie:负载均衡器在第一次响应中插入一个 Cookie,记录所选服务器的标识,后续请求根据该 Cookie 路由到同一台服务器。这种方式更加准确,但只适用于 HTTP 协议,并且需要七层负载均衡。
会话保持的问题
会话保持虽然解决了会话数据的问题,但它也破坏了负载均衡的灵活性:
- 负载不均:某些用户的活跃度远高于其他用户,绑定了这些用户的服务器会承受更高的负载;
- 故障时会话丢失:一台服务器宕机,它所保存的全部会话数据都会丢失,这部分用户需要重新登录;
- 扩缩容困难:新增的服务器只能接收新的用户,已有的用户仍然绑定在原来的服务器上,负载无法快速均衡;移除服务器时,需要等待其上的会话自然过期,或者接受会话丢失。
无状态化
更好的解决方案是让应用服务器无状态化:不在服务器本地保存会话数据,而是将其存放在所有服务器都能访问的外部存储中,例如分布式缓存或数据库;或者将会话信息经过签名后直接保存在客户端,例如使用经过签名的令牌,每次请求时携带。
无状态的服务器可以接收任何用户的任何请求,负载均衡器可以完全自由地分配流量,扩缩容和故障处理也变得简单得多。这是现代可扩展服务的一个基本设计原则。
七、负载均衡器自身的高可用
负载均衡器是所有流量的入口,如果它本身发生故障,整个服务都将不可用。因此,负载均衡器自身也需要具备高可用性。
主备模式
最常见的做法是部署两台负载均衡器,一台作为主节点处理流量,另一台作为备节点。两者共享一个虚拟 IP 地址,平时由主节点持有。
主备节点之间通过一种虚拟路由冗余协议互相发送心跳。当备节点在一段时间内没有收到主节点的心跳时,就认为主节点已经故障,接管虚拟 IP 地址,并向网络中广播地址更新消息,使后续的流量发往自己。
主备模式的缺点是备节点在正常情况下处于闲置状态,资源利用率只有一半;并且整体的处理能力受限于单台负载均衡器。
集群模式
在大规模场景中,通常部署多台负载均衡器组成一个集群,同时处理流量,常见的方式有:
- 等价多路径路由:多台负载均衡器通过动态路由协议向上游路由器宣告同一个虚拟 IP 地址。路由器认为通往该地址有多条等价的路径,根据数据包的源地址、目标地址、端口等信息计算哈希,将不同的连接分散到不同的负载均衡器上。某台负载均衡器故障时,它停止宣告路由,路由器自动将流量分配给其余的负载均衡器;
- 任播:在多个地理位置部署负载均衡器,宣告相同的 IP 地址。网络路由会自动将用户的流量引导到在网络拓扑上最近的节点。
在多台负载均衡器并行工作的情况下,需要注意一个问题:当负载均衡器的数量发生变化时,路由器的哈希映射可能会改变,同一个连接的后续数据包可能被发送到另一台负载均衡器。如果这台负载均衡器不知道该连接应当转发给哪台后端服务器,连接就会中断。解决方法包括:在各负载均衡器上使用一致性哈希选择后端服务器,使所有负载均衡器对同一个连接做出相同的选择;或者在负载均衡器之间同步连接状态。
八、全局负载均衡
对于服务全球用户的系统,通常会在多个地理区域部署服务。全局负载均衡(Global Server Load Balancing)负责决定将用户的请求引导到哪个区域。
基于 DNS 的全局负载均衡
最常见的方式是通过 DNS 实现。用户访问一个域名时,权威 DNS 服务器根据一定的策略,返回不同区域服务的 IP 地址:
- 基于地理位置:根据用户的地理位置,返回最近区域的地址;
- 基于延迟:根据各区域到用户所在网络的实测延迟,返回延迟最低的区域;
- 基于权重:按照设定的比例在各区域之间分配流量;
- 基于健康状态:某个区域出现故障时,不再返回其地址,将流量转移到其他区域。
基于 DNS 的全局负载均衡有一些固有的局限:
- DNS 缓存:DNS 解析结果会被各级缓存保存一段时间。即使将某个区域从解析结果中移除,已经缓存了旧结果的用户仍然会继续访问该区域,直到缓存过期。设置较短的缓存时间可以加快切换速度,但会增加 DNS 查询的负担;
- 定位不精确:权威 DNS 服务器看到的通常是用户所使用的递归解析服务器的地址,而不是用户本身的地址。如果用户使用了一个远离自己的公共 DNS 服务,可能会被分配到错误的区域。一种 DNS 扩展机制允许递归解析服务器在查询中携带用户所在网段的信息,以改善定位的准确性;
- 粒度粗:DNS 只能决定用户连接哪个 IP 地址,无法进行请求级别的精细控制。
基于任播的全局负载均衡
另一种方式是任播:在全球多个地点宣告相同的 IP 地址,由互联网的路由系统自动将用户的流量引导到网络拓扑上最近的节点。
任播的优点是故障切换速度快:某个节点故障时,只需停止宣告路由,流量会在路由收敛后自动转移到其他节点,不受 DNS 缓存的影响。许多内容分发网络和公共 DNS 服务都采用了任播。
其局限在于,"网络拓扑上最近"并不一定意味着延迟最低或负载最合适,而且路由的变化可能导致正在进行的 TCP 连接被转移到另一个节点而中断。因此,任播更常用于无状态的短连接,或者在边缘节点之后再进行进一步的流量调度。
九、客户端负载均衡与服务网格
集中式负载均衡的问题
在微服务架构中,服务之间存在大量的相互调用。如果每个服务之间的调用都经过一个集中式的负载均衡器,会带来几个问题:
- 每次调用都多了一跳网络转发,增加了延迟;
- 集中式负载均衡器需要承载所有服务间的流量,容易成为瓶颈和单点故障;
- 每增加一个服务,都需要配置相应的负载均衡规则。
客户端负载均衡
客户端负载均衡将负载均衡的逻辑放在调用方:
- 服务实例启动时,向服务注册中心注册自己的地址;
- 调用方从注册中心获取目标服务的所有可用实例列表,并订阅其变化;
- 每次调用时,调用方在本地根据调度算法选择一个实例,直接发起请求。
这种方式消除了额外的网络转发,没有集中式的瓶颈,并且调用方可以根据自己观测到的响应时间和错误率,做出更精细的调度决策。
其缺点是负载均衡的逻辑需要集成到每个服务的代码中。如果服务使用多种编程语言开发,就需要为每种语言实现和维护一套客户端库,并保持它们的行为一致。
服务网格
服务网格(Service Mesh)试图兼得两者的优点:将负载均衡等网络功能从应用代码中剥离出来,交给一个与应用部署在一起的边车代理(Sidecar Proxy)处理。
- 每个服务实例旁边都运行一个代理进程,服务的所有入站和出站流量都经过这个代理;
- 代理负责服务发现、负载均衡、健康检查、重试、超时、熔断、TLS 加密、流量统计等功能;
- 一个集中的控制平面负责管理所有代理的配置,下发路由规则和策略。
服务网格使得负载均衡和流量管理与编程语言无关,可以统一配置和管理。代价是每个服务实例都需要额外运行一个代理进程,消耗一定的 CPU 和内存资源,并且每次调用都要经过两次代理转发,增加了少量延迟。为了降低这些开销,一些较新的方案尝试将部分功能下沉到节点级别的共享代理或操作系统内核中实现。
十、实践建议
选择合适的层级。 如果只需要简单地分发 TCP 连接,并且追求极高的性能,四层负载均衡就足够了;如果需要基于内容的路由、TLS 终结、请求级别的分发或精细的流量控制,则需要七层负载均衡。
根据负载特点选择调度算法。 请求处理时间相近、服务器性能相同时,轮询简单有效;请求处理时间差异较大时,最少连接或基于响应时间的算法更合适;多个负载均衡器实例并行工作时,两次随机选择是一个稳健的默认选择;需要缓存亲和性时,使用一致性哈希。
认真设计健康检查。 健康检查是负载均衡可靠性的基础。检查要能真实反映服务的可用性,同时避免因共享依赖的故障导致所有实例被同时摘除。合理设置检查间隔、超时时间和判定阈值,在快速发现故障和避免误判之间取得平衡。
尽量让服务无状态。 无状态的服务能够充分发挥负载均衡的灵活性。会话数据应当存放在外部存储中,而不是依赖会话保持。
实现优雅的上下线。 服务应当支持优雅停止,配合负载均衡器的连接排空机制,确保发布和缩容过程中不丢失请求。新实例上线时,使用慢启动或预热机制,避免冷启动时被流量压垮。
谨慎配置重试。 只对幂等请求进行重试,限制重试的次数和比例,在重试之间使用退避策略,防止重试风暴放大故障。
确保负载均衡器自身高可用。 负载均衡器本身不能成为单点故障,需要通过主备、集群或任播等方式实现冗余,并定期演练故障切换。
做好监控。 监控各后端服务器的请求量、响应时间、错误率和连接数,以及负载均衡器自身的资源使用情况。负载分布的明显不均,往往是调度算法不合适、会话保持导致倾斜或某台服务器性能异常的信号。
结语
负载均衡是构建可扩展、高可用服务的基础设施。它看似只是一个简单的流量分发问题,背后却涉及网络协议的各个层级、调度算法的理论分析、分布式系统中的信息不对称,以及故障检测与恢复的种种权衡。
从四层的高性能数据包转发,到七层的精细内容路由;从简单的轮询,到考虑实时负载的两次随机选择;从数据中心内部的服务器调度,到跨越全球的区域流量引导;从集中式的负载均衡器,到嵌入每个服务实例的边车代理,负载均衡技术随着系统规模和架构的演进而不断发展。
在实际系统中,往往需要将多种层级、多种算法、多种部署方式组合使用。理解它们各自的原理、适用场景和局限,才能根据具体的需求设计出合理的流量架构,让系统在面对增长的流量和不可避免的故障时,依然保持稳定可靠。
- 点赞
- 收藏
- 关注作者
评论(0)