博客为参考《网络是怎样连接的》一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。
1.前言
👉热身问答,下列说法是正确的还是错误的:
- 当使用浏览器访问Web服务器时,浏览器的通信对象不仅限于Web服务器。
- 没有防火墙就不能连接到互联网。
- 也有防火墙无法抵御的攻击。
👉答案:
- 正确。浏览器有时候是和Web服务器通信,有时候是和缓存服务器以及负载均衡器等进行通信。
- 错误。防火墙并不是必需的,但是没有防火墙会增加风险。
- 正确。防火墙不会检查通信数据的具体内容,因此无法抵御隐藏在通信数据内容中的攻击。
2.Web服务器的部署地点
2.1.在公司里部署Web服务器
网络包从互联网到达服务器的过程,根据服务器部署地点的不同而不同。最简单的是图5.1(a)中的这种情况,服务器直接部署在公司网络上,并且可以从互联网直接访问。这种情况下,网络包通过最近的POP中的路由器、接入网以及服务器端路由器之后,就直接到达了服务器。其中,路由器的包转发操作,以及接入网和局域网中包的传输过程都和我们之前讲过的内容没有区别$^1$。
以前这样的服务器部署方式很常见,但现在已经不是主流方式了。这里有几个原因。第一个原因是IP地址不足。这样的方式需要为公司网络中的所有设备,包括服务器和客户端计算机,都分配各自的公有地址。然而现在公有地址已经不够用了,因此采用这种方式已经不现实了。
另一个原因是安全问题。这种方式中,从互联网传来的网络包会无节制地进入服务器,这意味着服务器在攻击者看来处于“裸奔”状态。当然,我们可以强化服务器本身的防御来抵挡攻击,这样可以一定程度上降低风险。但是,任何设置失误都会产生安全漏洞,而裸奔状态的服务器,其安全漏洞也都会暴露出来。人工方式总会出错,安全漏洞很难完全消除,因此让服务器裸奔并不是一个稳妥的办法。
因此,现在我们一般采用图5.1(b)中的方式,即部署防火墙$^1$。防火墙的作用类似于海关,它只允许发往指定服务器的指定应用程序的网络包通过,从而屏蔽其他不允许通过的包。这样一来,即便应用程序存在安全漏洞,也可以降低相应的风险。因为防火墙屏蔽了不允许从外部访问的应用程序,所以即便这些程序存在安全漏洞,用于攻击的网络包也进不来$^2$。当然,即便如此风险也不会降到零,因为如果允许外部访问的应用程序中有安全漏洞,还是有可能遭到攻击的$^3$,但怎么说也远比完全暴露安全漏洞的风险要低得多。这就是防火墙的作用。
- 防火墙:一种抵御外部网络攻击的机制,也是最早出现的一种防御机制。现在已经出现了很多可以绕过防火墙的攻击方法,因此防火墙一般需要和反病毒、非法入侵检测、访问隔离等机制并用。
- 在设计防火墙机制的那个年代,还没有特别恶劣的攻击方式,因此只要服务器管理员正确配置应用程序,就可以防止出现漏洞。当时的设计思路就是对于允许外部访问的应用程序进行正确配置,防止出现漏洞,而对于其他应用程序则用防火墙来进行屏蔽保护。
- 因此管理员必须注意两点:1)更新应用程序修补安全漏洞;2)正确配置应用程序避免出现漏洞。
2.2.将Web服务器部署在数据中心
图5.1(a)和图5.1(b)都是将Web服务器部署在公司里,但Web服务器不仅可以部署在公司里,也可以像图5.1(c)这样把服务器放在网络运营商等管理的数据中心里,或者直接租用运营商提供的服务器。
数据中心是与运营商核心部分NOC直接连接的,或者是与运营商之间的枢纽IX直接连接的。换句话说,数据中心通过高速线路直接连接到互联网的核心部分,因此将服务器部署在这里可以获得很高的访问速度$^1$,当服务器访问量很大时这是非常有效的。此外,数据中心一般位于具有抗震结构的大楼内,还具有自主发电设备,并实行24小时门禁管理,可以说比放在公司里具有更高的安全性。此外,数据中心不但提供安放服务器的场地,还提供各种附加服务,如服务器工作状态监控、防火墙的配置和运营、非法入侵监控等,从这一点来看,其安全性也更高。
- 将服务器部署在公司里时,只要提高接入网的带宽,就可以让访问速度变得更快。
如果Web服务器部署在数据中心里,那么网络包会从互联网核心部分直接进入数据中心,然后到达服务器。如果数据中心有防火墙,则网络包会先接受防火墙的检查,放行之后再到达服务器。无论如何,网络包通过路由器的层层转发,最终到达服务器的这个过程都是相同的。
3.防火墙的结构和原理
3.1.主流的包过滤方式
无论服务器部署在哪里,现在一般都会在前面部署一个防火墙,如果包无法通过防火墙,就无法到达服务器。因此,让我们先来探索一下包是如何通过防火墙的。
防火墙的基本思路刚才已经介绍过了,即只允许发往特定服务器中的特定应用程序的包通过,然后屏蔽其他的包。不过,特定服务器上的特定应用程序这个规则看起来不复杂,但网络中流动着很多各种各样的包,如何才能从这些包中分辨出哪些可以通过,哪些不能通过呢?为此,人们设计了多种方式$^1$,其中任何一种方式都可以实现防火墙的目的,但出于性能、价格、易用性等因素,现在最为普及的是包过滤方式。因此,我们就集中介绍一下包过滤方式的防火墙是怎样工作的。
- 防火墙可分为包过滤、应用层网关、电路层网关等几种方式。
3.2.如何设置包过滤的规则
网络包的头部包含了用于控制通信操作的控制信息,只要检查这些信息,就可以获得很多有用的内容。这些头部信息中,经常用于设置包过滤规则的字段如表5.1所示。不过,光看这张表还是难以理解过滤规则是如何设置的,所以我们来看一个具体的例子。
假设我们的网络如图5.2所示,将开放给外网的服务器和公司内网分开部署,Web服务器所在的网络可以从外网直接访问。现在我们希望允许从互联网访问Web服务器(图5.2①),但禁止Web服务器访问互联网(图5.2②)。以前很少禁止Web服务器访问互联网,但现在出现了一些寄生在服务器中感染其他服务器的恶意软件,如果阻止Web服务器访问互联网,就可以防止其他服务器被感染。要实现这样的要求,应该如何设置包过滤的规则呢?我们就用这个例子来看一看包过滤的具体思路。
在设置包过滤规则时,首先要观察包是如何流动的。通过接收方IP地址和发送方IP地址,我们可以判断出包的起点和终点。在图5.2①的例子中,包从互联网流向Web服务器,从互联网发送过来的包其起点是不确定的,但终点是确定的,即Web服务器。因此,我们可以按此来设定规则,允许符合规则的包通过。也就是说,允许起点(发送方IP地址)为任意,终点(接收方IP地址)为Web服务器IP地址的包通过(图5.2中表的第1行)。如果可以确定发送方IP地址,也可以将其加入规则,但这个例子中起点是不确定的,因此可以不将发送方IP地址设为判断条件。
这样一来,从互联网发往Web服务器的包就可以通过防火墙了,但光这样还无法完成访问。因为收到包之后,Web服务器需要通过确认应答机制通知发送方数据已经正常收到,这需要Web服务器向互联网发送包。在Web服务器发往互联网的包中,我们可以将起点(发送方IP地址)为Web服务器地址的包设置为允许通过(图5.2中表的第3行)。像这样,我们可以先根据接收方和发送方地址判断包的流向,并设置是允许还是阻止。
3.3.通过端口号限定应用程序
不过,按照前面的设置,相当于允许了互联网和Web服务器之间所有的包通过,这个状态很危险。因此,我们最好是阻止除了必需服务(也就是本例中的Web服务)以外的所有应用程序的包。
当我们要限定某个应用程序时,可以在判断条件中加上TCP头部或者UDP头部中的端口号。Web服务器的端口号为80$^1$,因此我们在刚才的接收方IP地址和发送方IP地址的基础上再加上80端口作为条件就可以了。也就是说,当包的接收方IP地址为Web服务器地址,且接收方端口号为80时,允许这些包通过(图5.2中表的第1行);或者当包的发送方IP地址为Web服务器地址,且发送方端口号为80时,允许这些包通过(图5.2中的表的第3行)。如果要允许访问除Web之外的其他应用程序,则只要将该应用程序的端口号设置到防火墙中并允许通过就可以了。
- 也可以不使用80端口而使用其他端口,但这种情况一定是在Web服务器程序中特别设置过的,因此只要按照服务器的设置来调整防火墙设置就可以了。
3.4.通过控制位判断连接方向
现在我们已经可以指定某个具体的应用程序了,但是条件还没达到,因为还没有办法阻止Web服务器访问互联网。Web使用的TCP协议是双向收发网络包的,因此如果单纯地阻止从Web服务器发往互联网的包,则从互联网访问Web服务器的操作也会受到影响而无法进行。光判断包的流向还不够,我们必须要根据访问的方向来进行判断。这里就需要用到TCP头部中的控制位。TCP在执行连接操作时需要收发3个包(见第2章),其中第一个包的TCP控制位中SYN为1,而ACK为0。其他的包中这些值都不同,因此只要按照这个规则就能够过滤到TCP连接的第一个包。
如果这第一个包是从Web服务器发往互联网的,那么我们就阻止它(图5.2表中的第2行)。这样设置之后,当然也不会收到对方返回的第二个响应包,TCP连接操作就失败了。也就是说,只要以Web服务器为起点访问互联网,其连接操作必然会失败,这样一来,我们就阻止了Web服务器对互联网的访问。
那么,从互联网访问Web服务器会不会受影响呢?从互联网访问Web服务器时,第一个包是接收方为Web服务器,符合图5.2表中的第1行,因此允许通过。第二个包的发送方是Web服务器,但TCP控制位的规则与第二行不匹配$^1$,因此符合第三行的规则,允许通过。随后的所有包要么符合第一行,要么符合第三行,因此从互联网访问Web服务器的所有包都会被允许通过。
- 第二个包中,SYN为1,ACK也为1,因此不符合刚刚设定的规则。
通过接收方IP地址、发送方IP地址、接收方端口号、发送方端口号、TCP控制位这些条件,我们可以判断出通信的起点和终点、应用程序种类,以及访问的方向。当然,如表5.1列出的那样,还有很多其他的字段可以用作判断条件。通过对这些条件进行组合,我们就可以对包进行筛选。这里也可以添加多个规则,直到能够将允许的访问和不允许的访问完全区分开为止。这样,我们就可以通过设置规则,让允许访问的包通过防火墙,其他的包则不能通过防火墙$^1$。
- 还有一种思路是只阻止有风险的访问,允许其他所有的访问,这种方法有可能漏掉未知的有风险的网络包。因此,为了避免未知的风险,一般来说都是只允许必要的包通过,其他的包全部阻止。
不过,实际上也存在无法将希望允许和阻止的访问完全区分开的情况,其中一个代表性的例子就是对DNS服务器的访问。DNS查询使用的是UDP协议,而UDP与TCP不同,它没有连接操作,因此无法像TCP一样根据控制位来判断访问方向。所以,我们无法设置一个规则,只允许公司内部访问互联网上的DNS服务器,而阻止从互联网访问公司内部的DNS服务器。这一性质不仅适用于DNS,对于所有使用UDP协议的应用程序都是共通的。在这种情况下,只能二者择其一——要么冒一定的风险允许该应用程序的所有包通过,要么牺牲一定的便利性阻止该应用程序的所有包通过$^1$。
- 如果是使用包过滤之外的其他方式的防火墙,有时候是可以判断UDP应用程序的访问方向的。
3.5.从公司内网访问公开区域的规则
图5.2这样的网络结构中,我们不仅要设置互联网和公开区域之间的包过滤规则,还需要设置公司内网和互联网之间,或者公司内网与公开区域之间的包过滤规则。这时,需要注意的是不要让这些规则互相干扰。例如,为了让公司内网与公开区域之间的网络包自由流动,我们可以将接收方IP地址为公开区域的包设置成全部允许通过。但是,如果在这条规则里没有限定发送方IP地址,那么连来自互联网的包也都会被无条件允许进入公开区域了,这会导致公开区域中的服务器全部暴露在危险状态中。因此,我们必须谨慎地设置规则,防止出现这样的情况。
3.6.从外部无法访问公司内网
包过滤方式的防火墙不仅可以允许或者阻止网络包的通过,还具备地址转换功能,因此还需要进行相关的设置。也就是说,互联网和公司内网之间的包需要进行地址转换才能传输,因此必须要进行相关的设置$^1$。具体来说,就是和包过滤一样,以起点和终点作为条件,根据需要设置是否需要进行地址转换。私有地址和公有地址之间的对应关系,以及端口号的对应关系都是自动管理的,因此只需要设置是否允许地址转换就可以了。
- 互联网路由器的路由表中没有私有地址的路由信息,因此凡是接收方为私有地址的包,在经过互联网中的路由器时都会被丢弃,这就是为什么必须使用地址转换的原因。相对地,防火墙内置的路由功能可以由用户自行设置,因此可以在路由表中配置私有地址相关的路由,使得公司内网到公开区域的访问可以以私有地址的形式来进行,这意味着公司内网和公开区域之间传输的包不需要地址转换。
3.7.通过防火墙
像这样,我们可以在防火墙中设置各种规则,当包到达防火墙时,会根据这些规则判断是允许通过还是阻止通过。
如果判断结果为阻止,那么这个包会被丢弃并被记录下来$^1$。这是因为这些被丢弃的包中通常含有非法入侵的痕迹,通过分析这些包能够搞清楚入侵者使用的手法,从而帮助我们更好地防范非法入侵。
- 如果将内置包过滤功能的路由器用作防火墙,则在丢弃包时基本上不会留下记录,这是因为路由器的内存容量小,没有足够的空间用来记录日志。
如果包被判断为允许通过,则该包会被转发出去,这个转发的过程和路由器是相同的。
实际上,在防火墙允许包通过之后,就没有什么特别的机制了,因此包过滤并不是防火墙专用的一种特殊机制,而是应该看作在路由器的包转发功能基础上附加的一种功能。只不过当判断规则比较复杂时,通过路由器的命令难以维护这些规则,而且对阻止的包进行记录对于路由器来说负担也比较大,因此才出现了专用的硬件和软件。如果规则不复杂,也不需要记录日志,那么用内置包过滤功能的普通路由器来充当防火墙也是可以的。
3.8.防火墙无法抵御的攻击
防火墙可以根据包的起点和终点来判断是否允许其通过,但仅凭起点和终点并不能筛选出所有有风险的包。比如,假设Web服务器在收到含有特定数据的包时会引起宕机。但是防火墙只关心包的起点和终点,因此即便包中含有特定数据,防火墙也无法发现,于是包就被放行了。然后,当包到达Web服务器时,就会引发服务器宕机。通过这个例子大家可以看出,只有检查包的内容才能识别这种风险,因此防火墙对这种情况无能为力。
要应对这种情况有两种方法。这个问题的根源在于Web服务器程序的Bug,因此修复Bug防止宕机就是其中一种方法。这类Bug中,危险性较高的会作为安全漏洞公布出来,开发者会很快发布修复了Bug的新版本,因此持续关注安全漏洞信息并更新软件的版本是非常重要的。
另一种方法就是在防火墙之外部署用来检查包的内容并阻止有害包的设备或软件$^1$。当然,即便是采用这种方法也并不是完美无缺的,因为包的内容是否有风险,是由Web服务器有没有Bug决定的,因此当服务器程序中有潜在的Bug并且尚未被发现时,我们也无法判断包中的风险,也无法阻止这样的包。也就是说,我们无法抵御未知的风险。从这一点来看,这种方法和直接修复Bug的方法是基本等效的,但如果服务器数量较多,更新软件版本需要花费一定的时间,或者容易忘记更新软件,这时对包的内容进行检查就会比较有效。
- 有时会作为防火墙的附件提供。
4.通过将请求平均分配给多台服务器来平衡负载
4.1.性能不足时需要负载均衡
当服务器的访问量上升时,增加服务器线路的带宽是有效的,但并不是网络变快了就可以解决所有的问题。高速线路会传输大量的网络包,这会导致服务器的性能跟不上$^1$。尤其是通过CGI等应用程序动态生成数据的情况下,对服务器CPU的负担更重,服务器性能的问题也会表现得越明显。
- 无论服务器部署在公司里还是数据中心里,这个问题都是共通的。
要解决这个问题,大家可能首先想到的是换一台性能更好的服务器,但当很多用户同时访问时,无论服务器的性能再好,仅靠一台服务器还是难以胜任的。在这种情况下,使用多台服务器来分担负载的方法更有效。这种架构统称为分布式架构,其中对于负载的分担有几种方法,最简单的一种方法就是采用多台Web服务器,减少每台服务器的访问量。假设现在我们有3台服务器,那么每台服务器的访问量会减少到三分之一,负载也就减轻了。要采用这样的方法,必须有一个机制将客户端发送的请求分配到每台服务器上。具体的做法有很多种,最简单的一种是通过DNS服务器来分配。当访问服务器时,客户端需要先向DNS服务器查询服务器的IP地址,如果在DNS服务器中填写多个名称相同的记录,则每次查询时DNS服务器都会按顺序返回不同的IP地址。例如,对于域名www.lab.glasscom.com,如果我们给它分配如下3个IP地址。
当第1次查询这个域名时,服务器会返回如下内容。
当第2次查询时,服务器会返回如下内容。
当第3次查询时,服务器会返回如下内容。
当第4次查询时就又回到第1次查询的结果(图5.3)。这种方式称为轮询(round-robin),通过这种方式可以将访问平均分配给所有的服务器。
但这种方式是有缺点的。假如多台Web服务器中有一台出现了故障,这时我们希望在返回IP地址时能够跳过故障的Web服务器,然而普通的DNS服务器并不能确认Web服务器是否正常工作,因此即便Web服务器宕机了,它依然可能会返回这台服务器的IP地址$^1$。
- 如果浏览器在访问DNS服务器返回的第一个IP地址失败时,能够继续尝试第二个IP地址,就可以回避这个问题了,最近的浏览器有很多都已经具备了这样的功能。
此外,轮询分配还可能会引发一些问题。在通过CGI等方式动态生成网页的情况下,有些操作是要跨多个页面的,如果这期间访问的服务器发生了变化,这个操作就可能无法继续。例如在购物网站中,可能会在第一个页面中输入地址和姓名,在第二个页面中输入信用卡号,这就属于刚才说的那种情况。
4.2.使用负载均衡器分配访问
为了避免出现前面的问题,可以使用一种叫作负载均衡器的设备。使用负载均衡器时,首先要用负载均衡器的IP地址代替Web服务器的实际地址注册到DNS服务器上。假设有一个域名www.lab.glasscom.com,我们将这个域名对应的IP地址设置为负载均衡器的IP地址并注册到DNS服务器上。于是,客户端会认为负载均衡器就是一台Web服务器,并向其发送请求,然后由负载均衡器来判断将请求转发给哪台Web服务器(图5.4)$^1$。这里的关键点不言而喻,那就是如何判断将请求转发给哪台Web服务器。
- 转发请求消息使用的是后面要讲到的“代理”机制,缓存服务器也使用这种机制。此外,有些负载均衡器中也内置缓存功能。负载均衡器和缓存服务器很相似,或者可以说它是由缓存服务器进一步发展而来的。
判断条件有很多种,根据操作是否跨多个页面,判断条件也会有所不同。如果操作没有跨多个页面,则可以根据Web服务器的负载状况来进行判断。负载均衡器可以定期采集Web服务器的CPU、内存使用率,并根据这些数据判断服务器的负载状况,也可以向Web服务器发送测试包,根据响应所需的时间来判断负载状况。当然,Web服务器的负载可能会在短时间内上下波动,因此无法非常准确地把握负载状况,反过来说,如果过于密集地去查询服务器的负载,这个查询操作本身就会增加Web服务器的负载。因此也有一种方案是不去查询服务器的负载,而是根据事先设置的服务器性能指数,按比例来分配请求。无论如何,这些方法都能够避免负载集中在某一台服务器上。
当操作跨多个页面时,则不考虑Web服务器的负载,而是必须将请求发送到同一台Web服务器上。要实现这一点,关键在于我们必须要判断一个操作是否跨了多个页面。HTTP的基本工作方式是在发送请求消息之前先建立TCP连接,当服务器发送响应消息后断开连接,下次访问Web服务器的时候,再重新建立TCP连接$^1$。因此,在Web服务器看来,每一次HTTP访问都是相互独立的,无法判断是否和之前的请求相关。
- 现在越来越多的服务器在发送响应消息之后会等待一段时间再断开连接,这个等待时间大约只有几秒钟,像购物网站这种跨多页面填写信息的场景已经超过了这个等待时间,因此还是会断开连接。
之所以会这样,是因为Web中使用的HTTP协议原本就是这样设计的。如果要判断请求之间的相关性,就必须在Web服务器一端保存相应的信息,这会增加服务器的负担。此外,Web服务器最早并不是用来运行CGI程序的,而是主要用来提供静态文件的,而静态文件不需要判断请求之间的相关性,因此最早设计HTTP规格的时候,就有意省略了请求之间相关性的判断。
那么在不知道请求之间的相关性时,能不能根据一系列请求的发送方IP地址相同这一点来判断呢?也不行。如果使用了代理机制$^1$,所有请求的发送方IP地址都会变成代理服务器的IP地址,无法判断实际发送请求的客户端是哪个。此外,如果使用了地址转换,发送方IP地址则会变成地址转换设备的IP地址,也无法判断具体是哪个客户端。
- 代理:一种介于客户端与Web服务器之间,对访问操作进行中转的机制。
于是,人们想出了一些方案来判断请求之间的相关性。例如,可以在发送表单数据时在里面加上用来表示关联的信息,或者是对HTTP规格进行扩展,在HTTP头部字段中加上用来判断相关性的信息$^1$。这样,负载均衡器就可以通过这些信息来作出判断,将一系列相关的请求发送到同一台Web服务器,对于不相关的请求则发送到负载较低的服务器了。
- 这种信息俗称Cookie。
5.使用缓存服务器分担负载
5.1.如何使用缓存服务器
除了使用多台功能相同的Web服务器分担负载之外,还有另外一种方法,就是将整个系统按功能分成不同的服务器$^1$,如Web服务器、数据库服务器。缓存服务器就是一种按功能来分担负载的方法。
- 也可以将“使用多台功能相同的服务器”和“使用多台功能不同的服务器”这两种方法结合起来使用。
缓存服务器是一台通过代理机制对数据进行缓存的服务器。代理介于Web服务器和客户端之间,具有对Web服务器访问进行中转的功能。当进行中转时,它可以将Web服务器返回的数据保存在磁盘中,并可以代替Web服务器将磁盘中的数据返回给客户端。这种保存的数据称为缓存,缓存服务器指的也就是这样的功能。
Web服务器需要执行检查网址和访问权限,以及在页面上填充数据等内部操作过程,因此将页面数据返回客户端所需的时间较长。相对地,缓存服务器只要将保存在磁盘上的数据读取出来发送给客户端就可以了,因此可以比Web服务器更快地返回数据。
不过,如果在缓存了数据之后,Web服务器更新了数据,那么缓存的数据就不能用了,因此缓存并不是永久可用的。此外,CGI程序等产生的页面数据每次都不同,这些数据也无法缓存。无论如何,在来自客户端的访问中,总有一部分访问可以无需经过Web服务器,而由缓存服务器直接处理。即便只有这一部分操作通过缓存服务器提高了速度,整体性能也可以得到改善。此外,通过让缓存服务器处理一部分请求,也可以减轻Web服务器的负担,从而缩短Web服务器的处理时间。
5.2.缓存服务器通过更新时间管理内容
下面来看一看缓存服务器的工作过程。缓存服务器和负载均衡器一样,需要代替Web服务器被注册到DNS服务器中。然后客户端会向缓存服务器发送HTTP请求消息(图5.5(a)①、图5.6(a))。这时,缓存服务器会接收请求消息,这个接收操作和Web服务器相同。简单来说就是创建用来等待连接的套接字,当客户端进行连接时执行连接操作,然后接收客户端发送的请求消息。从客户端来看,缓存服务器就相当于Web服务器。接下来,缓存服务器会检查请求消息的内容,看看请求的数据是否已经保存在缓存中。根据是否存在缓存数据,后面的操作会有所不同,现在我们假设不存在缓存数据。这时,缓存服务器会像图5.6(b)②这样,在HTTP头部字段中添加一个Via字段,表示这个消息经过缓存服务器转发,然后将消息转发给Web服务器(图5.5(a)②)。
在这个过程中,我们需要判断应该将请求消息转发给哪台Web服务器。如果只有一台Web服务器,那么情况比较简单,只要将Web服务器的域名和IP地址配置在缓存服务器上,让它无条件转发给这台服务器就可以了。不过,如果一台缓存服务器对应多台Web服务器就没那么简单了,需要根据请求消息的内容来判断应该转发给哪台Web服务器。要实现这个目的有几种方法,其中比较有代表性的是根据请求消息的URI(图5.6(b)①)中的目录名来进行判断。使用这种方法时,我们首先需要在缓存服务器上进行如下设置。
- 当URI为/dir1/这个目录时,转发给www1.lab.glasscom.com
- 当URI为/dir2/这个目录时,转发给www2.lab.glasscom.com
缓存服务器会根据上述规则来转发请求消息,在这个过程中,缓存服务器会以客户端的身份向目标Web服务器发送请求消息。也就是说,它会先创建套接字,然后连接到Web服务器的套接字,并发送请求消息。从Web服务器来看,缓存服务器就相当于客户端。于是,缓存服务器会收到来自Web服务器的响应消息(图5.5(a)③、图5.6(c)),接收消息的过程也是以客户端的身份来完成的。
接下来,缓存服务器会在响应消息中加上图5.6(d)①这样的Via头部字段,它表示这个消息是经过缓存服务器中转的,然后缓存服务器会以Web服务器的身份向客户端发送响应消息(图5.5(a)④)。同时,缓存服务器会将响应消息保存到缓存中,并记录保存的时间(图5.5(a)④’)。
这种在客户端和Web服务器之间充当中间人的方式就是代理的基本原理。在中转消息的过程中,缓存服务器还会顺便将页面数据保存下来,随着缓存数据的积累,用户访问的数据命中缓存的几率也会提高。接下来我们来看一看命中缓存的情况(图5.5(b))。
首先,接收客户端的请求消息并检查缓存的过程和刚才是一样的(图5.5(b)①、图5.6(a))。然后,如图5.7(a),缓存服务器会添加一个If-Modified-Since头部字段并将请求转发给Web服务器,询问Web服务器用户请求的数据是否已经发生变化(图5.5(b)②、图5.7(a))。
然后,Web服务器会根据If-Modified-Since的值与服务器上的页面数据的最后更新时间进行比较,如果在指定时间内数据没有变化,就会返回一个像图5.7(b)一样的表示没有变化的响应消息(图5.5(b)③)。这时,Web服务器只要查询一下数据的最后更新时间就好了,比返回页面数据的负担要小一些。而且返回的响应消息也比较短,能相应地减少负担。接下来,返回消息到达缓存服务器,然后缓存服务器就会知道Web服务器上的数据和本地缓存中的数据是一样的,于是就会将缓存的数据返回给客户端(图5.5(b)④)。缓存服务器返回的响应消息的内容和没有命中缓存的情况是一样的(图5.6(d))。
此外,当Web服务器上的数据有变化时,后面的过程和没有命中缓存的情况是一样的。Web服务器会返回最新版本的数据(图5.5(a)③、图5.6(c)),然后缓存服务器加上Via字段发送给客户端,同时将数据保存在缓存中。
5.3.最原始的代理——正向代理
刚才讲的是在Web服务器一端部署一个代理,然后利用其缓存功能来改善服务器的性能,还有一种方法是在客户端一侧部署缓存服务器。
实际上,缓存服务器使用的代理机制最早就是放在客户端一侧的,这才是代理的原型,称为正向代理$^1$(forward proxy)。
- 其实正向代理并不是一开始就叫这个名字,最早说的“代理”指的就是我们现在说的正向代理,或者也叫“代理服务器”。这是因为最早只有这么一种代理,后来出现了各种其他方式的代理,为了相互区别才起了“××代理”这样的名字。此外,由于代理种类变多了,叫“××代理服务器”实在太长,一般都会省略“服务器”3个字。
正向代理刚刚出现的时候,其目的之一就是缓存,这个目的和服务器端的缓存服务器相同。不过,当时的正向代理还有另外一个目的,那就是用来实现防火墙。
防火墙的目的是防止来自互联网的非法入侵,而要达到这个目的,最可靠的方法就是阻止互联网和公司内网之间的所有包。不过,这样一来,公司员工就无法上外网了,因此还必须想一个办法让必要的包能够通过,这个办法就是利用代理。简单来说,代理的原理如图5.8所示,它会先接收来自客户端的请求消息,然后再转发到互联网中$^1$,这样就可以实现只允许通过必要的网络包了。这时,如果能够利用代理的缓存,那么效果就会更好,因为对于以前访问过的数据,可以直接从位于公司内网的代理服务器获得,这比通过低速线路访问互联网要快很多$^2$。
代理(Proxy)本来的意思并不是“转发”消息,而是先把消息收下来,然后“伪装”成原始客户端向Web服务器发出访问请求。
代理出现于ADSL、FTTH等技术实用化之前,那个时候还没有廉价高速的接入网,因此必须想办法榨干低速接入网中的所有能力。代理的缓存功能正是有效利用低速接入网的一种方法。
此外,由于代理在转发过程中可以查看请求的内容,所以可以根据内容判断是否允许访问。也就是说,通过代理可以禁止员工访问危险的网站,或者是与工作内容无关的网站。包过滤方式的防火墙只能根据IP地址和端口号进行判断,因此无法实现这一目的。
在使用正向代理时,一般需要在浏览器的设置窗口中的“代理服务器”一栏中填写正向代理的IP地址,浏览器发送请求消息的过程也会发生相应的变化。在没有设置正向代理的情况下,浏览器会根据网址栏中输入的http://…字符串判断Web服务器的域名,并向其发送请求消息;当设置了正向代理时,浏览器会忽略网址栏的内容,直接将所有请求发送给正向代理。请求消息的内容也会有一些不同。没有正向代理时,浏览器会从网址中提取出Web服务器域名后面的文件名或目录名,然后将其作为请求的URI进行发送;而有正向代理时,浏览器会像图5.9这样,在请求的URI字段中填写完整的http://…网址。
正向代理转发消息的过程也和服务器端的缓存服务器有一些不同,不同点在于对转发目标Web服务器的判断上。使用正向代理时,URI部分为http://…这样的完整网址,因此可以根据这个网址来转发,不需要像服务器端的缓存服务器一样事先设置好转发目标Web服务器,而且可以发给任意Web服务器。而服务器端的缓存服务器只能向事先设置好的目标进行转发,这就是两者不同的地方。
5.4.正向代理的改良版——反向代理
正如前面讲过的,使用正向代理需要在浏览器中进行设置,这可以说是识别正向代理的一个特征。但是,设置浏览器非常麻烦,如果设置错误还可能导致浏览器无法正常工作。
需要设置浏览器这一点除了麻烦、容易发生故障之外,还有其他一些限制。如果我们想把代理放在服务器端,那么服务器不知道谁会来访问,也没办法去设置客户端的浏览器,因此无法采用这种方法来实现。
于是,我们可以对这种方法进行改良,使得不需要在浏览器中设置代理也可以使用。也就是说,我们可以通过将请求消息中的URI中的目录名与Web服务器进行关联,使得代理能够转发一般的不包含完整网址的请求消息。我们前面介绍的服务器端的缓存服务器采用的正是这种方式,这种方式称为反向代理(reverse proxy)。
个人注解:
正向代理:
定义:正向代理是位于客户端和目标服务器之间的代理服务器。它代表客户端向目标服务器发出请求,并将服务器的响应返回给客户端。
工作原理:当客户端想要访问目标服务器时,首先向正向代理服务器发出请求。正向代理服务器接收到请求后,再向目标服务器发出请求。目标服务器的响应通过正向代理服务器返回给客户端。
主要功能:
- 隐藏客户端身份:正向代理可以隐藏客户端的真实IP地址,使目标服务器无法直接知道客户端的真实身份。
- 访问控制:正向代理可以用于控制客户端的访问权限,阻止对某些网站的访问。
- 缓存:正向代理可以缓存服务器的响应,以提高访问速度和减少带宽消耗。
- 安全性:正向代理可以用于扫描和过滤客户端的请求,提供额外的安全层。
应用场景:
- 访问被屏蔽的网站:通过正向代理访问一些在地理位置或网络上被屏蔽的网站。
- 提高访问速度:通过缓存来提高访问速度,减少带宽消耗。
反向代理:
定义:反向代理是位于目标服务器和客户端之间的代理服务器。它代表服务器向客户端提供服务,并将客户端的请求转发给目标服务器。
工作原理:当客户端向目标服务器发出请求时,反向代理服务器首先接收该请求,然后根据请求将其转发给合适的目标服务器。目标服务器的响应通过反向代理服务器返回给客户端。
主要功能:
- 隐藏服务器身份:反向代理可以隐藏目标服务器的真实IP地址,使客户端无法直接知道服务器的真实身份。
- 负载均衡:反向代理可以将请求分发到多台目标服务器上,以均衡负载,提升服务性能和可靠性。
- 缓存:反向代理可以缓存服务器的响应,以提高访问速度和减少目标服务器的负载。
- 安全性:反向代理可以用于过滤和扫描请求,保护目标服务器免受攻击。
应用场景:
- 提高服务性能:通过负载均衡和缓存来提高服务性能和可靠性。
- 提高安全性:隐藏真实服务器的IP地址,保护服务器免受直接攻击。
- SSL卸载:反向代理可以处理SSL加密和解密,从而减轻目标服务器的负担。
5.5.透明代理
缓存服务器判断转发目标的方法还有一种,那就是查看请求消息的包头部。因为包的IP头部中包含接收方IP地址,只要知道了这个地址,就知道用户要访问哪台服务器了$^1$。这种方法称为透明代理(transparent proxy)。
- HTTP1.1版本增加了一个用于表示访问目标Web服务器的Host字段,因此也可以通过Host字段来判断转发目标。
这种方法也可以转发一般的请求消息,因此不需要像正向代理一样设置浏览器参数,也不需要在缓存服务器上设置转发目标,可以将请求转发给任意Web服务器。
透明代理集合了正向代理和反向代理的优点,是一个非常方便的方式,但也需要注意一点,那就是如何才能让请求消息到达透明代理。由于透明代理不需要设置在浏览器中,那么浏览器还是照常向Web服务器发送请求消息。反向代理采用的是通过DNS服务器解析引导的方法,但透明代理是不能采用这种方法的,否则透明代理本身就变成了访问目标,也就无法通过接收方IP地址判断转发目标了,这就失去了透明代理的意义。总之,正常情况下,请求消息是从浏览器直接发送到Web服务器,并不会到达透明代理。
于是,我们必须将透明代理放在请求消息从浏览器传输到Web服务器的路径中,当消息经过时进行拦截。可能大家觉得这种方法太粗暴,但只有这样才能让消息到达透明代理,然后再转发给Web服务器。如果请求消息有多条路径可以到达Web服务器,那么就必须在这些路径上都放置透明代理,因此一般是将网络设计成只有一条路可以走的结构,然后在这一条路径上放置透明代理。连接互联网的接入网就是这样一个关口,因此可以在接入网的入口处放置透明代理$^1$。使用透明代理时,用户不会察觉到代理的存在,也不会注意到HTTP消息是如何被转发的,因此大家更倾向于将透明代理说成是缓存$^2$。
- 也可以采用在网络中的某些地方将Web访问包筛选出来并转发给透明代理的方法。
- 最近有很多场合中已经将透明代理直接叫作“缓存”而不是“代理”了,不过无论叫什么名字,其内部结构是相同的。
6.内容分发服务
6.1.利用内容分发服务分担负载
缓存服务器部署在服务器端还是客户端,其效果是有差别的。如图5.10(a)所示,当缓存服务器放在服务器端时,可以减轻Web服务器的负载,但无法减少互联网中的流量。这一点上,将缓存服务器放在客户端更有效(图5.10(b))。互联网中会存在一些拥塞点,通过这些地方会比较花时间。如果在客户端部署缓存服务器,就可以不受或者少受这些拥塞点的影响,让网络流量更稳定,特别是当访问内容中含有大图片或视频时效果更明显。
不过,客户端的缓存服务器是归客户端网络运营管理者所有的,Web服务器的运营者无法控制它。比如,某网站的运营者觉得最近网站上增加了很多大容量的内容,因此想要增加缓存服务器的容量。如果缓存放在服务器端,那么网站运营者可以自己通过增加磁盘空间等方式来进行扩容,但对于放在客户端的缓存就无能为力了。进一步说,客户端有没有缓存服务器还不一定呢。
因此,这两种部署缓存服务器的方式各有利弊,但也有一种方式能够集合两者的优点。那就是像图5.10(c)这样,Web服务器运营者和网络运营商签约,将可以自己控制的缓存服务器放在客户端的运营商处。
这样一来,我们可以把缓存服务器部署在距离用户很近的地方,同时Web服务器运营者还可以控制这些服务器,但这种方式也有问题。对于在互联网上公开的服务器来说,任何地方的人都可以来访问它,因此如果真的要实现这个方式,必须在所有的运营商POP中都部署缓存服务器才行,这个数量太大了,非常不现实。
要解决这个问题也有一些办法。首先,我们可以筛选出一些主要的运营商,这样可以减少缓存服务器的数量。尽管这样做可能会导致有些用户访问到缓存服务器还是要经过很长的距离,但总比直接访问Web服务器的路径要短多了,因此还是可以产生一定的效果。
接下来这个问题更现实,那就是即便减少了数量,作为一个Web服务器运营者,如果自己和这些运营商签约并部署缓存服务器,无论是费用还是精力都是吃不消的。为了解决这个问题,一些专门从事相关服务的厂商出现了,他们来部署缓存服务器,并租借给Web服务器运营者。这种服务称为内容分发服务$^1$。
- 内容分发服务也叫CDS(Content Delivery Service)。现在更常用的名称叫CDN(Content Delivery Network或Content Distribution Network)。
提供这种服务的厂商称为CDSP$^1$,他们会与主要的供应商签约,并部署很多台缓存服务器$^2$。另一方面,CDSP会与Web服务器运营者签约,使得CDSP的缓存服务器配合Web服务器工作。只要Web服务器与缓存服务器建立关联,那么当客户端访问Web服务器时,实际上就是在访问CDSP的缓存服务器了。
- CDSP:Content Delivery Service Provider,内容分发服务运营商。
- 有些CDSP会在互联网中部署几百台缓存服务器。
缓存服务器可以缓存多个网站的数据,因此CDSP的缓存服务器就可以提供给多个Web服务器的运营者共享。这样一来,每个网站运营者的平均成本就降低了,从而减少了网站运营者的负担。而且,和运营商之间的签约工作也由CDSP统一负责,网站运营者也节省了精力。
6.2.如何找到最近的缓存服务器
在使用内容分发服务时,如图5.11所示,互联网中有很多缓存服务器,如何才能从这些服务器中找到离客户端最近的一个,并让客户端去访问那台服务器呢?
我们可以像正向代理一样在浏览器中进行设置,但用户那么多,也没办法帮所有人去设置浏览器。因此,我们需要一种机制,即便用户不进行任何设置,也能够将请求消息发送到缓存服务器。
这样的方法有几种,下面我们按顺序来介绍。第一个方法是像负载均衡一样用DNS服务器来分配访问。也就是说,我们可以在DNS服务器返回Web服务器IP地址时,对返回的内容进行一些加工,使其能够返回距离客户端最近的缓存服务器的IP地址。在解释这种方法的具体原理之前,我们先来复习一下DNS的基本工作方式。
互联网中有很多台DNS服务器,它们通过相互接力来处理DNS查询,这个过程从客户端发送查询消息开始,也就是说客户端会用要访问的Web服务器域名生成查询消息,并发送给自己局域网中的DNS服务器$^1$(图5.12①)。然后,客户端DNS服务器会通过域名的层次结构找到负责管理该域名的DNS服务器,也就是Web服务器端的那个DNS服务器,并将查询消息发送给它(图5.12②)。Web服务器端的DNS服务器收到查询消息后,会查询并返回域名相对应的IP地址。在这台DNS中,有一张管理员维护的域名和IP地址的对应表,只要按照域名查表,就可以找到相应的IP地址(图5.12③)。接下来,响应消息回到客户端的DNS服务器,然后再返回给客户端(图5.12④)。
- 如果自己的本地局域网中没有DNS服务器,则将请求发送给运营商的DNS服务器。
上面讲的是Web服务器的域名只对应一个IP地址的情况,如果一个域名对应多个IP地址,则按照前面图5.3的轮询方式按顺序返回所有的IP地址。
如果按照DNS服务器的一般工作方式来看,它只能以轮询方式按顺序返回IP地址,完全不考虑客户端与缓存服务器的远近,因此可能会返回离客户端较远的缓存服务器IP地址。
如果要让用户访问最近的缓存服务器,则不应采用轮询方式,而是应该判断客户端与缓存服务器的距离,并返回距离客户端最近的缓存服务器IP地址。这里的关键点不言自明,那就是到底该怎样判断客户端与缓存服务器之间的距离呢?
方法是这样的。首先,作为准备,需要事先从缓存服务器部署地点的路由器收集路由信息(图5.13)。例如,在图5.13的例子中,一共有4台缓存服务器,在这4台服务器的部署地点又分别有4台路由器,则我们需要分别获取这4台路由器的路由表,并将4张路由表集中到DNS服务器上。
接下来,DNS服务器根据路由表查询从本机到DNS查询消息的发送方,也就是客户端DNS服务器的路由信息。例如,根据图5.13路由器A的路由表,可以查出路由器A到客户端DNS服务器的路由。通过互联网内部的路由表中的路由信息可以知道先通过运营商X,然后通过运营商Y,最后到达运营商Z这样的信息,通过这样的信息可以大致估算出距离。依次查询所有路由器的路由表之后,我们就可以通过比较找出哪一台路由器距离客户端DNS服务器最近。提供路由表的路由器位于缓存服务器的位置,而客户端DNS服务器也应该和客户端在同一位置,这样就等于估算出了缓存服务器与客户端之间的距离,从而能够判断出哪台缓存服务器距离客户端最近了。实际上,客户端DNS服务器不一定和客户端在同一位置,因此可能无法得出准确的距离,但依然可以达到相当的精度。
6.3.通过重定向服务器分配访问目标
还有另一个让客户端访问最近的缓存服务器的方法。HTTP规格中定义了很多头部字段,其中有一个叫作Location的字段。当Web服务器数据转移到其他服务器时可以使用这个字段,它的意思是“您要访问的数据在另一台服务器上,请访问那台服务器吧。”这种将客户端访问引导到另一台Web服务器的操作称为重定向,通过这种方法也可以将访问目标分配到最近的缓存服务器。
当使用重定向告知客户端最近的缓存服务器时,首先需要将重定向服务器注册到Web服务器端的DNS服务器上。这样一来,客户端会将HTTP请求消息发送到重定向服务器上。重定向服务器和刚才一种方法中的DNS服务器一样,收集了来自各个路由器的路由信息,并根据这些信息找到最近的缓存服务器,然后将缓存服务器的地址放到Location字段中返回响应。这样,客户端就会重新去访问指定的缓存服务器了(图5.14、图5.15)。
这种方法的缺点在于增加了HTTP消息的交互次数,相应的开销也比较大,但它也有优点。对DNS服务器进行扩展的方法是估算客户端DNS服务器到缓存服务器之间的距离,因此精度较差;相对而言,重定向的 方法是根据客户端发送来的HTTP消息的发送方IP地址来估算距离的,因此精度较高。
此外,也可以使用除路由信息之外的其他一些信息来估算距离,进一步提高精度。重定向服务器不仅可以返回带有Location字段的HTTP消息,也可以返回一个通过网络包往返时间估算到缓存服务器的距离的脚本,通过在客户端运行脚本来找到最优的缓存服务器。这个脚本可以向不同的缓存服务器发送测试包并计算往返时间,然后将请求发送到往返时间最短的一台缓存服务器,这样就可以判断出对于客户端最优的缓存服务器,并让客户端去访问该服务器。
6.4.缓存的更新方法会影响性能
还有一个因素会影响缓存服务器的效率,那就是缓存内容的更新方法。缓存本来的思路是像图5.5那样,将曾经访问过的数据保存下来,然后当再次访问时拿出来用,以提高访问操作的效率。不过,这种方法对于第一次访问是无效的,而且后面的每次访问都需要向原始服务器查询数据有没有发生变化,如果遇到网络拥塞,就会使响应时间恶化。
要改善这一点,有一种方法是让Web服务器在原始数据发生更新时,立即通知缓存服务器,使得缓存服务器上的数据一直保持最新状态,这样就不需要每次确认原始数据是否有变化了,而且从第一次访问就可以发挥缓存的效果。内容分发服务采用的缓存服务器就具备这样的功能。
此外,除了事先编写好内容的静态页面之外,还有一些在收到请求后由CGI程序生成的动态页面,这种动态页面是不能保存在缓存服务器上的。这种情况下,我们可以不保存整个页面,而是将应用程序生成的部分,也就是每次内容都会发生变化的动态部分,与内容不会发生变化的静态部分分开,只将静态部分保存在缓存中。
Web服务器前面存在着各种各样的服务器,如防火墙、代理服务器、缓存服务器等。请求消息最终会通过这些服务器,到达Web服务器。Web服务器接收请求之后,会查询其中的内容,并根据请求生成并返回响应消息。