Skip to content

无状态高可用:负载均衡

About 4075 wordsAbout 14 min

架构新书

2026-10-16

随着本世纪初互联网的迅速发展,基于互联网的访问流量迅速增长,特别是访问大型网站,大型电商,以及物联网。不同于传统企业应用的固定流量,这些基于互联网的服务的流量增长速度快,流量波动较大,难以预测,需要一套把流量分配到服务节点集群的方法,称之为负载均衡。

负载均衡,是用于将请求的分配到特定的后端部署的多个服务上,如下负载均衡例子

  • 基于HTTP的请求,访问大型网站和电商
  • 设备对物联网的的MQTT请求,或者基于其他协议的TCP、UDP请求
  • 后端服务通过数据库代理访问数据库集群
  • 任务分配到多个节点执行。
  • 用于容灾的异地三中心,用户就近访问本地集群,本地区域级别灾难时后跨地域访问。

为了实现负载均衡,将请求分配到服务节点(也有可能是灰度测试专用节点),有9种负载均衡方式。实际业务系统会使用其中一种或者多种混合

分配方式描述
客户端路由客户端从后端地址列表中,通过一定的算法(如随机算法),选出一个后端地址访问。Zookeeper客户后端采用此方式。后端地址列表可以预先配置,或者访问某个后端服务获取。
DNSDNS把域名或者后端服务名翻译成后端的服务地址。 智能DNS根据用户IP,地域等来源等信息,返回最近的IP地址。异地三中心使用智能DNS定位到可访问的服务器集群
负载均衡器LB这是最常见的负载均衡方式,服务端对外地址是负载均衡器地址,负载均衡器将客户端请求转发给后端服务。常见的四层负载均衡器LVS,7层负载均衡器Nginx。负载均衡器既可以通过F5这样的硬件实现,也可以通过LVS,Nginx,HAProxy这样的软件实现。
API网关同负载均衡器,部署在客户端和服务端之间。后端服务是微服务集群,API网关将HTTP请求转化为后端微服务请求。API网关能提供认证鉴权,限流熔断的微服务功能
服务发现为服务框架中,服务端地址列表是动态变化的场景。服务端启动时候自动注册自己的服务地址,客户端能查找出所有服务的地址列表并通过一定算法选出一个后端访问地址。如Dubbo的服务发现机制。
资源发现服务同动态服务注册类似,服务端启动后自动注册动服务地址,客户端需要查询专门的资源发现服务,获取一个后端访问地址。资源发现服务可以依据各种策略返回地址,如物联网中大家的电冰箱和小家电的扫地机从主网关获得不同的物联网那云端访问地址。
服务重定向客户端访问任意服务端后,服务端根据请求信息,要求客户端重新访问一个新的后端服务地址。比如HTTP3XX 的临时或者永久重定向。这适合路由业务复杂,通过中间件或者客户端解决不了的分配场景.
消息中间件Consumer集群通过订阅消息中间件Topic,能实现负载均衡。除了消息中间件外,还有ESB企业总线也能实现功能。
ECMPECMP(Equal-Cost Multi Path Routing,等价多路径路由协议),用于局域网内负载均衡器自身的集群。此通常是运维或者云厂商配置并部署,不再本书讨论范围。

客户端路由

客户端通过配置好的服务端地址列表,选择一个服务地址访问,如下是ZK客户端使用Curator访问ZK集群

CuratorFramework client = CuratorFrameworkFactory.builder()
    .connectString("zk1:2181,zk2:2181,zk3:2181")  // 三个节点随机选择一个
    .build();
client.start();

有些产品的物联网中,也采用这种方式,设备首次激活时候,用户会指定设备所在区域,如中国区,然后将设备和区域发到服务端,服务端返回一个适合此设备访问的MQTT Broker地址

DNS

Domain Name System,域名系统,用于将人类容易记住的域名翻译成机器能用的IP地址,其中智能DNS或者GSLB,能根据用户的地域,运营商信息,把域名翻译成最接近用户的集群IP址。下图是一个典型的异地三中心的架构,本地用户通过智能DNS获得了访问本地机房的服务集群的负载均衡器的IP地址。

负载均衡器

负载均衡器是如今最经常使用的负载均衡方式,通常分为硬件负载均衡器和软件均衡器,前者最出名的是F5,后者则是LVS,HAProxy和Nginx。负载均衡器可以作用在OSI传输层上,即根据客户端请求的IP和端口,从后端选择一个节点。也可以作用在OSI的应用层上,根据协议信息,比如HTTP的URL,HTTP Header等信息选择后端节点。下图是一个混合使用了LVS和Nginx的集群

上图首先使用了LVS作为集群入口,当用户通过HTTP访问时候,将路由到Nginx集群,当用户使用FTP协议访问时候,将路由到文件下载服务器。

Nginx作为应用层负载集群,可以把不同URL地址请求发送到不同的后端集群中,如上图的支付服务和库存服务

# 上游服务组定义(后端服务地址)
upstream pay_backend {
    server 192.168.0.11:8080 ;
    server 192.168.0.12:8080;   # 多个节点自动负载均衡
}

upstream stock_backend {
    server 192.168.0.21:8080;
    server 192.168.0.22:8080;
}

# 入口 server
server {
    listen       80;
    server_name  api.example.com;

    # ---------- 按前缀路由 ----------
    # /pay 开头的请求 → 支付服务
    location /pay/ {
        proxy_pass http://pay_backend;   
    }

    # /stock 开头的请求 → 库存服务
    location /stock/ {
        proxy_pass http://stock_backend;
   
    }

}

注意LVS自身高可用需要通过Keepalived软件实现,Keepalived 是一款使用 C 语言编写的路由软件。它为 Linux 系统以及基于 Linux 的基础设施提供简洁可靠的负载均衡与高可用能力,目前已在全球的数据中心、互联网服务提供商及硬件厂商的生产环境中广泛部署。

除了分配流量使用负载均衡器外,流量管理(限流,灰度),流量的可观测,以及安全相关(比如SSL终结,防止Dos攻击)功能,负载均衡器都可以实现,这也是为什么负载均衡器是现在分布式系统常用的负载均衡方式。

API网关

API网关与Nginx类似,它是在微服务流行的的情况下诞生,其后端服务不仅支持HTTP服务,也支持基于RESTFull,Dubbo或者gPRPC等协议的微服务,也提供了更强大的流量管理,安全和审计,以及可观测性功能。常用的API网关有Apache APISIX,Spring Cloud Gateway,Kong等。

下图展示了在Nginx 后。部署多个API网关实例,API网关支持HTTP转Dubbo调用,也支持Dubbo调用转HTTP

服务发现

微服务框架中的服务,通过将自身服务地址注册到ZK或者Nacos上,客户端可以从ZK或者Nacos上获取所有服务的地址列表,并选择一个地址调用。以Dubbo为例子,服务发现如下图所示

![](./images/ balance_discover.png)

Consumer: Dubbo客户端,能从注册中心获取到所有可用Provider的地址列表

Providder:Doubbo服务端,提供服务,并能像注册中心注册自身

注册中心:ZK、Nacos或者etcd等实现,用于存储Provider地址,以及检测Provider健康,当Provider变化时候通知Consumer。

需要注意的是在微服务架构中使用注册中心,注册中心本身的必须高可用。比如使用ZK适合大多数注册中心应用场景,但因为ZK集群存储数据是强一致(将在9.5说明),当大量Proivder注册时候(比如集群网络断后恢复),ZK成了性能瓶颈导致Provider集群启动较慢,甚至超时,这时候必须手工滚动重启Provider以避免注册超时。

资源发现服务

资源服务发现是提供专门的服务用于查找服务地址,客户端在调用后端服务前,需要查询此服务获得后端服务的地址。比如物联网的主网关就是用于返回设备一个接入网关址。

1 )设备请求主网关,传入设备ID,设备产品类型等信息

  1. 主网关根据设备信息,从可用的接入网关列表中选择一个可用的接入网关,地址返回到设备

3)设备根据接入网关地址访问网关,实现设备控制,属性上报等功能

这种负载均衡方式通常是特定的业务条件决定,如根据设备的产品类型和是否是VIP用户的设备等,Nginx或者API网关无法实现或者无法通过插件实现。

传统的JavaEE,也是查询JNDI,获取数据库连接池,EJB,JMS等资源地址。

服务重定向

有些业务场景,只有服务本身才知道服务的正确地址在哪里,负载均衡器或者客户端都无法判断。比如HTTP重定向协议,服务端返回一个3XX Code,并给与一个永久或者临时的服务地址供客户端访问。可以通过后端服务节点实现。

还有个例子是物联网中,通过手机下发设备控制命令,服务端集群的A、B、C 3个节点,其中A节点收到命令,将此命令转发到物联网并同步阻塞,等待物联网响应。

设备的响应如何从服务端集群中找到此服务节点,一种做法是随机发送给任何一个服务端节点,比如C节点,C节点在查询此设备在哪一个服务端实例上等待响应(此设备在A节点下发命令后,在Redis记录一条数据),然后C节点告知物联网重新发送到A节点,或者为了性能考虑,C节点直接转发设备响应到A节点,如下图所示

消息中间件

消息中间件也是常用的一种负载均衡器,比如应用产生的计费事件,发送给给消息中间件,计费服务会订阅这些事件并按照计费策略计算费用。通过横向扩展订阅服务可以提高其高可用。如果消息中间件提供了持久化存储能力,还能使得此计费事件即使消息中间件或者计费服务重启,也不会丢失。

ECMP

前面提到的使用负载均衡器可以保证后端服务高可用,但如何保证负载均衡器高可用呢,一种常见做法是使用ECMP协议,ECMP作用于OSI模型的网络层,核心能力是实现了等价多路径负载均衡和链路备份的目的。ECMP应用于多条不同链路到达同一目的地址的网络环境中。ECMP可以在该网络环境下同时使用多条链路,支持ECMP的有各个厂商的交换机或者使用Quagga等软件实现

在上面的9种流量分发的方式中,客户端路由,负载均衡器,API网关或则服务发现等,获得后端服务的地址列表后,如何决定流量分发给哪一个看后端服务,这通常有如下算法

算法描述
随机 Random最常用的算法,调用量越大,分配越均匀。
轮训 RoundRobin依次选择后端服务分配流量。
最少连接 Least Conn算法会优先选择一个最少连接的后端服务。最少连接的后端服务,意味着此后端服务处理能力强,可优先分配流量到此节点。此算法有可能产生羊群效应,即短时间内,大量流量分配到同一个节点
Power of Two ChoiceDubbo和Nginx提供的一种算法,会从俩个随机节点中,选出一个Least Conn节点。此算法避免了Least Conn产生的羊群效应
HASH根据请求的来源IP或者URL,或者其他从请求中提取的参数,取HASH值后路由到后端。使用HASH算法保证这类请求总是会路由到同一台后端服务器上。
一致性HASHHASH算法在后端频繁上下线情况下,同样请求会路由到不同后端服务上,为了避免这种情况,一致性HASH能实现只有少量Key的请求会重新分配到不同后端。关于HASH和一致性HASH,将在有状态服务中《分区》详细介绍。
least_time选择平均响应时间最小的节点,Nginx支持这种模式
First 顺序填满HAProxy支持的一种负载均衡算法,优先流量导向第一个节点,直到连接数达到配置最大值才切换下一台。优势是节约成本,尽量少启动服务器。缺点是流量集中前面几台节点,不适合高可用。

对于大多数业务场景,负载均衡算法选择随机或者轮训即可,对于有状态服务。 需要实现粘性连接(参考10.5),可以配置HASH算法或者一致性HASH。这4种负载均衡算法所有的负载均衡软硬件都支持。

如果后端节点的主机配置不同,处理能力不同,还可以针对随机和轮训算法,实现按照权重分配

权重算法说明
加权随机按照权重设置随机概率,比如一台8C的服务器和2台4C的服务,可以设置8C的权重更高,比如(8C,4C,4C)的权重为(20,10,10)
加权轮训按照权重分配流量,比如设置(8C,4C,4C)的权重为(20,10,10) 。需要注意,在轮训过程中,如果某一个节点权重过高,可能导致连续分配多个流量到此节点。因此,实际会使用平滑加权轮询算法。即刚被分配流量的高权重节点的权临时下调权重以避免下次流量还被分配到此节点

有些算法能感知到后端节点的负载,将流量优先分配给负载较低的服务节点,从而使得集群拥有更大的吞吐量

根据后端负载说明
基于后端服务的负载后端节点主动上报自己的负载,如Apache的HeartBeat模块主动上报CPU,连接数;或者后端服务的流量控制信息如Dubbo的Adaptive Load Balance)来分配流量
BUSYOVER 忙碌转移用在xxx-job分布式任务调度上,能感知到执行器(节点)线程池是否空闲,找到第一个空闲机器下发任务

HAProxy 和Nginx商业版本还可以配置让新节点刚开始只接收少量流量以让后端预热。如下Nginx指定了2节点使用默认的平滑加权轮询,且权重都为10. 其中0.1节点的流量预热30秒

upstream backend {
    server 10.0.0.1:8080 weight=10 slow_start=30s;
    server 10.0.0.2:8080 weight=10;
}

知行合一