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

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

TCP Splicing中关于TCP协议的细节

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

将此页作为电子邮件发送

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

级别: 初级

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

2003 年 6 月 09 日

续上篇对TCP splicing技术应用于面向内容的负载平衡技术,我们将在这里进一步探讨TCP Splicing中,对于TCP报文处理的个别细节,包括两个被Splicing的连接之间如何处理报文序号映射,常见的TCP 选项的兼容性等问题做出分析和探讨。
<!----><!----><!---->

TCP背景

由于TCP粘合技术需要对两个连接上的报文进行处理,以实现均衡器作为透明网关的作用。因此,这样的处理必然包括:对TCP的源目的地址的处理;对TCP报文序号的处理;对校验和的重新计算;对常见的TCP选项的协商和转换。如果客户端和集群节点不是同一类型的系统,可能在TCP协议栈的实现上有所区别。因此,作为集群的控制中心和转换中心,负载均衡器必然要为协议兼容性进行协商,以保证双向通信的正确性。

众所周知,TCP用Socket表示一对可以被唯一标识的端到端的协议实体:源IP地址和端口<――>目的IP地址和端口。每个TCP的报文中,属于控制作用的头部包含了源、目的的端口信息(地址信息被包含在IP报文的头部中)以及必要的控制信息。

TCP提供一种面向连接的、可靠的字节流服务。在一个TCP 连接中,仅有两方进行彼此通信。协议通过下列方式来提供可靠性,下面几点TCP的特性将在本篇中涉及:

  • 分段机制 :应用数据被分割成TCP认为最适合发送的数据块。本文将讨论最大报文分段机制的问题。
  • 可靠连接:当TCP收到发自TCP连接另一端的数据,它将发送一个确认Ack。确认报文的序号恰好是接收到的报文序号加一,相当于发送端下一个将要发送的报文序号。本文中涉及了报文以及ACK报文序列转换的问题。
  • 数据校验:TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是进行差错控制和重传。
  • 滑动窗口:为了提供流量控制而采取的缓冲手段。对于TCP的窗口扩充选项将在本文进行讨论。

 


TCP报文结构图
TCP报文结构图

上图是TCP报文的常见结构图。我们将关注TCP报文在进行TCP Splicing的时候,对于头20字节(TCP header 部分)所需要进行的修改工作。

TCP是全双工的协议,这意味着通信的双方在两个方向上都能进行数据传送的任务,每一端都必须为两个方向上的数据流维护序列号。维护序列号实用计数器机制,保证TCP协议能够知道哪些TCP报文是被确认过表示的,哪一些是仍未确认过的,下面是TCP Splicing均衡器使用的主要计数器:

  • Snd_nxt:表示下一个将要发送的报文的序列号。
  • Snd_una:表示最近一个尚未确认的但是已经发送过的报文的序列号。等效于发送端在这次连接中收到的应答报文的最大序列号。
  • Rcv_nxt:表示接下来期望收到的下一个报文序号。相当于已经收到的最大报文应答序号再加上1。正常情况是Rcv_nxt<=Snd_una。

以上的三个主要序号定义了TCP连接中(单个方向上的)的序列空间。我们可以假定该序列空间从0开始计数,一直到65535,然后重复(显然,TCP的具体实现中不会从0开始进行序号计数)。大于snd_nxt的序号的报文表示那些还没有发送的报文;小于rcv_nxt序号的报文表示那些发送出去之后收到的应答报文(这些被确认过的报文很有可能还待在缓冲区里面没有传递给应用程序)。如果应答报文在发送的途中丢失或者应答超时,我们就会发现:snd_una<rcv_nxt。需要做出重传的处理。

此外,序号空间中还必须有表示滑动窗体大小的snd_wnd;以及表示当前滑动窗体中最.大的待发送报文的序号snd_max。具体的可以看下图:


报文序号空间实例图





回页首


映射序列号

由于Splicing将两个连接粘合起来,并在核心层完成报文的修改,使得均衡器能够扮演透明网关的角色(即客户端和服务器都看不到均衡器的存在,都认为是在和对方直接进行通信)一方面,均衡器以及整个集群的所有机器,都要支持虚拟IP地址,并确认均衡器拥有对VIP的控制权,这样不管最终响应客户端的是哪一台集群节点,对于客户端而言都是从VIP这个地址返回的。另外,由于被Splicing 的两个连接(客户端到均衡器,均衡器到集群节点)使用完全不同的序列号空间,因此必须有一种机制将两个连接上传送的报文的序列号进行互相的转换――映射。


报文序列空间映射示意图

上图中出现三个独立的序列号空间:CSEQ、DSEQ、SSEQ。(TCP是全双工的通信协议,反方向的处理是对称的)。在通信的两端分别会向对方发起连接传输,而这里提到的三个序列空间分别表示某一个方向上的通信序列,包括SYN、Data以及ACK报文等。

TCP的任何一个方向上的通信是都有序的,因此我们定义序列空间为:该方向上发起的第一个SYN报文的序列号作为该序列空间的起始号码。

因此,CSEQ就是客户端向均衡器发起连接请求的SYN报文序号,也是均衡器向集群节点发起连接的SYN报文序号。DSEQ和SSEQ都是用于建立三次握手产生的第二个SYN报文的序号。因为在Splicing的过程中,报文的长度不会被修改,因此当报文经过均衡器的修改之后的序号偏移是不变的。所以可以保证进行序号映射的时候,只需要进行固定的便宜即可。 报文转换流程详见本文系列之十。

CSEQ:由于通常的通信流程是客户端先向集群发出请求,而在连接建立并且完成有效的调度之后,均衡器就直接可以用先前客户端请求所使用的序列号向集群节点请求数据。这样,均衡器无需将客户端的序列号进行转换,就能直接使用在均衡器à节点之间的通信连接上。

DSEQ:均衡器à客户端 序列号。当均衡器对客户端完成三次握手的过程之后,也就建立了这个方向上的序列空间。均衡器所有发往客户端的数据报文(除了应答报文Ack之外)都使用这个序列空间。

SSEQ:由于集群节点à均衡器的通信连接独立进行,因此它对序列的选择不受均衡器的控制。因此,当均衡器和集群节点完成三次握手之后,新的序列空间SSEQ也同时建立。

那么显然的是:从 客户端à集群节点 这个方向上看,所有的报文序列都采用CSEQ序列空间;而从 集群节点à客户端 这个方向来说,报文必须完成从 SSEQ到DSEQ序列的转换。转换的过程很简单,如果均衡器不对经过的报文的长度作任何修改,那么重新计算的序号就是:

客户端口的新序号=现有的服务器端口收到的报文序号-(SSEQ-DSEQ)

完成这样的映射的计算量非常小,因此在进行报文转换的时候,避免了复杂的序号操作。





回页首


对TCP报文段的转换

为了能够无缝的完成对两个TCP连接的Splicing,均衡器必须对报文的Header部分进行修改。修改面向目标实体兼容,这样在集群两端的客户端和服务器节点,都看不到均衡器的存在,认为连接是与对方直接建立的。因此,均衡器首先需要完成上述的报文序列的重新映射,此外,还要处理以下一些TCP header的信息:

修改IP头信息:

  • 改变源地址和目的地址信息以适应对称的连接,客户端的请求IP包的地址是:客户端IPà集群均衡器IP(也就是VIP);均衡器发往集群节点的转发IP包的地址信息是:均衡器IP地址(VIP或者内部IP)à集群节点的内部IP地址(非VIP)。源地址和目的地址都要进行修改。反方向的IP包的地址转换规则也是类似。这个方法和常见的NAT的实现基本一样。
  • 除去所有进入集群的IP包头部的选项信息。
  • 重新计算IP包的校验和。

修改TCP报文头部信息:

  • 改变TCP报文的源端口和目的端口,同地址修改一样,客户端端口->集群公共端口 ;以及均衡器的发送端口->集群节点的公共端口。但仅需要修改源端口,目的端口不用修改;反方向也类似。
  • 完成TCP报文序列号空间的映射。DSEQ<->SSEQ两个序列号的转换,包括SYN、data和Ack。
  • 对部分能够解释的TCP选项进行转换,使之满足目标系统的TCP/IP协议栈的要求。
  • 修改TCP校验和。

对于TCP校验和的计算,负载均衡器实际上要作两次。对于均衡器从任一方向上收到的报文,在处理之前需要先计算校验和以确定报文正确性;完成报文的地址信息以及选项转换之后,还要再次计算校验和,写入相应的校验和信息发送到splicing的另外一端。

对于紧急指针的处理,均衡器采取不加修改的办法,直接让紧急指针的信息通过均衡器。这样的处理,使得均衡器能够完全兼容伯克利Socket的外带数据实现模式。而通常我们用urgent标记来终止某一端的通信。

TCP提供了"紧急方式(urgent mode)",它使一端可以告诉另一端有些具有某种方式的"紧急数据"已经放置在普通的数据流中。另一端被通知这个紧急数据已被放置在普通数据流中,由接收方决定如何处理。

可以通过设置TCP首部中的两个字段来发出这种从一端到另一端的紧急数据已经被放置在数据流中的通知。URG比特被置1,并且一个16bit的紧急指针被置为一个正的偏移量,该偏移量必须与TCP首部中的序号字段相加,以便得出紧急数据的最后一个字节的序号。

紧急指针通常由应用层的程序处理,例如Telnet和Rlogin这样的程序,当它们从服务器到客户使用紧急方式时,意味着在这个方向上的数据流很可能要被客户的TCP停止。但如果服务器进程进入了紧急方式,尽管它不能够发送任何数据,服务器TCP也会立即发送紧急指针和URG标志。而客户TCP接收到这个通知时就会通知客户进程,于是客户可以从服务器读取其输入、打开窗口并使数据流动。





回页首


对于其他TCP选项的处理

TCP粘合的过程中,涉及到两方面的系统:客户端的系统和集群节点的系统 ,它们在TCP的具体实现细节上可能存在着不同,因此需要在建立TCP连接粘合的时候,由均衡器对双方的进行协商,保证连接上双方的TCP报文彼此相容。客户端首先和均衡器建立SYN三次握手连接,并在粘合连接之后将报文直接发向集群节点。因此均衡器在SYN握手的过程中必须对两个连接的报文选项进行协商或者转换。但是,在握手协议开始直到选择完服务节点之前,均衡器都不知道哪一个服务节点将与该客户端进行通信,所以在不知道目标节点的情况下,均衡器无法协商报文选项。

有几种方法可以解决这个问题。首先,对于不能兼容的连接(通过客户端连接建立时候获得的SYN报文信息可以判断)不予直接粘合,即均衡器强迫连接的每一个报文加载到用户空间进行转换处理,这样的结果显然导致效率低下的问题,集群均衡器退化为应用层代理服务器,但是可以彻底保证连接的兼容性。

另一种方法是,我们让均衡器仅接收满足兼容性的连接,这必须在均衡器和两端(客户端和节点)都完成SYN握手之后进行;对于握手阶段中发现的不能兼容的SYN,拒绝下一步粘合的操作,这种定义最小集的方法可以保护集群的性能,但是对客户端和集群节点的操作系统无形中就有了限制。

第三种方法是允许均衡器对小部分的TCP报文选项进行快速的转换,是介于第一种和第二种方案之间的做法,即满足节点和客户端的异构性又保证了性能不至于下降太多。下面我们就对连接粘合过程中一些扩展TCP报文选择的细节进行讨论。这些扩展主要面向高速IP网络应用,目前在一些比较新的系统中广泛使用,并且保持着比较好向后的兼容性。

TimeStamps时戳

时间戳选项使发送方在每个报文段中放置一个时间戳值。接收方在确认中返回这个数值,从而允许发送方为每一个收到的ACK计算RTT。目前许多实现为每一个窗口只计算一个RTT,对于包含8个报文段的窗口而言这是正确的。然而,较大的窗口大小则需要进行更好的RTT计算。



上图显示了时间戳选项的格式。发送方在第1个字段中放置一个32bit的值,接收方在应答字段中回显这个数值。包含这个选项的TCP首部长度将从正常的20字节增加为32字节。时间戳是一个单调递增的值。由于接收方只需要回显收到的内容,因此不需要关注时间戳单元是什么。这个选项不需要在两个主机之间进行任何形式的时钟同步。RFC 1323推荐在1毫秒和1秒之间将时间戳的值加1。

均衡器可以很容易就完成对时间戳选项的协商,因为如果一个被Splicing的连接中没有对应的时间戳选项信息,均衡器在处理的时候仅仅把该选项丢弃即可,不会影响之后的TCP粘合处理。

Windows Scale窗口扩大因子

窗口扩大选项使TCP的窗口定义从16位增加为32位。这并不是通过修改TCP首部来实现的,TCP首部仍然使用16位,而是通过定义一个选项实现对16位的扩大操作(scaling operation)来完成的。于是TCP在内部将实际的窗口大小维持为32位的值。



这个选项只能够出现在一个SYN报文段中,因此当连接建立起来后,在每个方向的扩大因子是固定的。为了使用窗口扩大,TCP通信的两端必须在它们的SYN报文段中发送这个选项。主动建立连接的一方(这里一般是客户端)在其SYN中发送这个选项,但是被动建立连接的一方(负载均衡器和服务节点)只能够在收到带有这个选项的SYN之后才可以发送这个选项。每个方向上的扩大因子可以不同。

TCP根据接收缓存的大小自动选择移位计数。也就是说,扩大因子的数值自动产生。当然也可以通过特定的接口由应用层进行修改。

客户端可以在发起SYN握手的时候向均衡器协商窗口扩大因子,数值可以是从0到16之间的任一值(用于表示扩大窗口的位移量,实际的窗口大小为:(16bit的windows大小)×2 (扩大因子))。当均衡器向服务节点发起SYN握手请求后,会将先前对应客户端的窗口扩大选项值传递到服务节点进行协商。如果服务节点支持该选项,将会使用该扩大因子与客户端进行splicing通信,尽管客户端仅仅是简单的把服务节点以0位移扩大因子看待。其实,作为典型的客户-服务通信模式,从服务端->客户端的返回数据量往往比较大,在客户端使用较大的窗口扩大因子也便于客户端接收大量数据,提高通信的效率。

如果服务节点不支持窗口扩大因子选项,均衡器需要忽略所有客户端的窗口扩大因子选项,使之无效,这一点和其他的扩展TCP选项的处理模式相同,主要是为了兼容更旧的TCP/IP协议栈实现系统。在后续的通信中,客户端将自动调整扩大因子,仅使用16位窗口大小选项来与服务节点通信。

SACK

选择确认(Selective ACK选择性ACK)SACK是针对TCP协议中的累积确认协议(Cumulative Acknowledgement)提出的。接收方有选择地在SACK确认信息中告知发送方已经正确接收到的部分数据包,而发送方根据SACK就可以只重发出错包,这就避免了不必要的数据重传。当通讯需要更有效率的进行时,通信的双方需要尽早地检测到任何丢失的分组,就发送端而言,可以根据获得的SACK的信息判断哪些分组未被确认,从而可以请求重新传输丢失的分组。

和时戳的处理机制类似,对于SACK的处理均衡器也是先判断粘合连接的两端是否都支持SACK。如果其中的一端在SYN握手之后被判断出不支持这一特性,那么均衡器对于使用SACK选项的另外一端发送的SACK报文将进行丢弃处理,以保证最低兼容性。如果两端的连接都支持SACK,那么均衡器将对SACK报文段中所包含的应答数据块进行序号映射处理――使用了前面所描述的序列空间映射机制。

MMS最大报文段

最大报文段长度(MSS)表示TCP传往另一端的最大块数据的长度。当一个连接建立时,连接的双方都要通告各自的MSS。我们已经见过MSS都是1024。其实,MMS并不是任何条件下都可协商。当建立一个连接时,每一方都有用于通告它期望接收的MSS选项(MSS选项只能出现在SYN报文段中)。



因此,在我们进行TCP Splicing的时候,均衡器一旦接收了连接的粘合请求,就必须对客户端和服务节点的SYN中标识的MMS进行判断并决定如何协商。如果一方不接收来自另一方的MSS值,则MSS就定为默认值536字节。

如果客户端的协议栈支持路径MTU发现(Path MTU Discovery),均衡器会根据客户端SYN 中的信息决定一个最合适的MMS值;在后续的Splicing中,如果被选的服务节点采取了更低的MMS来与均衡器通信的话(这意味着被Splicing的两端连接在MMS上不统一),均衡器会向先前的客户端发出ICMP不可达的错误:"需要分段,请设置DF标志位"的信息,告知客户端刚才的PMTU失败,并迫使客户端采取更低的MMS(和所Splicing的服务节点一致)来重传。而如果客户端不支持PMTU特性,则均衡器简单的强制两端使用MMS最小值536来进行通信。

Path MTU Discovery(MTU路径发现)由RFC1191定义,是一种动态发现因特网上任意一条路径的最大传输单元(MTU)的技术。该技术可以精确的选择出适合当前网络的最大MMS值和MTU路径,并在传输过程中使用该值。以提高TCP/IP协议栈通信的效率。详情参见RFC 1191文档。





回页首


结束语

到此为止,我们完成了对一个面向内容负载平衡集群系统所需的技术分析和原理描述。所介绍的是目前比较主流的TCP Splicing方法,能够满足小型集群系统的第七层交换要求。接下来的文章将讨论一个采用TCP Splicing技术的面向内容负载平衡集群的具体实现模型,关键算法以及核心模块。

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