集群的可扩展性及其分布式体系结构之十

 
developerWorks 中国  >  Linux  >

集群的可扩展性及其分布式体系结构之十

TCP粘合技术原理

developerWorks
文档选项
<noscript></noscript>
将此页作为电子邮件发送

将此页作为电子邮件发送

<!----><!---->
<!---->

级别: 初级

林凡 ([email protected])厦门大学

2003 年 6 月 09 日

本部分,将对面向内容交换的负载平衡中,使用的主要网络通信技术手段进行分析。其中,关于通信的半工(TCP HandOff)和双工(TCP Splicing粘合模式)是目前Content Switch(面向内容交换)集群系统使用的主要技术。传统的负载平衡技术主要有应用层协议代理服务器、三层和四层交换等。其中,应用层代理技术,面向特定的应用层协议,对客户端和服务器的数据流进行转换;三层和四层交换通过识别数据报文的有效地址信息进行双向的映射和调度。不管采用哪一种技术,其根本模式都是在网络连接的基础上进一步对数据包进行业务级分流。
<!----><!----><!---->

1、应用级代理

应用级代理广泛的应用于HTTP代理,HTTP缓存等领域,扮演着当今重要的网络服务角色。

一般的应用层代理都采用应用层连接粘合代理(split-connection),代理服务器位于客户端和被访问的服务站点之间,代理服务器透明的将客户请求和服务器响应进行双向转换,以协助双方完成通信的全过程。从HTTP缓存服务器(众所周知的Squid)到安全防火墙,代理服务器始终保持了对现有网络服务协议的兼容性(主要是HTTP等)以及对现有应用框架的集成性。

当用户提交请求,代理将请求转发给服务器,对于服务器而言,代理充当了客户端的角色;当服务器响应请求,代理收到后将数据返回客户端,对于客户端而言,充当服务端的角色。

但是,连接粘合代理存在着几大致命的问题:首先,代理服务器的性能始终是大规模通信的瓶颈;其次,由于代理服务器对任何方向上的数据都要进行转换,大大增加了通信延迟;最后,代理本质上对端到端的传输协议进行了修改,难免会存在传输协议的兼容性问题。

因此,在集群应用中,特别是大规模负载平衡集群系统中很难看到应用级代理的身影。


图一:应用层代理原理示意图
集群的可扩展性及其分布式体系结构之十_第1张图片

当客户端向服务器发起请求的时候,连接被重定向到代理服务器的指定端口上(例如IE浏览器中设定的代理一样);代理接收到这一连接请求以后,根据请求的目标地址和端口,向被访问的服务器发出另一个连接请求,并将客户端发来的请求转发到目标服务器上;随后,代理服务器强制在两个连接上进行互相转发请求数据和应答数据,成为客户端和服务器之间必经之路。因此,代理实现客户端对服务器的透明访问。本质上,代理将客户端的请求进行适当的修改,重新通过另一个连接发送到服务器端,反之依然。这样子的缺点是显然的:数据的双向拷贝需要经由核心的TCP/IP协议栈到用户空间,修改后再由用户空间到TCP/IP层进行处理转发,频繁的上下文交换导致代理的效率低下。而代理服务器也不得不维护至少双倍于请求数量的连接,这对于代理服务器的内存和CPU调度都有极高的要求。

这类的代理服务器大多数针对特定的应用层协议进行双向网络连接的交换工作,比如特定的HTTP、HTTPS代理,FTP代理等,都要求代理服务器理解对应的高层协议的通信原语,因此往往通过一个多线程的用户进程来实现。





回页首


2、核心级的TCP粘合交换技术 ― TCP Splicing

大多数的四层交换产品,都是在网络传输协议层(TCP)进行工作。它们通过转换数据包的端口和IP地址信息,将数据重新定向到服务节点上。比如,将HTTP请求定向到Web Cache服务器上,或者将请求均匀分配到不同的节点实现负载平衡。而面向内容调度的提出,要求在四层交换的基础上进一步识别交换数据的内容,以实现智能交换和更高的性能。

传统的四层交换,在客户端发出第一个Syn包后,均衡器对服务节点群进行选择,并将该SYN后续的报文直接发往选定的节点。处理的模式固然简单并且有效率,但是对于需要解析报文内容的交换机而言,需要在连接建立后(SYN开始之后并完成三次握手),才从接下来的报文中获得负载平衡所需的信息(比如,一个HTTP协议的Get的请求,在客户端完成了连接建立的操作之后,均衡器才能获得具体的Get指令内容)。这意味着,均衡器不能仅仅根据SYN报文的地址和端口信息就做出调度判断;而要把调度的决定延迟到相关业务数据到达的时候进行――整体上讲,交换被"延迟"了。

于是,原理上,负载均衡器(也就是应用级代理),需要监听客户端的连接请求,并在客户端发出连接的请求之后(从SYN开始),建立客户端到均衡器之间的连接(通过TCP的三次握手协议完成)。并在随后的请求报文中分析数据并决定真正被访问的服务节点,然后才与服务节点建立另一个连接,将两个连接粘合在一起(Splicing)。


图二:TCP Splicing原理示意图
集群的可扩展性及其分布式体系结构之十_第2张图片

TCP粘合连接的原理如上图所示。该结构于应用级代理的最大不同在于:客户端和集群节点之间的连接在操作系统的核心层进行连接粘合。也就是说,TCP粘合避免了数据包从核心空间到用户空间的上下文交换这一耗时的过程,并且可以利用操作系统在核心层TCP/IP协议栈的多线程处理能力,提高TCP连接的交换速度,比如Linux核心2.4。





回页首


3、TCP Splicing 处理流程简要描述

相比于应用层代理而言,TCP Splicing技术避免了数据报文的上下文切换工作,减少了核心空间到用户进程的通信开销,整体效率可以作的很好。这一类的技术目前应用的非常稳定有效率,在小型网关设备和简单的负载平衡集群系统中,足够支撑集群的整体运行。

简单说,要实现TCP连接的粘合,要经过至少三个步骤:

1、客户端发起连接请求,并且由均衡器截获请求,完成和客户端的三次握手协议,等待客户端的请求数据(比如Get指令)。


图三:第一步,客户端发起连接

2、均衡器接收到客户端发送的Get请求数据,选定集群节点后,伪装成客户端向集群节点发起请求,完成三次握手协议。并记录下两个连接的本地响应端口,用于映射两个TCP连接。


图四:第二步,均衡器向服务节点发起连接
集群的可扩展性及其分布式体系结构之十_第3张图片

3、经过前面的两次连接,均衡器在客户端和被选节点之前建立一个连接的映射关系。而后续两方的通信就无需再次被分析模块处理,直接在映射端互相交换就可以了。


图五:第三步,TCP连接粘合完成,数据直接交换
集群的可扩展性及其分布式体系结构之十_第4张图片




回页首


4、TCP Splicing数据包通信流程

这里我们详细分析一下TCP粘合处理流程中,对于两回的三次握手协议之后,再建立起连接映射的具体细节。

从前面的叙述我们知道,负载均衡器在客户端发起TCP连接请求并完成三次的握手确认后,并没有马上将数据转发,而是先伪装为服务节点响应客户端的请求,并在随后接收到第一个数据包中获取负载均衡所需的数据,决定实际的服务节点;并在完成与服务节点的通信确认工作时候才开始双方的数据转换工作。


TCP Splicing数据处理流程
集群的可扩展性及其分布式体系结构之十_第5张图片

图中,蓝色框线包含起来的部分表示负载均衡器,包括了专门负责监听客户端请求的端口和负责发送请求到服务节点的端口。而TCP粘合工作完成之后,实际上客户端和服务节点双方的数据包就在两个端口上直接交换,不需要经过复杂的用户空间的调度模块处理了,相当于核心级的报文交换机。

Step1:客户端向集群发起请求,典型的请求以一个Syn(CSEQ表示客户端报文的初始序列)报文为开始,标记一个TCP连接开始并请求回应一个三次握手协议过程。该请求到达负载均衡器的客户端口。

Step2:负载均衡器内建的网络输入流处理例程将截获客户端的SYN请求报文,解析后转发给连接管理模块,并记录下这一次的请求信息:源地址、源端口号、目标地址和目标端口号。

Step3:负载均衡器回应一个对应的ACK(CSEQ+1,表示对应于初始序列的应答序列)到客户端口,回送客户端监听口。同时,发起一个Syn(DSEQ表示新的报文序列号)。

Step4:Ack应答数据以及新的Syn报文从客户端监听口回送客户端,客户端接收响应,第一次握手完成。

众所周知的是,由于IP网络是分组转发的,而接收端试用的缓冲区也会导致收到非完全序列的报文。因此,TCP为了保证报文发送序列与接收序列的同步,定义了一个报文序列协议规范。关键在于:每次发送的报文中所包含的序列,一定是准备接下来接收的ACK应答报文的序列。

Step5:客户端在收到集群的正确应答数据后,认为连接已经建立(实际上,客户端仅仅是和负载均衡器建立了连接,它被欺骗了J)。向服务端发出数据请求报文DATA(CSEQ+1),并且对均衡器的SYN(DSEQ)进行应答,同时发送ACK(DSEQ+1)。至此完成了三次握手的全部过程,并且开始进行数据通信。

由于面向内容的负载平衡必须在收到客户端发出的含有协议内容的数据之后(例如Get指令),才能进行负载调度的判断工作,因此,需要针对客户端的连接请求进行"欺骗",待于客户端的连接建立之后将其挂起,准备调度。这样的操作,显然延迟了客户端到实际节点之间的连接进程,也有文献称之为TCP Delay Binding。

Step6:关键的一步。真正的请求数据第一次到达了负载均衡器的调度模块。调度根据请求中的有效协议信息做出负载平衡判断。判断的依据可以是请求的文件类型,或者是服务级别。另外,负载平衡器也要根据当前服务节点的权值队列,选择目前符合服务条件的负载最轻的节点。而客户端的请求在这里将被挂起,从客户端的角度看,连接请求被隐蔽的延迟了。关于负载平衡策略的讨论不是本文的重点,具体的调度算法和平衡策略将在后续篇章中介绍。

Step7:负载均衡器选择完实际的服务节点,以客户端的"身份"向服务节点发起SYN连接请求,特别一提的是,该请求使用了客户端原先发起的请求初始序列号CSEQ。这么做的目的是为了后面进行TCP粘合所必须的TCP Header数据转换时,尽可能减少均衡器的CPU计算量,也方便管理被粘合的两个连接。

Step8:SYN(CSEQ)由服务端监听口到达服务节点,从整个会话过程来看,客户端Step1所发出的SYN(CSEQ)被延迟地发送到了最终目的地!

Step9:服务端接受SYN(CSEQ),送回应答数据包ACK(CSEQ)+1),同时发起SYN(SSEQ)以完成TCP的握手例程。

Step10:值得注意的一个步骤,服务节点的SYN(SSEQ)报文序列被重新映射为SYN(DSEQ),和step3的报文序列相同。这样,从负载均衡器的角度来讲,他正确的获得了一个应答报文,并且这个被转换过序列的应答报文可以直接和客户端的报文序列映射在一起了。而ACK(CSEQ+1)应答报文被送回核心模块。

到这里为止,我们可以发现,服务端监听口对于均衡器而言,作为虚拟的服务端而存在,通过该端口的报文序列都是转换过后的序列,能够直接和客户端监听口的序列对应。同理,客户端监听口也承担了相似的角色。

Step11:均衡器确认了ACK(CSEQ+1)报文后,完成和服务节点的三次握手。发出应答报文ACK(DSEQ+1)(注意:均衡器在这里使用的是客户端序列,把自己当做客户端,把服务端监听口当做服务器)。同时发出请求数据Data(CSEQ+1)。

到这一步,均衡器完成了对Step5的TCP请求延迟处理。客户端的请求数据终于被发送给了服务节点(通过服务端监听口)。

Step12:经过服务端监听口的报文,需要被适当的修改序列:即DSEQ序列->SSEQ序列。这样的转换是固定偏移的,也就是第一次SYN被接收之后,二者序列之差可以作为整个通信过程中,序列转换的偏移量。因此,从端口送回到服务节点的报文是:Data(CSEQ+1)以及ACK(SSEQ+1)。

Step13:服务节点响应数据请求,例如返回请求的HTML文件。数据DATA(SSEQ+1)以长度len返回,并回送应答ACK(CSEQ+len+1)到均衡器的监听口。

Step14:监听口对Data(SSEQ+1)进行转换,ACK不转换。并将转换后的数据,直接传送到客户端监听口,无需经过核心调度模块的处理。由于前面已经记录了客户端的连接信息,因此这样的传送非常快速。而客户端监听口收到数据之后,即立刻发往客户端。

Step15:客户端对刚才收到的数据应答,将ACK(DSEQ+len+1)送往均衡器。均衡器将该报文发往服务端监听口。

Step16:服务端监听口对报文进行映射转换,DSEQ->SSEQ,向服务节点发送应答报文ACK(SSEQ+len+1)。表明一次数据 请求-回送 工作完成。

TUX是redhat中运行于核心空间的一个简易Web服务器。

他具备了基本的Web服务器的文件访问功能和常见的异常处理。对于客户端请求的如HTML、Gif等静态页面文件可以直接处理,无需将数据传送到用户空间去处理,通过Linux的核心层API读文件调用就可以直接返回请求的文件,并且可以利用TCP/IP的多线程能力提高整体吞吐量。但是对于特殊的请求,如CGI等,需要借助挂接的用户空间的运行模块处理,这些模块可以是支持CGI、PHP的Apache重磅Web服务器,也可以是用户自己写的处理模块。TUX实现了类似TCP粘合模型的处理方式:TUX的通过NetFilter钩子函数实现了对所有输入流的处理,并在核心直接完成请求的响应;对于特殊的HTTP请求(分析Get的内容获得),交给用户空间的程序处理(好似代理程序)。二者结合满足性能和功能的兼顾。

均衡器在客户端和服务节点之间扮演了一个透明网关的角色。二者互相不可见。而为了顺利实现两个TCP连接数据包的转换,均衡器设立负责扮演服务的客户端监听口和扮演客户的服务端监听口。数据包在服务端监听口进行TCP报文头的修改工作:序列号的映射,DSEQ<->SSEQ。

并且,均衡器调度核心在适当的时候,根据客户的请求进行负载平衡调度工作。功能上看,均衡器具备了两种功能:基于应用层协议解析的负载平衡调度策略,以及基于端口的报文交换。其中,报文交换的对象依赖于负载平衡选择的服务节点。但报文交换在一次调度策略确定之后就独立运行(图中的Step13~Step16),不再经过复杂的处理,可以获得非常高的性能和扩展性。另外,这么作也保护了服务节点的安全性。而调度核心独立于报文交换核心,它仅仅处理需要进行调度的部分报文,例如:包含请求不同文件类型信息的报文,或者是包含Session信息的报文。那么在设计端口映射管理核心程序的时候,需要对报文进行快速分类,决定哪一类报文可以直接通过报文交换核心,而哪一部分需要交给调度核心处理。

下一部分,我们将对 TCP 粘合技术中一些涉及到TCP/IP协议的具体细节进行研究,以发现该技术中所存在的问题和特性。



参考资料

你可能感兴趣的:(数据结构,应用服务器,linux,网络协议,网络应用)