负载均衡系统

负载均衡系统设计合集

负载均衡简介

负载均衡 将超过单节点承受能力的流量分配给多个节点

要点

  1. 服务发现 - 如何识别或路由到最终的业务应用
  2. 产品选型 - 采用什么产品、硬件、架构
  3. 负载算法

服务发现

即关于负载均衡的路径信息该在哪个节点维护

一般来说数据流有 3 个区域: 客户端 -> 负载均衡器(lb) -> N 个上游服务器(service)

方案一: 在客户端配置

即最终访问哪个节点是由客户端决定的,有多种细分方案

  1. 客户端硬编码;
    客户端有一个服务列表,每次请求按设定规则随机或轮询选择一个地址进行访问;

  2. 客户端指定服务器转发;
    客户端在http请求 的 header 头增减一个决定路由的字段, 服务器端LB再根据这个字段进行转发到指定服务器;

  3. 服务接口列表:
    客户先请求一个服务器接口, 获取一个可用地址列表, 然后选一个再发起业务请求, 类似 DNS或注册中心的机制;

优点: 简单, 可控, 客户端调试时很容易定位到这个请求是哪个服务器处理的;
缺点: 极端情况下变更服务节点可能需要同时改这三个区域的配置;

方案二: 在 LB 上配置

很好理解, 传统的 nginx, haprox 等都是这样做的;

即 LB 根据自身独立的负载规则转发到后端应用;

优点: 好理解, 可控程度高

缺点: 后端变更后(如新增节点), 需要再改一次 lb 的配置

方案三: 在后端服务上配置

核心是 lb 动态获取后端服务的变化, 有多种细分实现方法;

  1. 方法一: lb 提供一个接口, 后端应用服务上线时主动去注册, 下线时主动注销;

  2. 方法二: 有一个公共注册中心或配置中心(etcd, consul, redis, eureka, mysql 等),
    service 去注册和注销, lb 动态去拉取更新, 或者是 lb 进行 watch 监听;

    这两个方法都存在的问题: 程序异常停止没主动下线时的问题, 需要配合健康检查;

  3. 方法三: 和方法二差不多, 但 lb 不是监听注册中心, 而是监听 docker, kubernetes 等组件的 api, 服务一有变动立即可知;
    程序内部不需要引入注册类的库, 历史老旧应用无需改造即可适配, 特别适合云原生环境;

  4. 后端服务器重定向到目标地址。

优点: 一次变更只需要后端的服务改一次
缺点: 假设某个节点服务异常, 希望隔离流量待排查, 而不是直接下线服务时, 比较麻烦;

方案选择

根据实际情况选

  1. 绝对性能型 - 优先客户端负载
  2. 方便可控型 - 优先 lb 配置
  3. 自动化程度要求高 - 就由后端配置
  4. 如果是云原生或微服务场景也更推荐由后端应用控制

负载均衡技术栈选型

负载均衡的一般实现方式

1. 七层实现

如 nginx, HAProxy,traefik,硬件负载均衡器;

一般承载协议:HTTP, HTTPS, WS等

优点: 功能丰富, 灵活方便, 插件支持;如果不是性能和协议不满足,几乎不用考虑别的方式。

2. 四层实现

承载协议:TCP, UDP

实现:如 lvs, 硬件负载均衡器

优点: 性能好,稳定性高

3. 三层实现

一般靠交换机或路由器等网络设备去实现;

采用等价路由, 流量镜像, 源 IP HASH 等方式, 在网络层按 IP 地址进行分发;

特别大流量时可以考虑 OSPF+LVS 技术栈。

4. 二层负载

原理: 后端多个服务器配置相同的 IP 地址,LB 通过改写数据帧的 MAC 地址来和后端通信

例如:交换机的端口聚合

5. DNS 负载均衡

通过 DNS 服务器给客户端响应不同的 IP 地址 或 多个IP地址的方式, 以达到负载均衡的效果;
在单个入口性能达到瓶颈时, 非常有效;

如果使用智能 DNS, 还可以做到如:

  1. 不同区域的用户请求不同地址, 做到区域划分, 就近访问, 提高不同区域的响应性能;
  2. 也可以将静态资源解析到 CDN, 动态资源解析到主入口;
  3. 配合健康检查做故障转移, 早期的DNS服务一般不支持探活,但目前很多也开始支持了,例如某些厂家推的全局DNS。

缺点: 使用 DNS 就绕不过去客户端缓存问题, 于是配置更新生效的时间就不稳定;

6. 客户端负载均衡

方法一: 客户端有一个IP或域名列表,随机或其它方式选择一个目标地址进行请求;

方法二: 客户端先请求或监听某个接口, 获取一个 ip 列表, 然后再发起请求。

7. 基础网络协议

ipv6 的任播(Anycast):
通过路由寻址协议,可以把 包 发给最近或响应最好的一台设备上;

IPv4 的组播:
例如多个后端节点监听同一个组播地址, 但根据源IP hash 来决定自己是否处理这个包,也达到了类似效果。

选型时功能要求

1. 核心要求

  • 性能需求
  • 稳定性要求: 有没有经过大厂考验
  • 协议支持; 如 TCP,UDP,HTTP,HTTPS,WebSocket 等
  • 负载均衡算法支持
  • 健康检查策略: 主动检查和被动检查
  • 有没有扩展为 API 网关或业务网关的规划
  • 动态服务发现功能
  • 热更新: 如配置热更新,以及程序自身的热更新

2. 辅助功能要求

  1. https协议支持程度, ssl 卸载,ssl 算法调整
  2. 安全性要求: 黑白名单, 动态黑白名单
  3. 扩展性,插件支持
  4. 多节点管理方便程度

3. 管理类功能

  1. http 重定向 https: 避免进入到应用内再进行重定向
  2. 添加 XFF 头
  3. 修改请求 url
  4. 丰富的日志输出
  5. 监控需求, 有些是带监控面板, 有些是暴露了一些指标供外部采集;

按业务场景选择

业务量较低

更需要关注功能丰富而非性能,日 PV 小于 1000 万时, 推荐 nginx, traefix 等 7 层组件;

业务量中等时

需要开始考虑性能, 可以考虑 lvs+nginx;

业务量很高时

  1. 分拆入口: 如 DNS 多 IP 地址轮询, 或者 二级域名 image.wait.com voide.wait.com, 或用户分区;
  2. 不能拆入口时: 网络层处理流量负载(如ospf), 硬件负载设备处理业务负载;

业务量特别大

基于网络层的地址多路由; 即同一个 IP 地址不同区域解析到不同服务器上,例如泛播

常用产品分析

nginx

  • 一般业务场景足够, 高性能;
  • 功能丰富, 成熟, 通用度高

haproxy

  • 早期的产品, 支持四层和七层, 功能丰富
  • 除非特定生态, 现在一般不用

traefik

  • 特点在于动态发现后端服务变更
  • 适合容器化场景

lvs

  • 更适合做 4 层的负载
  • 属于内核级, 性能好, 稳定高, 资源使用率低, 没有多余的流量产生,
  • 有一个 fullnat 模块, 增加了 local address

ospf+lvs

  • 先在交换机上进行一次流量负载

端口聚合

  • 单台主机和交换机之间, 通过端口聚合, 可以扩大带宽并且线路冗余

优化项

  1. 服务器直接返回请求
    用户的请求进来时需要穿越多层负载, 为避免响应的包也回溯多层,
    可以将响应包由后端直接回给用户,不从 LB 再过一次, 提高性能。
    适合大文件下载的场景;

    例如 cilium 的 DSR模式, lvs 的 DR 模型, 某些硬件负载均衡器的 三角模型

  2. 增加上游服务数量
    一般来说增加服务数量而不是让每个服务处理更多的通信, 可以得到更低的延迟, 和更高吞吐量

  3. 关于 ssl 卸载
    加密解密是很耗CPU的,如果有https大文件下载场景, 最好不要把 ssl 卸载放在 lb 上,影响性能。

负载均衡算法

说明事项

不同模型下对于代理后面的的服务器叫法不一样, 有时候叫 上游(nginx), 有时候叫 后端,有的又叫 RS(lvs 的 RealServer),还有直接叫 server 的,大家知道是同一个东西就行。

简单分类

  1. 静态算法, 固定分发策略
  2. 动态算法(根据状态的不同分配方式由所变化)

静态算法

1. RR (Round-Robin) 轮询

轮流请求后端服务

优点:简单,性能高
特点: 不记录当前连接的状态,是一种无状态调度
缺点: 如果后端资源配置不一样, 容易造成负载不平衡

2. WRR (Weighted Round-Robin) 加权轮询

在 RR 的基础上,可以增加或减少分配给某个后端应用的流量比例;

场景: 应对后台服务性能不一致的场景, 性能好的服务器多处理一点流量;

例如: A=1, B=2;

则请求 1 给 A, 请求 2,3 给 B, 请求 4 给 A, 请求 5,6 给 B

3. random 随机算法

轮询算法实际上还是按列表环进行顺序分配;

而全随机算法, 在数据量足够大的场景下, 能达到均衡分布;

4. ip hash (Source Hashing)

区分2种场景

  1. 使用 源ip地址进行 hash, 一般是处理进来的流量, 源地址散列, 或源地址 HASH, 简称 SH
  2. 使用 目的ip地址进行 hash, 一般是处理出去的流量, Destination Hashing 简称 DH

根据这个请求的源地址进行 hash, 然后从地址列表中找出一个服务器来; 类似与 取模 运算;

优点: 同一个客户端IP的请求始终会落到一个后端节点,便于 cookie 与 session 进行会话绑定;
缺点: 后端新增或减少一个节点时,将有 1/n 流量始终不可用, 如果重启或重载配置, 则原来映射关系就会全部重新排。

5. 一致性哈希

简单理解: 划一个圈, 每个节点占一个范围;

如果有新增服务器, 就圈内找最合适的一个节点, 将它的一部分范围转交给新节点;
也可以是两个相邻节点各出让一部分
如果是某各节点故障, 就把它原来的这个范围, 给它相邻的两个节点进行瓜分;

优点: 可以在客户端实现
缺点: 节点变化时需要操作的内容有些多, 适合在客户端实现, 而不是 LB

6. 序列分配

根据键的序列进行分配; 如 ID 为 1-1000 分到 A 节点, 1001-2000 分配到 B 节点

7. 取模分配

对 key 和节点数进行取模计算后分配到相应节点

动态算法

1. 最小连接数(Least-Connection ) 简称 LC

对当前与后端服务器的连接进行统计, 谁少就把新连接给谁;

适合场景: 下载类的服务,小文件下载连接释放得快

2. 加权最少链接(Weighted Least-Connection) 简称 WLC

在 LC 的基础上增加了权重, 权重高的可以承受更多连接

3. 最快响应模式

传递连接给那些响应最快的服务器

4. 其它算法

还有一些 lvs 的不常用算法, 比较复杂,特定场景下可能有用

LBLC(局部性的最少链接): 先去目标ip的最近调度服务器, 如果不可用, 再使用 lc 算法;

LBLCR(带复制的基于局部性最少链接): 比较复杂。先去一个目标服务器小组, 按lc算法选一个节点; 如果节点不可用再选;
SED(最短期望延迟),
NQ(Never Queue)
最快响应模式

5. 自定义控制

如根据监控系统内的 CPU 使用率和连接数,当前大事务量等, 来改变注册中心内配置的权重
劣势: 实现复杂
优点: 非常灵活

获取真实源IP地址

一般来说普通 http 场景,推荐使用 xff 头的方式获取IP,

如果是 tcp 或 udp,先看是否支持 proxy protocol 协议,然后再考虑 lvs。

简述

  1. 从 xxf 头部获取
  2. 四层从 proxy protocol 协议或者 tcp option 字段
  3. 三层使用透明模型
  4. 自定义规则, 需要客户端支持

一、从应用层获取

1. http协议规范的xff头部

如 http 协议约定, X-FORWARD-FOR 头部用于存放源IP地址

如 nginx 一般配置

1
2
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

优点: 对网络架构要求低, 配置简便

缺点: 容易被伪造

2. 业务程序自行实现

即程序自行实现, 例如 client 端将源IP插入到报文正文内某字段, 再带到 server 端。

优点: 对网络架构无要求, 只要网络可通即可, 只要安全做好, 不容易被伪造

缺点: 需要改造旧的客户端和服务器

二、从四层获取(TCP/IP 也就是端口这一层)

1. tcp option 字段

原理: 在 4 层报文的 option 字段里增加源 IP 信息, 比如 tcp option, udp option,即 L4 的 toa 模式;

注:
toa 是指在 tcp option 里增加源 IP 信息;
uoa 是指在 udp option 里增加源 IP 信息;

实现: lvs-fullnat(只支持 toa), iqiyi/dpvs(支持 toa 和 uoa);

后端应用程序也要加载 toa, uoa 模块, 是用于替换后端应用程序获取 IP 时的系统接口的钩子。

优点: 对网络架构要求低。

缺点: lvs-fullnat 需要编译内核, 且多年未更新;
iqiyi/dpvs 功能强大, 但需要 dpdk 的支持;
而且都需要后端程序需要加载 toa/uoa 模块, 且只支持 linux 系统。

2. proxy protocol 协议支持

原理:
即在三次握手结束后的第一个数据包的头部插入一段数据, 记录着源IP地址, 这段数据在 4 层末尾和 7 层开头之间。

这项协议最早是 haproxy 开发推广的, 目前主流 proxy 应该是都支持了;

目前有 v1(明文)和 v2(二进制)两个版本;

优点: 网络侧不需要改造什么, 只要应用软件支持即可。

限制条件:

  1. 需要负载均衡器和后端同时开启 proxy protocol 协议,不能只开一侧;
  2. 支持此协议的后端应用比较少;
    已知支持此协议的软件: haproxy, nginx, apache、traefik、squid、mysql等,
  3. 已经支持的 LB 还得看支持什么版本,如阿里云的CLB只支持V2版本,
    nginx 早期只支持 v1 版本,1.13.11+ 才支持 v2 版本。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
nginx 配置示例

插入配置
server {
    listen              12345;
    proxy_pass          backend.example.com:8080;
    proxy_protocol      on;
}

接收配置
server {
    listen 80   proxy_protocol;
    listen 443  ssl proxy_protocol;

    real_ip_header  proxy_protocol;
    proxy_set_header X-Forwarded-For $proxy_protocol_addr;
}

三、从三层获取(IP层)

1. 转发模式

如果 LB 采用一次连接模式, 即只转发包,非代理模式时, 可以在网络层不对源 IP 做修改, 直接将数据包转发给后端, 当后端接收到数据的时候, 源 IP 就是真实 IP。

实现: LVS-DR, LVS-NAT, LVS-TUNNEL 模式;

优点: 逻辑简单, 当负载均衡器故障切换的时候, 从客户端到后端的 tcp 连接不会中断

缺点: 对网络架构有要求, 例如

LVS-TUNNEL 比较特别, 走的是隧道, 在原有数据包的开头封装了 IP 头, 当后端收到数据的时候, 将封装的 IP 头进行解封装, 获得的就是原始数据包,当然包括真实的 client IP。但是要后端也支持 ip 隧道。

DR 模式, 要求后端配 VIP, 并且回包要能直接回到客户机;

NAT 模式, 要求回包经过负载均衡器;

2. 代理模式

如果 LB 采用二次连接模式, 如 haproxy 的透明模式。

是指负载均衡器和后端会重新进行三次握手, 但保持数据包的源 IP 为真实 IP,类似于 F5 的透明模式。

实现原理: haproxy(开启 tproxy 透明代理模式)+ iptables(fwmark 打标记)+ 策略路由这 3 者组合才能实现。

优点: 可以实现 L7 层(如 HTTP/HTTPS)的负载均衡, 而一次连接方式主要是实现 L4 层的负载均衡。

缺点: 配置最复杂, 同时要求回包经过负载均衡器


LB 的一次连接和二次连接模式

一次连接:
负载均衡器对数据包仅做转发, 而不对后端重新发起三次握手, 类似交换机, 不记录状态;

二次连接: 和一次连接相对应, 在 tcp 转发时候, 对后端重新进行了三次握手,代理模式,有状态记录;

不严谨的话可以通过简单对比源端口是否有改变来简单判断是一次连接还是二次连接,
端口没改变, 可以理解为一次连接, 有改变就是二次连接

lb的替代方案

使用队列来替代负载均衡, 改用生产者消费者模型, 去除了转发算法

Licensed under CC BY-NC-SA 4.0
转载或引用本文时请遵守许可协议,知会作者并注明出处
不得用于商业用途!
最后更新于 2023-03-02 00:00 UTC