九月鹰飞
作者九月鹰飞2021-08-05 08:46
其它, 光大银行

Linux主机实现负载均衡的思路、原理及方法浅析

字数 5255阅读 10573评论 0赞 4

前一段 xx 工作期间,由于系统口令复杂度的要求提高,大家需要修改大量服务器口令,由于复杂口令要求有特殊字符,靠人力有点难以生成,于是乎手写了一个生成复杂口令的工具,使用 uwsgi+python 快速上线,小伙伴们用着很 hi ,访问量蹭蹭上涨,无奈虚机配置极低,经常不可用,于是乎正好来一个横向扩展。

1 、利用 iptables 来实现一个简单的负载均衡器,思路如下:


说干就干,在 LB-server 之 nat 表 PREROUTING 链上增加如下配置

-A PREROUTING -d 172.18.0.5/32 -p tcp -m tcp --dport 9009 -m statistic --mode random --probability 0.50000000000 -j DNAT --to-destination 172.18.0.6

-A PREROUTING -d 172.18.0.5/32 -p tcp -m tcp --dport 9009 -j DNAT --to-destination 172.18.0.7

第一条规则意味着有 50% 的几率命中本条记录,在路由前阶段将访问目标地址修改为后端 Realserver1 的地址,第二条则意味着第一条未命中后, 100% 几率将访问目标地址修改问 Realserver2 的地址。

发起测试, client 并未如期获得返回数据,将数据包 dump 后分析发现, Realserver 并未接收到请求数据,负载均衡器接受到了 client 的请求,但并未做出任何回应,网络包数据如下

16:36:29.053158 IP 172.20.0.22.38803 > 172.18.0.5.9009: Flags [S] , seq 3103445218, win 14600, options [mss 1460,sackOK,TS val 3716049980 ecr 0,nop,wscale 7],

16:36:32.060016 IP 172.20.0.22.38803 > 172.18.0.5.9009: Flags [S] , seq 3103445218, win 14600, options [mss 1460,sackOK,TS val 3716050732 ecr 0,nop,wscale 7],

16:36:38.075568 IP 172.20.0.22.38803 > 172.18.0.5.9009: Flags [S] , seq 3103445218, win 14600, options [mss 1460,sackOK,TS val 3716052236 ecr 0,nop,wscale 7],

…………………………………………………..

可以看到 client 正是按照退避指数算法在不停的尝试发起链接( syn 包),而 LB-server 未做出响应,未响应的原因是未开启允许内核转发,修改 net.ipv4.ip_forward=1 后再次发起请求。

然而依旧未成功,再次分析发现,这次 Realserver 已经接收到请求数据,然而 client 端将连接 reset 。

Realserver 数据如下:

16:44:37.501349 IP 172.20.0.22.38811 > 172.18.0.6.9009: Flags [S] , seq 3379421156, win 14600, options [mss 1460,sackOK,TS val 3716246683 ecr 0,nop,wscale 7], length 0

16:44:37.501426 IP 172.18.0.6.9009 > 172.20.0.22.38811: Flags [S.] , seq 421010026, ack 3379421157, win 28960, options [mss 1460,sackOK,TS val 1273447151 ecr 3716246683,nop,wscale 7], length 0

16:44:37.501662 IP 172.20.0.22.38811 > 172.18.0.6.9009: Flags [R] , seq 3379421157, win 0, length 0

Client 数据如下:

15:00:04.996857 IP 172.20.0.22.38811 > 172.18.0.5.9009: S 3379421156:3379421156(0) win 14600

15:00:04.997362 IP 172.18.0.6.9009 > 172.20.0.22 .38811: S 421010026:421010026(0) ack 3379421157 win 28960

15:00:04.997394 IP 172.20.0.22.38811 > 172.18.0.6.9009: R 3379421157:3379421157(0) win 0

可以看到从 client 的视角,请求的目的地址( LB-server 地址)与实际响应的地址( Realserver 地址)并不相同,故链接被 reset ,可以看到问题的根本原因是 realserver 的返回数据并没有经过负载均衡器(反向 nat ),而是直接返回给 client ,想到的第一方法是在 realserver 上增加了一条静态路由, ip route add 172.20.0.22 via 172.18.0.5 ,让其返回给 client 的数据先经过负载均衡器,这下 ok 了。

CLIENT:/tmp # curl -s http://172.18.0.5:9009/suijimima

Realserver1 generate passwd “y05_d=DZ”

CLIENT:/tmp # curl -s http://172.18.0.5:9009/suijimima

Realserver2 generate passwd “U=9mDG!B”

CLIENT:/tmp # sh lb-test.sh

528 requests to Realserver1

472 requests to Realserver2

共发起 1000 次测试请求,负载基本上均衡,略有偏差。

但是还不完美,因为不可能到每个 client 的返回路由都加到 realserver 上(另一方法直接修改 realserver 的默认路由),所以这个方法有点难以实施,于是乎又另辟蹊径,删除 realserver 的静态路由,在 LB-server 之 nat 表的 POSTROUTING 链上上在增加一个 SNAT 的转换规则,

iptables -t nat -A POSTROUTING -d 172.18.0.6/32 -j SNAT --to-source 172.18.0.5

iptables -t nat -A POSTROUTING -d 172.18.0.7/32 -j SNAT --to-source 172.18.0.5

这回完全骗过了 realserver ,所有的请求源地址都为 LBserver 的地址,但同时 socket 层也失去了源地址保持的能力(好在 http 层会有 REMOTE_ADDR )。

此种方案的延伸,如果后端 realserver 数量为 3 ,那么 3 条 DNAT 的几率则应该为 0.33 , 0.5 , 1 , 4 台依此规则重新计算。

基本满足了需求,但是还不够完美!

2 、提到负载均衡完美就不能不说 ipvs ,思路如下:

在 LB-server 上采用 ipvs-nat 模式,增加如下配置

ipvsadm -A -t 172.18.0.5:9009 -s rr

ipvsadm -a -t 172.18.0.5:9009 -r 172.18.0.7:9009 -m

ipvsadm -a -t 172.18.0.5:9009 -r 172.18.0.6:9009 –m

-A POSTROUTING -d 172.18.0.6/32 -j SNAT --to-source 172.18.0.5

-A POSTROUTING -d 172.18.0.7/32 -j SNAT --to-source 172.18.0.5

但是实际上仍然出现 client 端的目标地址( lb-server )与回包地址( realserver )不一致的问题,这个问题 ( 某淘的 Fullnat 版本可以解决,但是性能会更加下降 ) ,本环境暂未实现。 不过在此我们依然可以采用在 realserver 上增加静态路由的方法里验证。

CLIENT:/tmp # sh lb-test.sh

500 requests to Realserver1

500 requests to Realserver2

可以看到, ipvs 模块负载均衡得更完美,但是此种方案与 SNAT 并无本质上的区别,如果后端 realserver 数量较多则性能有较大影响,实现中最好将 realserver 的默认网关指向 LB-server (靠添加静态路由是不现实的)。

IPVS 提供了另一种模式叫做直接路由(修改 mac 实现),要求 lb-server 与 realserver 在同一网段,正好符合我的测试条件,于是乎又改了一番。

ipvsadm -A -t 172.18.0.5:9009 -s rr

ipvsadm -a -t 172.18.0.5:9009 -r 172.18.0.7:9009 –g

ipvsadm -a -t 172.18.0.5:9009 -r 172.18.0.6:9009 –g

同时需要在 Realserver 的 lo 接口上配置 LB-server 的服务地址(需要自己骗自己),并且需要修改 arp 请求响应的模式

echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore

echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce

echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore

echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce

ifconfig lo:1 172.18.0.5 broadcast 172.18.0.5 netmask 255.255.255.255 up

route add -host 172.18.0.5 dev lo:1

此种模式性能较高,但是配置复杂,网络环境要求更为苛刻。

3 、以上两种方法有那么一点略显凌乱,难以理解,使用起来也不那么方便。说道负载均衡我们又不得不提第三个利器------ NGINX

upstream mimashengcheng {

server 172.18.0.6:9009;

server 172.18.0.7:9009;

}

location /suijimima {

proxy_pass http://mimashengcheng;

}

Nginx 的主要配置项,当然与 uwsgi 配合还可以使用 uwsgi_proxy 模式,在此不做过多测试

CLIENT:/tmp # sh lb-test.sh

500 requests to Realserver1

500 requests to Realserver2

Nginx 的负载分配也很完美,但是其工作原理,需要两端建链,性能在理论上稍逊色一些,还有四层模式的代理,此处不在验证。

上述三种方法基本覆盖了容器、微服务等各大领域当前阶段主流负载均衡的解决方案,不同之处在于容器往往工作在不同的网络空间中,比较隐蔽。本篇仅进行了最简单 rr (轮循)算法的测试,并未对其它算法及会话保持等特性进行充分说明,可以看出, NAT 及 LVS 的特点是工作层次低,都由内核处理,没有用户态流量产生,所以抗负载能力比较强,应用范围广,可以对几乎所有的应用做负载,但是对网络模型要求高,部署及使用不太方便。 Ngnix 工作在网络的 7 层之上,可以针对 http 应用做一些分流的策略,安装和配置比较简单,对网络的依赖比较小,理论上只要网络可达就能进行负载功能。

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

4

添加新评论0 条评论

Ctrl+Enter 发表

本文隶属于专栏

最佳实践
不同的领域,都有先行者,实践者,用他们的最佳实践来加速更多企业的建设项目落地。

核心数据库服务器选型优先顺序调查

发表您的选型观点,参与即得50金币。