性能不足时需要负载均衡
当服务器的访问量上升时
,
增加服务器线路的带宽是有效的
,
但并不是网络变快了就可以解决所有的问题。
高速线路会传输大量的网络包,这会导致服务器的性能跟不上
。
尤其是通过
CGI
等应用程序动态生成数据的情况下,
对服务器
CPU
的负担更重
,
服务器性能的问题也会表现得越明显
。要解决这个问题,
大家可能首先想到的是换一台性能更好的服务器
,
但当很多用户同时访问时,无论服务器的性能再好,仅靠一台服务器还是难以胜任的。在这种情况下,使用多台服务器来分担负载的方法更有效。
这种架构统称为分布式架构,其中对于负载的分担有几种方法,最简单的一种方法就是采用多台 Web 服务器,减少每台服务器的访问量
。
假设现在我们有 3
台服务器
,
那么每台服务器的访问量会减少到三分之一
,
负载也就减轻了。
要采用这样的方法
,
必须有一个机制将客户端发送的请求分配到每台服务器上。
具体的做法有很多种
,
最简单的一种是通过
DNS
服务器来分配。
当访问服务器时
,
客户端需要先向
DNS
服务器查询服务器的
IP地址,
如果在
DNS
服务器中填写多个名称相同的记录
,
则每次查询时DNS 服务器都会按顺序返回不同的
IP
地址
。
例如
,
对于域名
www.lab.glasscom.com,如果我们给它分配如下
3
个
IP
地址
。
192.0.2.60
192.0.2.70
192.0.2.80
当第
1
次查询这个域名时
,
服务器会返回如下内容
。
192.0.2.60 192.0.2.70 192.0.2.80
当第 2 次查询时,服务器会返回如下内容。
192.0.2.60 192.0.2.70 192.0.2.80
当第
3
次查询时
,
服务器会返回如下内容
。
192.0.2.80 192.0.2.60 192.0.2.70
当第
4
次查询时就又回到第
1
次查询的结果
(如图所示
)。
这种方式称为 轮询(round-robin),通过这种方式可以将访问平均分配给所有的服务器
。 但这种方式是有缺点的。
1)
假如多台 Web 服务器中有一台出现了故障, 这时我们希望在返回 IP 地址时能够跳过故障的 Web 服务器,然而普通的DNS 服务器并不能确认 Web 服务器是否正常工作,因此即便 Web 服务器宕机了,它依然可能会返回这台服务器的 IP 地址
。
2)此外,
轮询分配还可能会引发一些问题
。
在通过
CGI
等方式动态生成网页的情况下,
有些操作是要跨多个页面的
,
如果这期间访问的服务器发生了变化,
这个操作就可能无法继续
。
例如在购物网站中
,
可能会在第一个页面中输入地址和姓名,
在第二个页面中输入信用卡号
,
这就属于刚才说的那种情况。
使用负载均衡器分配访问
为了避免出现前面的问题,可以使用一种叫作负载均衡器的设备。使用负载均衡器时,首先要用负载均衡器的 IP 地址代替 Web 服务器的实际地址注册到 DNS 服务器上。假设有一个域名 www.lab.glasscom.com,我们将这个域名对应的 IP 地址设置为负载均衡器的 IP 地址并注册到 DNS 服务器上。于是,客户端会认为负载均衡器就是一台 Web 服务器,并向其发送请求,然后由负载均衡器来判断将请求转发给哪台 Web 服务器(如图)。
这里的关键点不言而喻,那就是如何判断将请求转发给哪台 Web 服务器
。
判断条件有很多种,
根据操作是否跨多个页面
,
判断条件也会有所不同。
如果操作没有跨多个页面
,
则可以根据
Web
服务器的负载状况来进行判断。
负载均衡器可以定期采集 Web 服务器的 CPU、内存使用率,并根据这些数据判断服务器的负载状况,也可以向 Web 服务器发送测试包,根据响应所需的时间来判断负载状况
。
当然
,
Web
服务器的负载可能会在短时
间内上下波动
,
因此无法非常准确地把握负载状况
,
反过来说,如果过于密集地去查询服务器的负载,这个查询操作本身就会增加 Web 服务器的负载
。
因此也有一种方案是不去查询服务器的负载
,
而是根据事先设置的服务器性能指数,
按比例来分配请求
。
将用户的请求绑定到一台固定的服务上
无论如何
,
这些方法都能够避免负载集中在某一台服务器上。
当操作跨多个页面时,则不考虑 Web 服务器的负载,而是必须将请求发送到同一台 Web 服务器上
。
要实现这一点
,
关键在于我们必须要判断一个操作是否跨了多个页面。
HTTP 的基本工作方式是在发送请求消息之前先建立 TCP 连接,当服务器发送响应消息后断开连接,下次访问 Web 服务器的时候,再重新建立 TCP 连接
。
因此
,
在
Web
服务器看来
,
每一次HTTP 访问都是相互独立的
,
无法判断是否和之前的请求相关
。之所以会这样,
是因为
Web
中使用的
HTTP
协议原本就是这样设计的。
如果要判断请求之间的相关性,就必须在 Web 服务器一端保存相应的信息,这会增加服务器的负担
。
此外
,
Web
服务器最早并不是用来运行CGI 程序的
,
而是主要用来提供静态文件的
,
而静态文件不需要判断请求之间的相关性,
因此最早设计
HTTP
规格的时候
,
就有意省略了请求之间相关性的判断。
那么在不知道请求之间的相关性时,能不能根据一系列请求的发送方IP 地址相同这一点来判断呢?也不行
。
如果使用了我们后面要讲的代理机制
,
所有请求的发送方
IP
地址都会变成代理服务器的
IP
地址
,
无法判断实际发送请求的客户端是哪个。
此外
,
如果使用了地址转换
,
发送方
IP
地址则会变成地址转换设备的 IP
地址
,
也无法判断具体是哪个客户端
。于是,
人们想出了一些方案来判断请求之间的相关性
。
例如
,
可以在发送表单数据时在里面加上用来表示关联的信息,或者是对 HTTP 规格进行扩展,在 HTTP 头部字段中加上用来判断相关性的信息
。
这样
,
负载均衡器就可以通过这些信息来作出判断,
将一系列相关的请求发送到同一台Web 服务器
,
对于不相关的请求则发送到负载较低的服务器了
。
缓存服务器:分担负载
除了使用多台功能相同的
Web
服务器分担负载之外
,
还有另外一种方法,
就是将整个系统按功能分成不同的服务器,如 Web 服务器、数据库服务器。
缓存服务器就是一种按功能来分担负载的方法。缓存服务器是一台通过代理机制对数据进行缓存的服务器
。
代理介于Web 服务器和客户端之间,具有对 Web 服务器访问进行中转的功能
。
当进行中转时,它可以将 Web 服务器返回的数据保存在磁盘中,并可以代替Web 服务器将磁盘中的数据返回给客户端。这种保存的数据称为缓存,缓存服务器指的也就是这样的功能。
Web 服务器需要执行检查网址和访问权限
,
以及在页面上填充数据等内部操作过程,
因此将页面数据返回客户端所需的时间较长
。
相对地,缓存服务器只要将保存在磁盘上的数据读取出来发送给客户端就可以了
,
因此可以比 Web
服务器更快地返回数据
。 不过,
如果在缓存了数据之后
,
Web
服务器更新了数据
,
那么缓存的数据就不能用了,
因此缓存并不是永久可用的
。
此外
,
CGI
程序等产生的页面数据每次都不同,
这些数据也无法缓存
。
无论如何
,
在来自客户端的访问中,
总有一部分访问可以无需经过
Web
服务器
,
而由缓存服务器直接处理。
即便只有这一部分操作通过缓存服务器提高了速度
,整体性能也可
以得到改善
。
此外
,
通过让缓存服务器处理一部分请求
,
也可以减轻
Web
服务器的负担
,
从而缩短
Web
服务器的处理时间
。
缓存服务器通过更新时间管理内容
下面来看一看缓存服务器的工作过程
。
缓存服务器和负载均衡器一样,
需要代替
Web
服务器被注册到
DNS
服务器中
。
然后客户端会向缓存服务器发送 HTTP
请求消息
(
图
(
a
)
①
、
图
(
a
))。
这时
,
缓存服务器会接收请求消息,
这个接收操作和
Web
服务器相同
。
简单来说就是创建用来等待连接的套接字,
当客户端进行连接时执行连接操作
,
然后接收客户端发送的请求消息。
从客户端来看,缓存服务器就相当于 Web 服务器
。
接下来
,
缓存服务器会检查请求消息的内容,
看看请求的数据是否已经保存在缓存中
。 根据是否存在缓存数据,
后面的操作会有所不同
,
现在我们假设不存在缓存数据。
这时
,
缓存服务器会像图
(
b
)
②这样
,
在 HTTP 头部字段中添加一个 Via 字段,表示这个消息经过缓存服务器转发,然后将消息转发给 Web 服务器(图(a)②)
。 在这个过程中,
我们需要判断应该将请求消息转发给哪台
Web
服务器。
如果只有一台
Web
服务器
,
那么情况比较简单
,
只要将
Web
服务器的域名和 IP
地址配置在缓存服务器上
,
让它无条件转发给这台服务器就可以了。
不过
,
如果一台缓存服务器对应多台
Web
服务器就没那么简单了
,需要根据请求消息的内容来判断应该转发给哪台 Web
服务器
。
要实现这个目的有几种方法,
其中比较有代表性的是根据请求消息的
URI
(
图
(
b
)
①
)中的目录名来进行判断。
使用这种方法时
,
我们首先需要在缓存服务器上进行如下设置。
•
当
URI
为
/dir1/
这个目录时
,
转发给
www1.lab.glasscom.com
•
当
URI
为
/dir2/
这个目录时
,
转发给
www2.lab.glasscom.com
缓存服务器会根据上述规则来转发请求消息
,
在这个过程中
,
缓存服务器会以客户端的身份向目标 Web
服务器发送请求消息
。
也就是说
,
它会先创建套接字,
然后连接到
Web
服务器的套接字
,
并发送请求消息
。
从Web 服务器来看,缓存服务器就相当于客户端。
于是
,
缓存服务器会收到来自 Web
服务器的响应消息
(
图
5.5
(
a
)
③
、
图
5.6
(
c
)),
接收消息的过程也是以客户端的身份来完成的。
接下来
,
缓存服务器会在响应消息中加上图
(
d
)
①这样的
Via
头部字段,
它表示这个消息是经过缓存服务器中转的
,
然后缓存服务器会以Web 服务器的身份向客户端发送响应消息
(
图
5.5
(
a
)
④
)。
同时
,
缓存服务器会将响应消息保存到缓存中,
并记录保存的时间
(
图
5.5
(
a
)
④
’)。
这种在客户端和 Web 服务器之间充当中间人的方式就是代理的基本原
理。
在中转消息的过程中
,
缓存服务器还会顺便将页面数据保存下来
,
随着缓存数据的积累,用户访问的数据命中缓存的几率也会提高
。
接下来我
们来看一看命中缓存的情况
。
命中缓存的情况
首先
,
接收客户端的请求消息并检查缓存的过程和刚才是一样的
。
然后
,
如图
5.7
(
a
),
缓存服务器会添加一个 If-Modified-Since 头部字段并将请求转发给 Web 服务器,询问 Web 服务器用户请求的数据是否已经发生变化
。 然后,
Web
服务器会根据
If-Modified-Since
的值与服务器上的页面数据的最后更新时间进行比较,
如果在指定时间内数据没有变化
,
就会返回一个像图 5.7
(
b
)
一样的表示没有变化的响应消息
(
图
5.5
(
b
)
③
)。
这时
,
Web 服务器只要查询一下数据的最后更新时间就好了,比返回页面数据的负担要小一些。而且返回的响应消息也比较短,能相应地减少负担
。
接下
来
,
返回消息到达缓存服务器
,
然后缓存服务器就会知道
Web
服务器上的
数据和本地缓存中的数据是一样的
,
于是就会将缓存的数据返回给客户端
(
图
5.5
(
b
)
④
)。
缓存服务器返回的响应消息的内容和没有命中缓存的情况是一样的
。
此外,
当
Web
服务器上的数据有变化时
,
后面的过程和没有命中缓存的情况是一样的。
Web
服务器会返回最新版本的数据
,
然后缓存服务器加上
Via
字段发送给客户端
,
同时将数据保存在缓存中。