数据中心网络高可用技术:序

数据中心的网络和家用网络有很大不同,家用网络一个小路由器就够了,挂了的话,就忍受一下没有网络的时间,然后去网上下单再买一个换上。数据中心可不行,所有的东西都要设计成高可用的。

这篇文章来讲讲在数据中心网络用到的技术,主要是和高可用相关的。

本文谈到的网络,重点是3层及以下。因为 4 层是端对端的,如今主要是在服务端软件进行高可用设计,我们在四层负载均衡漫谈里讨论了这些技术并且分析了很多案例。

意义和必要性

高可用指的是,任何一个组件坏了,都不影响其他的服务。比如说,服务器的电源坏了,还要能继续提供服务。怎么做到呢?再加一个电源。高可用的本质就是加机器(冗余硬件),笑。

现在的软件都是高可用设计了,无状态的可以随意扩展,有状态的比如 Etcd 可以部署多个实例,实例之间自己选举,自动 Failover,是不是对机器的可用性要求就低了?

假设一台服务器运行1年不出问题的概率是 99%,那么如果部署一万台服务器的话,1年之内就会有 1% * 10000 = 100 台会出现问题。如果部署的足够多,那么小概率发生的事情在绝对数量上就不会小。如果机器的可用性不够高,那么即使像 Etcd 3 副本部署,同时有 2 台出现故障的概率也会变大。而且,同一个批次的硬件很可能在大约同一时间出现故障,我就见过多次 RAID 里同时坏两块盘的情况。

高可用设计对维护操作也很友好。为了不剧透网络部分好玩的内容,我们还是拿电源举例。现在的服务器大部分是 Dual PSU (Dual Power supply unit,双电源)设计,支持热插拔。如果一个电源坏了,另一个直接继续提供服务,技术人员可以拔掉坏的,换上新的,不需要停机时间。否则的话就麻烦了,需要联系机器负责人,机器负责人联系上面运行的进程负责人,大家开始迁移服务,然后才能操作。

像双电源的设计还可以缩小故障影响范围,比如两个电源单位,分别去连接两条电线。假设一个强电线路挂了,就和电源单位挂了一样,另一个电源直接上岗。如果没有的话,可能大批的服务器就下线了。

说到底,最重要的是交付的服务质量。对于数据中心来说,交付的服务就是机器,提高服务质量就是最大程度提高机器的可用性。向客户提供服务的时候,尽最大努力提高服务的质量;使用别人的服务的时候,要假设别人的服务可能挂掉。所以 PaaS 平台使用这些机器,要假设机器会挂,使用其他技术手段提高 PaaS 的可用性,提供给别人用;SaaS 使用 PaaS 的时候,也假设 PaaS 也可能会挂,通过技术提高自己软件服务的可用性。

所以在数据中心中,机器都尽可能设计成了高可用的:硬盘用 RAID 做容器,电源做冗余,网络做冗余。这个博客使用的虚拟机,已经 1117 天没有关机了,可用性非常高。

kawabangga.com 所在的服务器运行时间

这个系列文章就来谈谈网络是怎么实现高可用的。比如,从服务器到交换机的链路如何做到高可用,从交换机到交换机,从服务器到网关路由器,等等。

明确一下网络高可用的目标。服务器最终的目标是提供服务,服务通过应用层网络协议提供出去:HTTP,webRTC 等等。这些协议都是基于 TCP 或者 UDP,由于四层的高可用技术主要是四层负载均衡解决了,四层负载均衡一般使用的是商用服务器,在这些服务器前面的网络设备主要工作在二层和三层,对于这些设备来说,四层网络就是这些设备要传输的数据。这些设备要做的就是:确保四层数据的传输是高可用的,或者说,尽量在三层 IP 协议上保证可用性,这也是 IP 协议的设计:Best effort.

最后我们这样总结一下这些技术的最终目标:在服务器上 ping 一个互联网 IP,中间的硬件无论哪一个挂了,高可用的网络设计都要保证能够继续 ping 的通(中间可能会有短暂的丢包)。

基础知识回顾

在讨论这些好玩的技术之前,我们先重新认识一下机房中的设备,和这些设备的工作方式。和网络相关的,主要就是服务器、交换机、和路由器了。

服务器

服务器是最常见的终端设备了,也是开发者最熟悉的设备。它没有网络的转发功能,是其他网络设备的主要用户。

在讨论服务器的时候,我们要从两个方面分开讨论:

  1. 发出流量,又叫 Transmit, outbound;
  2. 接收流量,又叫 Receive, inbound;

流量都是通过网络单元的形式发送出去的,在三层叫做包,二层叫做帧。服务器拿 Linux 来距离,在发送数据的时候,无论是 TCP 还是 UDP,都是先封装成三层数据包,再封装成二层数据帧,最后通过物理层让网卡发送数据。不是所有的数据都是基于三层协议发送的,但是数据一定是通过二层协议发送出去的,因为二层是连接物理层的电信号和数字信号的地方。数据中心的二层协议一般就是 Ethernet 了。

数据封装成三层包很简单,因为应用发送数据的时候已经指定了 IP 地址,kernel 根据这些信息封装添加 I P header 就完成了。

封装成二层也很简单,主要是添加二层 Header 和计算 CRC。Header 中最重要的是放上自己的 MAC 地址和目标的 MAC 地址。怎么知道目标 MAC 地址应该写啥呢?首先根据要发送的目标 IP 和自己的子网判断目标 IP 是否和自己在同一个子网,如果在同一个子网,那么目标 MAC 地址就写目标 IP 的 MAC 地址;如果不在同一个子网,就写默认网关 (default gateway) 的 MAC 地址。那又怎么知道目标 IP 或者默认网关的 MAC 地址是什么呢?机器本地是没有办法知道的,所以要通过网络协议询问,即 ARP 协议。

想知道一个 IP 对应的 MAC 地址,就设置一个问题,问「谁有 IP xxx 的 MAC 地址?如果知道,请回复给 IP yyy,yyy 的 MAC 地址是 B。」前面提到 ARP 也是基于二层的,我们就得把这个问题封装到二层中,来源 MAC 就是自己的 MAC,B,目标 MAC 地址是 FF:FF:FF:FF:FF:FF,即广播给所有的人。这时候所有人会收到这个 ARP 问题,但是只有 IP 是 xxx 的会回复,告知其 MAC 地址。其他人虽然不回复,但是也会收到这个 ARP 问题,通过这个 ARP 问题,所有的人都知道了:「哦,虽然不需要我回答,但是我也获得了 yyy 的 MAC 地址,我先记下来,说不定以后用得到呢。」

通过 ARP 协议拿到了 MAC 地址,就可以完成二层的封装了,最后通过物理层发送出去。ARP 协议的目的就是找到 IP 和 MAC 地址的对应,这个记录会在机器上缓存一段时间,减少 ARP 查询量,也能减少 ARP 带来的延迟。

接收流量的行为很简单,网卡从物理层收到网络数据,解析成二层数据帧,判断目标 MAC 地址是否是发给我的,如果不是就丢弃,如果是,就把数据交给 kernel。如果是广播报,就根据协议判断是否需要回复,如上面提到的 ARP。

交换机

交换机和路由器都是流量转发设备,对它们来说,流量就没有发出和接收的区别了,它们的工作都是将收到的网络包转发出去。交换机工作在二层,路由器工作在三层。

交换机的转发逻辑很简单,它只看二层的 header 来完成转发。交换机有一个 MAC address table,是 MAC 地址和交换机端口的对应,交换机启动的时候 MAC address table 是空的,也不需要配置和导入数据,交换机可以自己「学习」。收到一个二层数据帧,交换机做的事情如下:

  1. 把来源 MAC 地址和来源端口号记录到 MAC address table 中,这就是交换机的「学习」过程,它从每一个收到的包进行学习;
  2. 根据目标 MAC 查找自己的 MAC address table;
  3. 如果找到目标 MAC 对应的端口号,就从这个端口把数据转发出去;
  4. 如果找不到,就转发到除了来源端口之外的其他端口(参考上文服务器收到帧的操作,如果不是发给自己的,会丢弃);
交换机通过收到的包学习 MAC 地址和端口的对应关系

MAC 地址表中,一个 MAC 地址只能对应唯一一个端口,不能对应多个端口。即,表示这个 MAC 转发到这个端口;但是一个端口可以对应多个 MAC 地址,或者说,多个 MAC 地址可以对应到同一个端口,因为一个端口后面不仅仅可以是服务器,也可以是交换机。

路由器

路由器工作在三层,但是三层必须要依靠二层承载才能工作,所以路由器里面也有一个二层实现。在每次收发数据包的时候,它的工作方式是和服务器一样的,都是通过 ARP 询问 MAC 地址,然后把要发送的三层包添加二层 header(主要是 MAC 地址),发送出去。对于交换机的视角来说,路由器和服务器一样,都是它的「客户」。

在转发数据包的时候,路由器每次拿到一个 IP 包,就检查自己的路由表,找到一个出口端口,然后从这个端口发送出去。

它和交换机一样,主要的工作是「转发」,只不过交换机参考的是 MAC 与端口的对应表,路由器参考的是 IP 与端口的对应表。

路由表怎么来呢?路由器有两个「面」:控制面和数据面。控制面负责生成路由表,数据面负责将进来的包经过查找路由表转发出去。

路由表有两种方式生成,一种是静态的配置。其实 Linux 服务器也有一个路由表,通过 ip route 命令可以看,只不过里面的项目很少,最重要的是默认网关,默认网关一般使用静态配置。但是对于路由器来说,端口太多,只有一个默认网关是不够的,在数据中心中,基本上不使用静态配置的方式。

另一种就是动态协商,比如 EIGRP,BGP,OSPF 等等。简单来说,就是路由器之间互相交换自己能够连接到的子网的信息,路由器根据这些信息生成路由表。

讲了这么多无聊的东西,我们终于可以开始讨论有趣的高可用方案了。不过无聊归无聊,后面用到的技术其实就是对这些基础行为的巧妙利用(或者说,滥用?)。所以复习一下还是有必要的。

下一篇文章,我们从分析如何让服务器到交换机的连接做到高可用开始讨论。

数据中心网络高可用技术系列

  1. 数据中心网络高可用技术:序
  2. 数据中心网络高可用技术之从服务器到交换机:active-backup
  3. 数据中心网络高可用技术之从服务器到交换机:balance-tlb 和 balance-alb
  4. 数据中心网络高可用技术之从服务器到交换机:链路聚合 (balance-xor, balance-rr, broadcast)
  5. 数据中心网络高可用技术之从服务器到交换机:802.3 ad
  6. 数据中心网络高可用技术之从交换机到交换机:MLAG, 堆叠技术
  7. 数据中心网络高可用技术之从服务器到网关:VRRP
 

理解网络的分层模型

虽然刚接触网络的时候,就首先学习了分层的模型,大部分的教材也是会在最开始就讲解网络的分层模型,但是与网络分层相关的实践却很少。

笔者直到工作多年以后,对此的理解才慢慢清晰起来。这一篇就讲一下我对「分层」的理解。这些概念看起来很简单,但是结合实践之后,会有不断有新的理解冒出来,越来越深入。

网络是如何连接的?

我们访问一个网站,本质是发送多个 HTTP 请求,然后收到 HTTP 响应。DNS 先抛开不说,因为它的作用是名字和 IP 的对应,拿到 IP 之后,我们和服务器之间的交互就不需要 DNS 了。假设我们现在已经知道了网站的 IP,那请求是怎么从我们的笔记本电脑发送到网站的呢?

从物理层面,这个数据包要从电脑发送到路由器,路由器发送给运营商,运营商内部经过很多的路由器和交换机,最后到达服务器。整个转发链路可以看作是经过了很多次设备两两之间进行转发,每一次包被转发都发生在两个设备之间(广播也可以看作是很多次两个设备之间的转发)。所以这个问题可以聚焦于每两个设备之间转发的时候,对数据包做了哪些处理。

数据包结构

这个图做了简化,只保留了每一层中最重要的地址信息。

二层做转发的时候,比如笔记本发送给路由器,会把 SRC MAC 地址设置为自己的 MAC 地址,DST MAC 地址设置为路由器的 MAC 地址。

它完全不看三层和三层以上的内容。所以数据包对它来说就像是这个样子,IP 地址等三层内容变成了它的数据:

二层转发视角

电脑转给路由器,设置 MAC 地址看起来很没必要?是的,这种相当于点对点的网络,没有地址也不会发错的。也确实存在一些其他的二层技术,比如 Frame Relay,二层的包头就没有 MAC 地址(但是有类似 MAC 地址作用的东西)。

但是下面这个结构,MAC 地址就很有用了。比如电脑 1 要发送给电脑 2 内容,DST MAC 地址写 MAC 2,然后交给交换机,交换机就会转发给电脑2。如果没有 MAC 的话,交换机收到这个包,就不知道应该发给 2 还是 3 了。

交换机在子网转发

如果有了 MAC 地址,交换机就可以把这个包从 MAC 地址对应的端口转发出去。

如今的大部分交换机都是非常「智能」的设备,可以通过查询 MAC 地址查找对应的端口进行转发。四十年前的设备不这么智能——它从一个端口收到包,就直接转发给其他所有的端口,就是 Hub(集线器)。所有的主机都会收到这个包,但是会检查 MAC 地址是否属于自己,如果不是,就丢弃。Ethernet 本身的设计就是将包转发到所有的端口,按广播的设计来的,所以它有冲突检测等设计,如今已经很少用到了。

三层转发同理,它只关心三层的内容,最重要的是 IP 地址。检查目标 IP,然后根据自己的路由表,在多个接口(路由器一般有多个接口,连接不同的网络,家用路由器可以理解为有两个接口,一个连接家里的内网,一个连接 ISP 那边的网络)选择其中的一个,然后通过这个接口将数据发送出去。路由表可以理解成是三层设备的一个转发数据库,是它最重要的依据,它的功能就是给定一个 目标 IP,可以从中查询出一个物理出口,然后就可以使用这个物理出口转发出去。

这个时候,二层网络就成了它的载体:二层用什么技术不重要,只要能给三层把封装好的三层包发到对面就可以,Ethernet 可以,Wi-Fi 可以,Frame Relay 也可以。如果是 Ethernet 的话,就把三层包封装到一个二层里面,即在外面贴上 DST MAC 地址是对面的设备,SRC MAC 是自己的 MAC,最后用物理层发送。

所有上层协议都可以依赖 IP 协议发送自己的数据,TCP, UDP, ICMP 等等,都是将 IP 作为载体。

互联网的沙漏,图来自Computer Networking: A Top-Down Approach, 7th Edition, Figure 4.13

通过互联网我们可以访问世界各地的网站(从网络的角度说,就是服务器,设备),就需要我们的电脑和世界上所有的服务器之间,都存在两两连接的线路,这些线路通常都是物理线路,海底光缆,ATM 线路,双绞线,铜线等等。无线技术一般只会用在接入层,因为它信噪比较低,转发设备之间很少用无线技术,一般都是物理线路连接的。所以网络上有人吵架的时候说「我要顺着网线爬过去掐死你」,假设他真的能够顺着网线爬,那么他确实能够从家里的网线爬到世界的任何地方。

所有的设备之间能够互相通讯,这就要求这些设备之间都运行了某种一样的协议,才能懂彼此的语言。这个协议就是 IP。IP 的上层有很多协议,下层也可以依赖各种协议,但是中间必须是 IP。每一个网络的自治域内可能使用非 IP 协议(但是我好像没听说过),但是自治域之间,都是用 IP 交换信息的,交换 IP 路由信息的协议都是用 BGP。就像一个沙漏一样

不同层的设备

网络中所有两个设备之间的转发都要经过物理层。物理层能做的事情相对较少。「集线器」就是物理层的设备,说它是物理层是因为二层及以上的数据都是它的内容,它连二层的内容也不看。从一个口收到数据,无论是什么,都直接复制到其他的口发出去。像是一个「端口复读机」。

二层设备主要是按照 MAC 地址转发,它不修改包的内容。不过上面的例子不知读者有没有发现一个问题——交换机怎么知道哪一个接口对应 MAC2 呢?如何将收到的包发送出去?其实很简单,如果它不知道,就把这个包发送给所有的接口。和「集线器」不同的是,交换机可能一开始不知道 MAC 和端口的对应,但是它可以学习。学习主要通过收到的包的来源 MAC 地址,比如这个包是从接口 1 收到的,而且包里面有一个 SRC MAC 是 MAC1,那二层设备就记住了,下次发给 MAC1 的时候,我就直接发送给接口 1,不用发给所有人了。这就叫「MAC 地址学习」,学习到的内容叫做 MAC 地址表,存储在内存中。

三层设备就是按照 IP 转发了。三层设备的负担更重一些,因为无法像二层那样简单地从要转发的包里面「学习」路由信息。因为发送者也不知道怎么转发到终点 IP 呀,人家指望着你呢,已经把包交给到你的手上了。三层设备根据自己的路由表转发,路由表可以通过静态配置。比如配置成往 xx 这个网段发送的话,就走接口1,如果往 yy 那个网段发送的话,就走接口2,如果找不到的话,就走接口3,类似这种。但是机房内设备众多,不可能一一手动配置,一般都是动态生成的。即,所有的路由器都知道自己的直连网络,然后在路由器之间交换彼此的信息,这样,大家彼此依靠,共同描绘一个转发地图。具体的路由协议内容非常复杂,就不展开了。总之,三层设备之间在交换两种信息:一种是路由信息,这部分我们也叫「控制面」;另一种就是实际要转发的数据了,我们叫「数据面」。

注意这一段的描述中,我用「二层设备」和「三层设备」来描述,而不是「交换机」和「路由器」,因为现代化的网络中,二层设备和三层设备的界线已经很模糊了,很多交换机都可以做三层转发。比如我们常说的「家用路由器」,它其实是一个 交换机+路由器+NAT + 防火墙。路由器听起来也比交换机高级,其实也不是,机房中有很多交换机是高规格的,比路由器要贵的多,能力也比路由器要猛,几百个口都能达到线速。

网络协议与网络分层

上面的问题,还剩下最后一块拼图:电脑知道要三层怎么封装,加上 IP 就行了,然后得封装好二层才能发出去。路由器的 IP 是手动设置好(或者通过 DHCP 协议得到)的,但是路由器的 MAC (DST MAC) 怎么写呢?

你要在一个陌生的班级的教室里要找李小明,怎么找?肯定是大喊一声「谁叫李小明呀?」

同理,当发送者不知道一个 IP 对应的 MAC 地址的时候,就发给网络内的所有人:「如果有人是这个IP,请回复给我,我的 MAC 是 MAC1」。于是路由器就回复给 MAC1「是我是我」,回复的包 SRC MAC 也设置为自己的 MAC。

所以说 ARP 是几层协议呢?大部分资料说是二层,也有的说二层和三层之间。

我觉得这种讨论没有意义。因为我们实际上在讨论两个东西而且试图将两个概念融合在一起。即:

  • 协议工作在几层(基于几层实现)?
  • 协议为几层提供服务?

如果分开讨论,就清楚很多了。ARP 基于二层(意味着只用到了二层的功能,不需要三层的东西就可以工作)实现,为三层提供服务,帮助找到 IP 对应的 MAC 地址。

我们可以给很多有类似争议的协议定义:

  • TLS 基于四层实现,为应用层提供服务;
  • TCP 基于三层实现,为应用层提供服务;
  • ICMP 工作在三层,为三层提供服务;
  • EIGRP 和 OSPF 基于 IP 协议,为三层提供服务;
  • BGP 基于四层实现,为三层提供服务(是不是很神奇?因为 BGP 通过 TCP 交换路由信息,为三层提供转发路由);

为什么要分层?

我的理解是:为了让每一个协议可以处理部分的问题,不同的协议之间可以互相工作。比如 IP 协议只要一个二层帮他转发,任何二层协议都可以,这样,在 Wi-Fi 大普及,2G 升级 3G 升级 4G 等等的时候,我们都可以继续用 IP 协议,不需要跟着升级换代。就像公司划分了不同的部门,每一个部门负责一部分事情,只要将事情做好,部门内部做事的方式与其他的部门无关。

每一个协议都给出了自己的承诺:比如三层协议,它承诺尽可能保证包到达目的地址;四层向应用层保证,用我传输的数据一定不会丢包,一定不会不完整,一定不会乱序,放心好了。所以我们在做应用层编程的时候,从来不需要关心(数据层面的)乱序,丢包等问题。

基于下层协议的承诺来工作,节省了很多力气。有点类似我们编程的时候,不是从头开始写字符串处理,而是用各种库一样。

理解协议

作为用户,我们在使用这些协议的时候,要知道这些协议提供的功能是什么,要求是什么。有点像用三方库之前要阅读文档一样。

这样很多问题其实也就不是问题了。比如「粘包」问题,是一个被人诟病的面试题。它是问「如果使用 TCP 发送多个包,这些包粘在一起无法分开怎么办?」这么问出来就显得提问者不懂 TCP,因为 TCP 的设计就是帮助用户发送一个字节流,它本身就没有「包」这个概念,所有的数据就是要「粘」在一起发送的。这并不能说是一个问题,而是 TCP 本身的特性。如果你使用 TCP 协议,你就要在这之上设计自己的协议,把自己的协议设计成可以让 TCP 使用「流」的方式传送。比如,HTTP 协议是使用 \r\n\r\n 来分割来 Header 和 Body,然后通过读 Header 中 Content-length 的长度来判断 Body 要读到那里;Redis 协议大致是先用一个数字表示内容的长度,读完了的话,再读就是下一个请求了。

再举个例子,就是对 HTTP 协议的误解。作为用户(甚至是程序开发者),我们对 HTTP 的感受可能是:GET 只需要输入一个 URL,Post 需要输入 URL 和 body,URL 在浏览器的地址栏可以看到,但是 Body 内容看不到,所以有人甚至会认为 Post 方法比 GET 更安全。但其实,如果抓包就会发现,GET 和 POST 两个 HTTP method 发送了相同的数据结构内容,GET 和 Post 就是一个字段的区别。所以面试官问:GET 和 POST 有什么区别吗?我觉得从协议结构上除了一个 method 字段之外没有区别:

  • GET 也可以发送 Body;
  • POST 也可以传输 url params;
  • POST 不会比 GET 更安全,POST 的 body 经过抓包也是一览无余;

网络的分层对问题排查的意义

工作中,在排查问题的时候,网络分层也至关重要。

从上面的描述,我们可以得出结论:如果某一层出现问题,这一层上面的所有协议都会有问题。因为上层协议是基于下层来实现的。在排查问题的时候,比如我们连不上数据库,首先使用 nc -v 10.0.0.1 3306 来确认问题,如果连不上说明 TCP 层有问题,然后去 ping 10.0.0.1,如果能通,说明三层没有问题,这就意味着三层及以下不需要排查了,像什么 ARP,路由信息,都不用查了——三层能通四层不通,问题必定出在四层(可能是防火墙 block 了端口)。相反,如果 ping 不通,就需要继续排查下层协议的问题,有没有可能是 ARP 的问题,等等。以此类推,如果在 nc -v 10.0.0.1 3306 这一步发现可以连通,那么说明四层及以下没有问题,就往上看就好了,是不是数据库程序卡住了?

在描述问题的时候,分层模型也能让我们更好地与同事交流。我经常看到有人这么问:「请检查下数据库有没有问题」,问题是,DBA 可能无法登陆你的机器,无法使用你的环境做测试,经过一些检查,他只能告诉你「没问题」。但是问题是有的,可能不是数据库的问题,于是就找更多的人来帮忙,最后大家在群里面面相觑,问题还是在那里。假设用户理解了网络的分层呢?他可能这么提问:「我们连接这个 IP 不通 10.0.0.1 ,端口是 3306,但是可以 ping 通这个 IP」,DBA 马上就可以开始定位问题了。

但是实际上在抓包和分析的时候,没有人会事先告诉你这是第几层的问题。解决实际的问题不是考试,所以我们必须理解每一层的职责是什么,才能根据具体的现象去判断应该在哪一层来寻求线索。

参考资料

==计算机网络实用技术 目录==

这篇文章是计算机网络实用技术系列文章中的一篇,这个系列正在连载中,我计划用这个系列的文章来分享一些网络抓包分析的实用技术。这些文章都是总结了我的工作经历中遇到的问题,经过精心构造和编写,每个文件附带抓包文件,通过实战来学习网路分析。

如果本文对您有帮助,欢迎扫博客右侧二维码打赏支持,正是订阅者的支持,让我公开写这个系列成为可能,感谢!

没有链接的目录还没有写完,敬请期待……

  1. 序章
  2. 抓包技术以及技巧
  3. 理解网络的分层模型
  4. 数据是如何路由的
  5. 网络问题排查的思路和技巧
  6. 不可以用路由器?
  7. 网工闯了什么祸?
  8. 网络中的环路和防环技术
  9. 延迟增加了多少?
  10. TCP 延迟分析
  11. 重新认识 TCP 的握手和挥手
  12. 重新认识 TCP 的握手和挥手:答案和解析
  13. TCP 下载速度为什么这么慢?
  14. TCP 长肥管道性能分析
  15. 后记:学习网络的一点经验分享
与本博客的其他页面不同,本页面使用 署名-非商业性使用-禁止演绎 4.0 国际 协议。
 

重新认识 TCP 的握手和挥手

在教科书上,我们学过 TCP 的 3 次握手和 4 次挥手,请读者思考一下:TCP 握手一定会使用 3 个包吗?TCP 的挥手一定会使用 4 个包吗?

下载这个抓包文件,并且分析以下问题:

问题1: 这个抓包文件中,一共有几个 TCP 连接?

问题2: 对于每一个 TCP 连接,请找出来和 TCP 握手有关的 3 个包,以及和 TCP 挥手有关的 4 个包

TCP 是可靠的传输层协议,这意味着在 TCP 连接中,丢失的每一个数据都会被识别出来。考虑这种情况:在一个 TCP 连接中,发送端发送完最后一个 segment,然后发送 FIN 包结束连接。即,发送端发送了两个包。这时候:

  • 假设 segment 数据丢失了,那么发送端不会得到这个 segment 的 ACK,需要重传 segment 数据;
  • 如果 FIN 丢了,也需要重传 FIN,4 次挥手中,ACK 的作用就是确保 FIN 也得到了 ACK 确认。

那么最后的问题是:如果发送端发送了 segment 数据和 FIN,但是只收到一个 ACK,发送端如何知道 ACK 的是 segment 数据,还是 FIN 包,还是二者都是?注意,ACK 会有 ACK 的序列号,表示这个号码之前的数据都已收到。但是 FIN 的数据长度是 0。那么 ACK 如何区分 FIN这个包 和 FIN 之前的数据呢?

发送端的问题

提示:TCP 肯定已经处理好了这种情况,所以答案就在 RFC 793 中。如果不想阅读 RFC,那么可以仔细分析 ACK number 来找到答案。

==计算机网络实用技术 目录==

这篇文章是计算机网络实用技术系列文章中的一篇,这个系列正在连载中,我计划用这个系列的文章来分享一些网络抓包分析的实用技术。这些文章都是总结了我的工作经历中遇到的问题,经过精心构造和编写,每个文件附带抓包文件,通过实战来学习网路分析。

如果本文对您有帮助,欢迎扫博客右侧二维码打赏支持,正是订阅者的支持,让我公开写这个系列成为可能,感谢!

没有链接的目录还没有写完,敬请期待……

  1. 序章
  2. 抓包技术以及技巧
  3. 理解网络的分层模型
  4. 数据是如何路由的
  5. 网络问题排查的思路和技巧
  6. 不可以用路由器?
  7. 网工闯了什么祸?
  8. 网络中的环路和防环技术
  9. 延迟增加了多少?
  10. TCP 延迟分析
  11. 重新认识 TCP 的握手和挥手
  12. 重新认识 TCP 的握手和挥手:答案和解析
  13. TCP 下载速度为什么这么慢?
  14. TCP 长肥管道性能分析
  15. 后记:学习网络的一点经验分享
与本博客的其他页面不同,本页面使用 署名-非商业性使用-禁止演绎 4.0 国际 协议。
 

TCP 延迟分析

本文是对《延迟增加了多少?》一文的答案和分析。

总延迟应该是 900ms 左右。

Ping 的延迟指的是 RTT, Round Trip Time. 即一个包发过去,对方发一个包回来,总延迟是 200ms。一种误解是认为 ping 测得的延迟是 200ms,所以一个请求发过去是 200ms,响应发回来是 200ms,总延迟是 400ms。如果仔细想一想的话,我们在发送端测量延迟的时候,没有办法只测量一个包从发送端达到接收端的延迟。除非是让接收端在回复的时候记录收到包的时间?但是发送端和接收端的时钟可能不一致,如果精确测量的话,协议上就要依赖不同的机器时钟对齐。直接让总时间除以 2?这也意义不大,因为包去和回的路线不一定一样,延迟也不一定是一半一半。所以我们在讨论延迟的时候,都是默认 RTT

TCP 握手的延迟是 1 个 RTT,即 200ms。这里也有很多人会有一个误解,就是认为「TCP 是三次握手,所以握手带来的延迟是 1.5 RTT」。这个误解是因为教科书的 TCP 序列图太迷惑了,看起来像是握手需要发送 3 个包,之后才能发送数据。实际上,在握手阶段,第三个包 ACK 发出之后,发送端直接开始发送数据。即,客户端发送 SYN 建立握手,收到 SYN+ACK,消耗了 1 个 RTT;发送端随即发送 ACK,然后直接进入数据发送阶段,开始发送数据。所以握手阶段第三个 ACK 包是不会带来延迟的

很多教材的 TCP 握手序列图是这样
但其实应该是这样,在第三个 ACK 发送之后直接开始发送数据

上文题目提到,请求的大小是 16KiB,这里经常出现的误解是:「一个 Frame 的 MTU 是 1500bytes,减去 20 bytes IP header 和 20 bytes TCP header,一个包携带的实际数据是 1460 bytes,所以 16KiB 请求传输完成是 16KiB/1460 bytes 个 RTT。」这也是被教科书常用的序列图迷惑了,TCP 传输中,发送端并不是发送一个包,等待 ACK,发送下一个包……而是直接传输很多包,接收端可以直接用一个 ACK 去 ACK 多个包。发送端是「一组包一组包地发送」,而不是「一个包一个包地发送」。

错误的序列图:每一个数据都等待 ACK
实际上一直发送数据,直到达到 rwnd 或者 cwnd 的瓶颈

那么一下子可以发送多少个包呢?发送端可以一下子把 16KiB 的数据都发送完吗?这里涉及到 TCP 的拥塞控制了。为了避免过载接收端和中间链路上的路由器等设备,TCP 发送端发送出去但是未确认的数据会保持在接收端窗口 (rwnd)和拥塞控制窗口之内 (cwnd)。接收端的窗口一般不是瓶颈,所以这里忽略不讨论。cwnd 在 Linux 的初始值是 10,即 TCP 连接建立之后,会一下子发送 10 个 MSS 的数据,即 1460 bytes * 10 大约是 14.6KiB 的数据。然后会等待接收端发送回来 ACK,再开始下一轮数据的发送,并且逐渐增大 cwnd。对于响应,同理,这时候服务 B 的服务器变成了发送端,也是需要发送两轮数据。更详细的解释可以阅读这篇:《TCP 拥塞控制对数据延迟的影响》。

最后,来到了四次挥手。这里的误解是 TCP 断开连接 4 次挥手需要花费 2 个 RTT。实际上,断开连接的过程是不产生耗时的,因为在 TCP 断开连接之前,应用的请求已经发送完成,响应也已经收到,kernel 已经将收到的数据送给了用户态,用户态的程序已经继续运行了。即使应用调用 close(), 也是直接返回,kernel 再慢慢处理关闭连接的过程。所以断开连接是 kernel 来做的「收尾工作」,不会贡献请求处理的延迟。

综上,总结一下,实际耗时是 900ms,其中:

  • TCP 建立连接需要 1 个 RTT;
  • 如果是 HTTP 请求很小,响应也很小,那么请求和响应需要耗费 1 个 RTT;
  • 但是 HTTP 请求很大,因为 cwnd 的限制,需要额外耗费 1 个 RTT 来传输;
  • 同理,响应也很大,也需要额外花费 1 个 RTT;
  • 服务端程序处理需要花费 100ms,保持不变;

最后是 200ms * 4 个 RTT + 100ms = 900ms;

再留一个问题给读者:你知道应该如何优化,让这个请求耗费的延迟最小吗?(分析一下最小延迟是多少,一个 RTT 用来传输数据是比不可少的,服务端的 100ms 也无法节省,所以最小延迟是 300ms,如何从 900ms 优化到 300ms 呢?)

提示

==计算机网络实用技术 目录==

这篇文章是计算机网络实用技术系列文章中的一篇,这个系列正在连载中,我计划用这个系列的文章来分享一些网络抓包分析的实用技术。这些文章都是总结了我的工作经历中遇到的问题,经过精心构造和编写,每个文件附带抓包文件,通过实战来学习网路分析。

如果本文对您有帮助,欢迎扫博客右侧二维码打赏支持,正是订阅者的支持,让我公开写这个系列成为可能,感谢!

没有链接的目录还没有写完,敬请期待……

  1. 序章
  2. 抓包技术以及技巧
  3. 理解网络的分层模型
  4. 数据是如何路由的
  5. 网络问题排查的思路和技巧
  6. 不可以用路由器?
  7. 网工闯了什么祸?
  8. 网络中的环路和防环技术
  9. 延迟增加了多少?
  10. TCP 延迟分析
  11. 重新认识 TCP 的握手和挥手
  12. 重新认识 TCP 的握手和挥手:答案和解析
  13. TCP 下载速度为什么这么慢?
  14. TCP 长肥管道性能分析
  15. 后记:学习网络的一点经验分享
与本博客的其他页面不同,本页面使用 署名-非商业性使用-禁止演绎 4.0 国际 协议。
 

延迟增加了多少?

小周(就是你!)所在的团队管理着一个服务 A,这个服务 A 需要访问服务 B 的 HTTP 接口。服务 A 和服务 B 部署在同一个 IDC 中,所以延迟很低,可以忽略不计。服务 B 处理请求需要花费 100ms,所以一个 HTTP 请求的总延迟大概是 100ms。

服务A在同一个 IDC 中请求服务 B

最近由于合规的要求,服务 B 需要迁移到另一个 IDC 中,物理延迟会上升。迁移之后,如果从服务 A 的 IP 去 ping 服务 B 的 IP,ping 显示延迟为 200ms

服务 B 迁移到了另一个 IDC,这时候 ping 是 200ms

在打开抓包文件分析之前,请问:如果服务 A 发送一个 HTTP 请求到服务 B,总延迟现在是多少?

注意:

  • TCP 需要重新建立连接
  • 请求的大小是 16KiB
  • 响应的大小是 20KiB

然后用 Wireshark 打开抓包文件,分析实际的延迟是多少?和自己的答案作对比。

提示:在分析延迟问题的时候,可以使用这里的方法,打开 Time Delta 列。

==计算机网络实用技术 目录==

这篇文章是计算机网络实用技术系列文章中的一篇,这个系列正在连载中,我计划用这个系列的文章来分享一些网络抓包分析的实用技术。这些文章都是总结了我的工作经历中遇到的问题,经过精心构造和编写,每个文件附带抓包文件,通过实战来学习网路分析。

如果本文对您有帮助,欢迎扫博客右侧二维码打赏支持,正是订阅者的支持,让我公开写这个系列成为可能,感谢!

没有链接的目录还没有写完,敬请期待……

  1. 序章
  2. 抓包技术以及技巧
  3. 理解网络的分层模型
  4. 数据是如何路由的
  5. 网络问题排查的思路和技巧
  6. 不可以用路由器?
  7. 网工闯了什么祸?
  8. 网络中的环路和防环技术
  9. 延迟增加了多少?
  10. TCP 延迟分析
  11. 重新认识 TCP 的握手和挥手
  12. 重新认识 TCP 的握手和挥手:答案和解析
  13. TCP 下载速度为什么这么慢?
  14. TCP 长肥管道性能分析
  15. 后记:学习网络的一点经验分享
与本博客的其他页面不同,本页面使用 署名-非商业性使用-禁止演绎 4.0 国际 协议。