SLB 是一种针对 UDP 流量的无会话负载均衡器,解决了针对此类流量使用传统(功能丰富)负载均衡器所固有的问题。
对于简单、无状态的 UDP 协议,尝试维持客户端和后端实例之间的“亲和性”(也称为“会话”)没有任何优势。传统的负载均衡器认为关联性很有帮助,因此它们会尝试将数据包从客户端路由到一致的后端服务器。相比之下,SLB 在所有可用后端上均匀(随机)地分发数据包。这会导致后端负载均匀,并在一个后端实例发生故障时提高鲁棒性(所有客户端的数据包丢失都会增加,而不是某些客户端的流量全部丢失)。
默认情况下,SLB 将在端口1812
和1813
上侦听传入的 UDP 数据包,并将它们中继到它知道的随机后端目标。它侦听的端口可以使用--server-port-range
选项,该选项接受单个端口(例如541
)或一系列端口(例如4000-5000
)。
要使 SLB 感知后端,需要将“看门狗”(又名“保持活动”)数据包发送到管理端口(更多内容见下文)。默认情况下,管理端口为1111
,但可以使用--admin-port
选项进行配置。如果您的系统中存在多个网卡,您可以使用--admin-ip
选项指定 IP。如果使用--admin-ip
指定的 IP 在多播 CIDR 范围 ( 244.0.0.0/4
) 内,SLB 将自动加入该多播组(更多信息见下文)。
其他选项在命令帮助中描述:
SimplestLoadBalancer:
Sessionless UDP Load Balancer sends packets to backends without session affinity.
Usage:
SimplestLoadBalancer [options]
Options:
--server-port-range <server-port-range> Set the ports to listen to and forward to backend targets
(default "1812-1813") [default: 1812-1813]
--admin-ip <admin-ip> Set the IP to listen on for watchdog events [default is first private IP]
--admin-port <admin-port> Set the port that targets will send watchdog events [default: 1111]
--client-timeout <client-timeout> Seconds to allow before cleaning-up idle clients [default: 30]
--target-timeout <target-timeout> Seconds to allow before removing target missing watchdog events [default: 30]
--default-target-weight <default-target-weight> Weight to apply to targets when not specified [default: 100]
--unwise Allows public IP addresses for targets [default: False]
--stats-period-ms <stats-period-ms> Sets the number of milliseconds between statistics messages printed to the
console (disable: 0, max: 65535) [default: 1000]
--default-group-id <default-group-id> Sets the group ID to assign to backends that when a registration packet doesn't
include one, and when port isn't assigned a group [default: 0]
--version Show version information
-?, -h, --help Show help and usage information
后端不是在命令行中配置的。相反,它们使用发送到管理端口 ( --admin-port
) 的定期 UDP 数据包动态注册和取消注册。这些数据包的内容可能会有所不同,具体取决于您在环境中使用 SLB 的方式。
如果您运行一台 SLB 服务器,则可以将后端配置为将数据包发送到该 IP 和管理端口。这是最简单的场景。每个后端将发送带有两个“魔法字节”的消息来指示内容的“后端注册”:
0x11 0x11
SLB 会将此类数据包解释为“将发送方注册为后端”。可选地,消息可以包含一两个附加字节(权重和组ID),其目的将在下面更详细地讨论。
0x11 0x11 [X] [X]
^ ^
| |
| one byte for group id
|
one byte for weight
在某些环境中,注册数据包不会从后端本身发送,并且 SLB 支持此类用例。当从“第三方”发送注册数据包时,内容需要包含正在注册的后端的 IP 地址:
0x11 0x11 X X X X [X] [X]
^ ^ ^
| | |
| | one byte for group id
| |
| one byte for weight
|
four bytes for ip to add
同样,可以选择性地附加权重和组ID字节。
当需要使用多个 SLB 进行更强大的 HA 部署时,可以通过使用多播组 IP 来简化后端和 SLB 之间的通信。这很有帮助,因为每个 SLB 必须了解每个后端。在这种情况下,SLB 服务器应使用--admin-ip
选项来指定多播地址,这将导致 SLB 加入多播组,从而全部接收发送到该 IP 的任何消息。后端可以使用该单个 IP 进行配置,从而最大程度地减少其工作负载并简化其配置(特别是当 SLB 由于自动扩展和/或使用 Spot 实例而轮流提供和停止服务时)。
请注意,使用多播 IP 需要支持多播的交换机,或者(更有可能)在配置了多播域的 AWS VPC 中运行。
从 2.0 版本开始,管理数据包格式非常简单。在最简单的单 SLB 用例中,来自后端的注册数据包可能只包含两个幻字节 ( 0x11
0x11
)。或者,数据包可以来自不同的源(例如管理服务器)并包含四个字节来指定后端的 ipv4 地址。在任何一种情况下,都可以附加两个额外的可选字节,用于相对于其他后端的流量“权重”,以及分配给后端的“组”(下面将详细介绍组)。在 ASCII 艺术中:
0x11 0x11 [X X X X] [X] [X]
^ ^ ^
| | |
| | one byte for group id
| |
| one byte for weight
|
four bytes for ip to add
要立即删除目标,请发送第一个字节为0x86
而不是0x11
的数据包(如果从管理服务器发送,请附加要删除的后端的 IP):
0x86 0x11 [X X X X]
^
|
four bytes for ip to remove
权重用于控制传送到每个后端的相对流量。如果未指定权重,则默认值 100(可使用--default-target-weight
配置)将应用于后端,并且每个后端将接收相同数量的数据包。也就是说,预计(并且建议)后端根据其处理流量的能力调整其管理数据包中的权重值(当 CPU 较高、正在应用更新等时可能会减少)。例如:
100
、 50
和50
,那么第一个后端将获得 50% 的流量,第二个和第三个后端将各获得 25% 的流量。31
和31
,则每个后端将接收 50% 的流量。使用组时,相对权重是与同一组中的其他后端(并非跨所有组)进行评估的。
以足够的节奏可靠地发送管理数据包非常重要。每次 SLB 收到数据包时,后端的“最后查看”时间都会更新。如果 30 秒(可使用
--target-timeout
配置)过去了而没有看到后端,则它将被删除,并且不会再向其发送任何流量。
默认情况下,所有后端将用于为负载均衡器服务的所有端口提供服务。
但是,可以使用 SLB 端口分配消息并在注册消息中提供组 ID 将各个端口分配给后端子集。例如,假设您希望对端口 1812-1813 进行 SLB 负载平衡流量,但将到达每个端口的流量分配给一组不同的服务器。为此:
x66 x11
)。这些消息不需要重复,并且可以在需要更改端口组分配时发送(但是,重复这些消息没有坏处,这可以方便地确保在服务重新启动后端口被正确分配到组)。 0x66 0x11 X X X
^ ^
| |
| one byte for group ID
|
two bytes for port number, litten endian
使用 Linux bash
发送管理数据包非常简单。这可以使用netcat
(又名nc
)命令或/dev/udp
文件系统来完成。例如,如果您的负载均衡器正在侦听默认管理端口1111
并且您想要添加 IP 为192.168.1.22
目标:
$ echo -e $( echo " x11x11 $( echo " 192.168.1.22 " | tr " . " " n " | xargs printf ' \x%02X ' ) " ) > /dev/udp/127.0.0.1/1111
由于手动发送这些数据包来注册一组目标可能很乏味,因此您可以创建一个小的 shell 脚本,例如lb.sh
:
#! /bin/bash
echo -ne $( echo " x11x11 $( echo " 192.168.1.22 " | tr " . " " n " | xargs printf ' \x%02X ' ) " ) > /dev/udp/127.1.1.1/1111
echo -ne $( echo " x11x11 $( echo " 192.168.1.23 " | tr " . " " n " | xargs printf ' \x%02X ' ) " ) > /dev/udp/127.1.1.1/1111
echo -ne $( echo " x11x11 $( echo " 192.168.1.24 " | tr " . " " n " | xargs printf ' \x%02X ' ) " ) > /dev/udp/127.1.1.1/1111
echo -ne $( echo " x11x11 $( echo " 192.168.1.25 " | tr " . " " n " | xargs printf ' \x%02X ' ) " ) > /dev/udp/127.1.1.1/1111
echo -ne $( echo " x11x11 $( echo " 192.168.1.26 " | tr " . " " n " | xargs printf ' \x%02X ' ) " ) > /dev/udp/127.1.1.1/1111
echo -ne $( echo " x11x11 $( echo " 192.168.1.27 " | tr " . " " n " | xargs printf ' \x%02X ' ) " ) > /dev/udp/127.1.1.1/1111
然后使用watch
命令每隔几秒调用该脚本:
$ watch -n10 ./lb.sh
适用于 Linux 和 Windows x64 以及 Linux ARM 的预构建二进制文件可作为 GitHub“版本”获得。这是一个非常简单的 .Net 8.0 项目,因此要构建它并运行(假设您已经安装了 dotnet-sdk-8.0):
dotnet build
您可能希望生成本机二进制可执行文件,这很方便并且提供一些性能优势。
对于Linux:
dotnet publish -o ./ -c Release -r linux-x64 /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained
对于 Windows:
dotnet publish -o ./ -c Release -r win10-x64 /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained
同样,在项目目录中使用dotnet run
运行也很简单:
$ dotnet run
或者,如果您已经构建了本机可执行文件:
$ ./SimplestLoadBalancer
请随时在 GitHub 上创建问题以获取问题和错误报告。没有提供模板或要求,但请尝试尽可能具有描述性 - 这将有助于确保我们能够以合理的方式做出回应。另请参阅贡献。
享受!