写在前面
- 今天和小伙伴们分享一些
前端服务器
的负载均衡
技术 - 内容为结合
《 SRE Google运维解密》
整理:- 涉及DNS 负载均衡
- VIP 负载均衡
- 反向代理负载均衡
- 理解不足小伙伴帮忙指正
傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。--------王小波
运维大型系统时,将所有鸡蛋放在一个篮子里是引来灾难的最好办法。所以从容灾,负载均衡角度考虑,一般的大型系统,受众较多的,比如电信运营商的系统,都会有多个数据中心,根据流量分布,地域划分,同时考虑地理位置,电费,房价,人力等相关的经济因素,负载均衡的同时,解决了容灾问题。
负载的模式有很多种,主备,轮询,随机,哈希一致性负载等,比如主备区域负载,正常业务可以走省级别的数据中心,然后数据增量同步给集团数据中心,当省数据中心故障时,可以切到集团的地址,反之亦然。
有时候硬件并不能解决问题
用户流量负载均衡(traffic load balancing)系统
是用来决定数据中心中的这些机器中哪一个用来处理某个请求的。
理想情况下,用户流量应该最优地分布于多条网络链路上、多个数据中心中,以及多台服务器上。但是这里的 “最优”是如何定义的呢?
并没有一个独立的答案,最优严重依赖于下列几个因素:
逻辑层级(是在全局还是在局部)
技术层面(硬件层面与软件层面)
用户流量的天然属性
讨论以下两个常见的用户流量场景:一个搜索请求
和一个视频上传请求
。
用户想要很快地获取搜索结果,所以对 搜索请求来说最重要的变量是延迟(latency)
。对于视频上传请求来说,用户已经预期该请求将要花费一定的时间,但是同时希望该请求能够一次成功,所以这里最重要的变量是吞吐量(throughput)
。
两种请求用户的需求
不同,是我们在 全局层面 决完“最优”分配方然的重要条件
- 最小化请求的延迟 :搜索请求将会被发往最近的、可用的数据中心:评价条件是
数据包往返时间(RTT)
, - 最大化吞吐量: 视频上传流将会采取另外一条路径——也许是一条目前带宽没有占满的链路—来最大化吞吐量,同时也许会牺牲一定程度的延迟。
局部层面: 在一个数据中心内部,我们经常假设同一个物理建筑物内的所有物理服务器都在同一个网络中,对用户来说都是等距的。因此在这个层面上的“最优”分配往往关注于优化资源的利用率,避免某个服务器负载过高
。
在现实中,很多其他因素也都在“最优
”方案的考虑范围之内:有些请求可能会被指派到某个稍远一点的数据中心,以保障该数据中心的缓存处于有效状态
。或者某些非交互式请求会被发往另外一个地理区域,以避免网络拥塞
。
负载均衡
,尤其是大型系统的负载均衡,是非常复杂
和非常动态化
的。
Google在多个层面上使用负载均衡策略来解决这些问题:
DNS进行负载均衡
在某个客户端发送HTTP请求之前,会通过域名获取IP,需要先通过系统底层Socket
库的解析器来向DNS服务器
发起请求查询IP地址。DNS系统会通过域名和地址的映射表中查找相关的记录,然后返回ip,这就为我们第一层的负载均衡
机制提供了一个良好基础:
DNS负载均衡,最简单的方案是在DNS回复中提供多个A记录或者AAAA记录
(ipv4&ipv6),由客户端任意选择一个IP地址使用。这种方案虽然看起来简单并且容易实现,但是存在很多问题。
PS C:\Users\liruilong> nslookup www.baidu.com
DNS request timed out.
timeout was 2 seconds.
服务器: UnKnown
Address: 10.96.0.1
非权威应答:
名称: www.a.shifen.com
Addresses: 220.181.38.150
220.181.38.149
Aliases: www.baidu.com
PS C:\Users\liruilong>
www.baidu.com
被解析为两个IP地址,即220.181.38.149和220.181.38.150
。这意味着当你尝试连接到www.baidu.com
时,你的计算机可以选择其中一个IP地址来建立连接。这样可以分散流量、提高性能和可用性,以应对高负载或服务器故障的情况。
在DNS服务器上创建多个A记录,每个记录对应一个服务器的IP地址。例如,假设有3个服务器的IP地址分别为192.168.1.1、192.168.1.2和192.168.1.3,则需要在DNS服务器上创建3个A记录,分别对应这些IP地址。
针对每个A记录,设置相同的域名和TTL值,以确保客户端每次查询时都会得到相同的结果。
在BIND DNS服务器中,可以使用round-robin
关键字启用轮询功能
编辑 BIND 配置文件,并在 example.com
zone
中添加以下行:
zone "example.com" {
type master;
file "example.com.zone";
rrset-order { random; };
};
同时需要在 example.com.zone
文件中添加对应三个域名相同的A 记录
第一个问题 是这种机制对客户端行为的约束力很弱:记录是随机选择的
,也就是每条记录都会引来有基本相同数量的请求流量。如何避免这个问题呢?
理论上我们可以使用SRV记录来指明每个IP地址的优先级和比重
,但是HTTP·协议目前还没有采用SRV记录。(某些互联网协议,如
IMAP、SIP 和 XMPP`,除了与特定的服务器连接外,还需要连接到一个特定的端口。SRV 记录是在 DNS 中指定端口,优先级和权重等)
_xmpp._tcp.example.com. 86400 IN SRV 10 5 5223 server.example.com
服务 XMPP
原型协议 TCP
域名 example.com
TTL(有效期) 86400
class(网络类型) IN
在提示下键入 SRV
优先级 10
权重 5
端口 5223
目标 server.example.com
另一个问题 是客户端无法识别“最近”的地址
。我们可以通过提供一个 anycast DNS服务器
地址,通过 DNS
请求一般会到达最近的地址这种方式来一定程度上缓解这个问题。内部DNS,服务器可以使用最近的数据中心地址来生成DNS回复。
更进一步的优化方式是,将所有的网络地址和它们对应的大概物理位置建立一个对照表,按照这个对照表来发送回复。
但是这种解决方案使得我们需要维护一个更加复杂的DNS服务,并且需要维护一个数据更新流水线(pipeline)
来保证位置信息的正确性。
当然,没有一个很简单的方案,因为这是由DNS的基本特性
决定的:最终用户很少直接跟权威域名服务器(authoritive nameserver)直接联系。在用户到权威服务器中间经常有一个递归解析器(recursive nameserver)代理请求。该递归解析器代理用户请求,同时经常提供一定程度的缓存机制。
这样的DNS中间人机制在用户流量管理上有三个非常重要的影响:
- 递归方式解析IP
- 不确定的回复路径
- 额外的缓存问题
负载均衡:虚拟IP
虚拟IP地址(VIP)不是绑定在某一个特定的网络接口上的,它是由很多设备共享的
。所以说和代理
是有本质区别的,代理往往是一个绑定到一个特定的网卡,主要用于负载均衡,这里讲的VIP是 `虚拟IP+负载均衡``的一种设计。
当然,从用户视角来看,VIP
仍然是一个独立的、普通的lP地址。理论上来讲,这种实现可以让我们将底层实现细节隐藏起来(比如某一个VIP
背后的机器数量),无缝进行维护工作。
比如我们可以依次升级某些机器,或者在资源池中增加更多的机器而不影响用户。
数据到达数据中心内部之后,我们可以在内部采用更大的MTU
来避免碎片重组的发生,但是这种做法需要网络设备支持
。尽早进行负载均衡
,以及分级多次进行
常见的 VIP 解决方案 一般使用 LVS+Keepalived
的方式,当然也可以考虑 NLB
,HLB
等
LVS
是什么,即 Linux 虚拟服务器
┌──[root@vms152.liruilongs.github.io]-[~]
└─$ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.26.200:80 rr
-> 192.168.26.153:80 Masq 2 0 0
-> 192.168.26.154:80 Masq 2 0 0
LVS
无法对真实负载机器进行监控检测,即无法判断负载节点是否健康,能够提供能力,由此引入 Keepalived
:
另外使用 keepalived 可进行 HA 故障切换,也就是有一台备用的 LVS,主 LVS 宕机,LVS VIP 自动切换到从,可以基于 LVS+Keepalived
实现负载均衡及高可用功能,满足网站7x24小时稳定高效的运行。
┌──[root@vms152.liruilongs.github.io]-[~]
└─$ansible node -a 'ipvsadm -Ln'
192.168.26.153 | CHANGED | rc=0 >>
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.26.200:80 rr persistent 2
-> 192.168.26.155:80 Route 3 0 0
-> 192.168.26.156:80 Route 3 0 0
192.168.26.154 | CHANGED | rc=0 >>
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.26.200:80 rr persistent 2
-> 192.168.26.155:80 Route 3 0 0
-> 192.168.26.156:80 Route 3 0 0
┌──[root@vms152.liruilongs.github.io]-[~]
└─$
Keepalived 基于三层检测(IP层,TCP传输层,及应用层),需要注意,如果使用了 keepalived.conf
配置,就不需要再执行 ipvsadm -A
命令去添加均衡的 realserver
命令了,所有的配置都在 keepalived.conf
里面设置即可。它可以自动地将真实服务器添加到 IPVS 中。当然,您需要在 keepalived.conf 文件中配置虚拟服务和真实服务器。
负载均衡:反向代理
基于反向代理的负载均衡技术, 通过使用反向代理服务器
来分发
传入的请求
到后端服务器
上。反向代理服务器充当了客户端和后端服务器之间的中间人
,将请求转发给后端服务器,并将响应返回给客户端。
常见的反向代理负载均衡技术:Nginx,
HAProxy,
Apache HTTP Server` 等。负载均衡角度理解,反向代理来说更细了,ISO 参考模型角度考虑, 支持 4层和7层的代理,同时可以考虑做一些动静分离
4 层代理
┌──[root@liruilongs.github.io]-[~/load_balancing]
└─$ cat nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
daemon off;
events {
worker_connections 1024;
}
# 四层代理的方式
stream{
server {
listen 8088;
proxy_pass backend;
}
upstream backend {
server web1:80;
server web2:80;
}
}
7 层代理
┌──[root@liruilongs.github.io]-[~/load_balancing]
└─$ cat nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
#daemon off;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$upstream_addr - $remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" ';
access_log /var/log/nginx/nginx_access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
server {
listen 8099;
server_name localhost;
root /var/www/html/;
index index.html index.htm;
access_log /var/log/nginx/default_access.log main;
error_log /var/log/nginx/default_error.log;
location / {
proxy_pass http://backend;
}
location ~ .* {
proxy_pass http://backend;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
upstream backend {
server web2:80;
server web1:80;
}
}
博文部分内容参考
© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知
《 SRE Google运维解密》
© 2018-2023 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)