This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
<audio id="audio" title="第37讲 | 知识串讲:用双十一的故事串起碎片的网络协议(上)" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7c/6a/7c5687c9e0486d2435efe8f0d779e76a.mp3"></audio>
基本的网络知识我们都讲完了,还记得最初举的那个“双十一”下单的例子吗?这一节开始,我们详细地讲解这个过程,用这个过程串起我们讲过的网络协议。
我把这个过程分为十个阶段从云平台中搭建一个电商开始到BGP路由广播再到DNS域名解析从客户看商品图片到最终下单的整个过程每一步我都会详细讲解。这节我们先来看前三个阶段。
## 1.部署一个高可用高并发的电商平台
首先,咱们**要有个电商平台**。假设我们已经有了一个特别大的电商平台,这个平台应该部署在哪里呢?假设我们用公有云,一般公有云会有多个位置,比如在华东、华北、华南都有。毕竟咱们的电商是要服务全国的,当然到处都要部署了。我们把主站点放在华东。
<img src="https://static001.geekbang.org/resource/image/ed/20/eddde5929de2a72b197321e5ad87e120.jpg" alt="" />
为了每个点都能“雨露均沾”也为了高可用性往往需要有多个机房形成多个可用区Available Zone。由于咱们的应用是分布在两个可用区的所以假如任何一个可用区挂了都不会受影响。
我们来回想[数据中心](https://time.geekbang.org/column/article/10098)那一节,每个可用区里有一片一片的机柜,每个机柜上有一排一排的服务器,每个机柜都有一个接入交换机,有一个汇聚交换机将多个机柜连在一起。
这些服务器里面部署的都是计算节点每台上面都有Open vSwitch创建的虚拟交换机将来在这台机器上创建的虚拟机都会连到Open vSwitch上。
<img src="https://static001.geekbang.org/resource/image/d6/a7/d66c01c39e911e784525a118c37b50a7.jpg" alt="" />
接下来,你**在云计算的界面上创建一个VPC**Virtual Private Cloud虚拟私有网络指定一个IP段这样以后你部署的所有应用都会在这个虚拟网络里使用你分配的这个IP段。为了不同的VPC相互隔离每个VPC都会被分配一个VXLAN的ID。尽管不同用户的虚拟机有可能在同一个物理机上但是不同的VPC二层压根儿是不通的。
由于有两个可用区在这个VPC里面要为每一个可用区分配一个Subnet也就是在大的网段里分配两个小的网段。当两个可用区里面网段不同的时候就可以配置路由策略访问另外一个可用区走某一条路由了。
接下来,应该**创建数据库持久化层**。大部分云平台都会提供PaaS服务也就是说不需要你自己搭建数据库而是采用直接提供数据库的服务并且单机房的主备切换都是默认做好的数据库也是部署在虚拟机里面的只不过从界面上你看不到数据库所在的虚拟机而已。
云平台会给每个Subnet的数据库实例分配一个域名。创建数据库实例的时候需要你指定可用区和Subnet这样创建出来的数据库实例可以通过这个Subnet的私网IP进行访问。
为了分库分表实现高并发的读写,在创建的多个数据库实例之上,会**创建一个分布式数据库的实例**也需要指定可用区和Subnet还会为分布式数据库分配一个私网IP和域名。
对于数据库这种高可用性比较高的,需要进行跨机房高可用,因而两个可用区都要部署一套,但是只有一个是主,另外一个是备,云平台往往会提供数据库同步工具,将应用写入主的数据同步给备数据库集群。
接下来是**创建缓存集**群。云平台也会提供PaaS服务也需要每个可用区和Subnet创建一套缓存的数据在内存中由于读写性能要求高一般不要求跨可用区读写。
再往上层就是**部署咱们自己写的程序**了。基础服务层、组合服务层、Controller层以及Nginx层、API网关等等这些都是部署在虚拟机里面的。它们之间通过RPC相互调用需要到注册中心进行注册。
它们之间的网络通信是虚拟机和虚拟机之间的。如果是同一台物理机则那台物理机上的OVS就能转发过去如果是不同的物理机这台物理机的OVS和另一台物理机的OVS中间有一个VXLAN的隧道将请求转发过去。
再往外就是**负载均衡**了负载均衡也是云平台提供的PaaS服务也是属于某个VPC的部署在虚拟机里面的但是负载均衡有个外网的IP这个外网的IP地址就是在网关节点的外网网口上的。在网关节点上会有NAT规则将外网IP地址转换为VPC里面的私网IP地址通过这些私网IP地址访问到虚拟机上的负载均衡节点然后通过负载均衡节点转发到API网关的节点。
网关节点的外网网口是带公网IP地址的里面有一个虚拟网关转发模块还会有一个OVS将私网IP地址放到VXLAN隧道里面转发到虚拟机上从而实现外网和虚拟机网络之间的互通。
不同的可用区之间,通过核心交换机连在一起,核心交换机之外是边界路由器。
在华北、华东、华南同样也部署了一整套每个地区都创建了VPC这就需要有一种机制将VPC连接到一起。云平台一般会提供硬件的VPC互连的方式当然也可以使用软件互连的方式也就是使用VPN网关通过IPsec VPN将不同地区的不同VPC通过VPN连接起来。
对于不同地区和不同运营商的用户我们希望他能够就近访问到网站而且当一个点出了故障之后我们希望能够在不同的地区之间切换这就需要有智能DNS这个也是云平台提供的。
对于一些静态资源可以保持在对象存储里面通过CDN下发到边缘节点这样客户端就能尽快加载出来。
## 2.大声告诉全世界,可以到我这里买东西
当电商应用搭建完毕之后,接下来需要将如何访问到这个电商网站广播给全网。
刚才那张图画的是一个可用区的情况,对于多个可用区的情况,我们可以隐去计算节点的情况,将外网访问区域放大。
<img src="https://static001.geekbang.org/resource/image/e1/24/e132bc3ba500b1197139f30c02e20124.jpg" alt="" />
外网IP是放在虚拟网关的外网网口上的这个IP如何让全世界知道呢当然是通过BGP路由协议了。
每个可用区都有自己的汇聚交换机如果机器数目比较多可以直接用核心交换机每个Region也有自己的核心交换区域。
在核心交换外面是安全设备然后就是边界路由器。边界路由器会和多个运营商连接从而每个运营商都能够访问到这个网站。边界路由器可以通过BGP协议将自己数据中心里面的外网IP向外广播也就是告诉全世界如果要访问这些外网IP都来我这里。
每个运营商也有很多的路由器、很多的点于是就可以将如何到达这些IP地址的路由信息广播到全国乃至全世界。
## 3.打开手机来上网,域名解析得地址
这个时候不但你的这个网站的IP地址全世界都知道了你打的广告可能大家也都看到了于是有客户下载App来买东西了。
<img src="https://static001.geekbang.org/resource/image/85/fc/85c125c225faba29c0f374e18ea8c6fc.jpg" alt="" />
客户的手机开机以后在附近寻找基站eNodeB发送请求申请上网。基站将请求发给MMEMME对手机进行认证和鉴权还会请求HSS看有没有钱看看是在哪里上网。
当MME通过了手机的认证之后开始建立隧道建设的数据通路分两段路其实是两个隧道。一段是从eNodeB到SGW第二段是从SGW到PGW在PGW之外就是互联网。
PGW会为手机分配一个IP地址手机上网都是带着这个IP地址的。
当在手机上面打开一个App的时候首先要做的事情就是解析这个网站的域名。
在手机运营商所在的互联网区域里有一个本地的DNS手机会向这个DNS请求解析DNS。当这个DNS本地有缓存则直接返回如果没有缓存本地DNS才需要递归地从根DNS服务器查到.com的顶级域名服务器最终查到权威DNS服务器。
如果你使用云平台的时候配置了智能DNS和全局负载均衡在权威DNS服务中一般是通过配置CNAME的方式我们可以起一个别名例如 [vip.yourcomany.com](http://vip.yourcomany.com) 然后告诉本地DNS服务器让它请求GSLB解析这个域名GSLB就可以在解析这个域名的过程中通过自己的策略实现负载均衡。
GSLB通过查看请求它的本地DNS服务器所在的运营商和地址就知道用户所在的运营商和地址然后将距离用户位置比较近的Region里面三个负载均衡SLB的公网IP地址返回给本地DNS服务器。本地DNS解析器将结果缓存后返回给客户端。
对于手机App来说可以绕过刚才的传统DNS解析机制直接只要HTTPDNS服务通过直接调用HTTPDNS服务器得到这三个SLB的公网IP地址。
经过了如此复杂的过程咱们的万里长征还没迈出第一步刚刚得到IP地址包还没发呢话说手机App拿到了公网IP地址接下来应该做什么呢
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,130 @@
<audio id="audio" title="第38讲 | 知识串讲:用双十一的故事串起碎片的网络协议(中)" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6d/e8/6d61c5d70872758beb9c2b1fa3e434e8.mp3"></audio>
上一节我们讲到手机App经过了一个复杂的过程终于拿到了电商网站的SLB的IP地址是不是该下单了
别忙,俗话说的好,买东西要货比三家。大部分客户在购物之前要看很多商品图片,比来比去,最后好不容易才下决心,点了下单按钮。下单按钮一按,就要开始建立连接。建立连接这个过程也挺复杂的,最终还要经过层层封装,才构建出一个完整的网络包。今天我们就来看这个过程。
## 4.购物之前看图片静态资源CDN
客户想要在购物网站买一件东西的时候,一般是先去详情页看看图片,是不是想买的那一款。
<img src="https://static001.geekbang.org/resource/image/70/69/7023762edeaf4d481bc90331f60db769.jpg" alt="">
我们部署电商应用的时候一般会把静态资源保存在两个地方一个是接入层nginx后面的varnish缓存里面一般是静态页面对于比较大的、不经常更新的静态图片会保存在对象存储里面。这两个地方的静态资源都会配置CDN将资源下发到边缘节点。
配置了CDN之后权威DNS服务器上会为静态资源设置一个CNAME别名指向另外一个域名 cdn.com 返回给本地DNS服务器。
当本地DNS服务器拿到这个新的域名时需要继续解析这个新的域名。这个时候再访问的时候就不是原来的权威DNS服务器了而是 cdn.com 的权威DNS服务器。这是CDN自己的权威DNS服务器。
在这个服务器上还是会设置一个CNAME指向另外一个域名也即CDN网络的全局负载均衡器。
本地DNS服务器去请求CDN的全局负载均衡器解析域名全局负载均衡器会为用户选择一台合适的缓存服务器提供服务将IP返回给客户端客户端去访问这个边缘节点下载资源。缓存服务器响应用户请求将用户所需内容传送到用户终端。
如果这台缓存服务器上并没有用户想要的内容,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器,将内容拉到本地。
## 5.看上宝贝点下单,双方开始建连接
当你浏览了很多图片,发现实在喜欢某个商品,于是决定下单购买。
电商网站会对下单的情况提供RESTful的下单接口而对于下单这种需要保密的操作需要通过HTTPS协议进行请求。
在所有这些操作之前,首先要做的事情是**建立连接**。
<img src="https://static001.geekbang.org/resource/image/70/83/705e4cc5acc7b364bdf015f333d40783.jpg" alt="">
HTTPS协议是基于TCP协议的因而要先**建立TCP的连接**。在这个例子中TCP的连接是在手机上的App和负载均衡器SLB之间的。
尽管中间要经过很多的路由器和交换机但是TCP的连接是端到端的。TCP这一层和更上层的HTTPS无法看到中间的包的过程。尽管建立连接的时候所有的包都逃不过在这些路由器和交换机之间的转发转发的细节我们放到那个下单请求的发送过程中详细解读这里只看端到端的行为。
对于TCP连接来讲需要通过三次握手建立连接为了维护这个连接双方都需要在TCP层维护一个连接的状态机。
一开始客户端和服务端都处于CLOSED状态。服务端先是主动监听某个端口处于LISTEN状态。然后客户端主动发起连接SYN之后处于SYN-SENT状态。服务端收到发起的连接返回SYN并且ACK客户端的SYN之后处于SYN-RCVD状态。
客户端收到服务端发送的SYN和ACK之后发送ACK的ACK之后处于ESTABLISHED状态。这是因为它一发一收成功了。服务端收到ACK的ACK之后也会处于ESTABLISHED状态因为它的一发一收也成功了。
当TCP层的连接建立完毕之后接下来轮到**HTTPS层建立连接**了在HTTPS的交换过程中TCP层始终处于ESTABLISHED。
对于HTTPS客户端会发送Client Hello消息到服务器用明文传输TLS版本信息、加密套件候选列表、压缩算法候选列表等信息。另外还会有一个随机数在协商对称密钥的时候使用。
然后服务器会返回Server Hello消息告诉客户端服务器选择使用的协议版本、加密套件、压缩算法等。这也有一个随机数用于后续的密钥协商。
然后服务器会给你一个服务器端的证书然后说“Server Hello Done我这里就这些信息了。”
客户端当然不相信这个证书于是从自己信任的CA仓库中拿CA的证书里面的公钥去解密电商网站的证书。如果能够成功则说明电商网站是可信的。这个过程中你可能会不断往上追溯CA、CA的CA、CA的CA的CA反正直到一个授信的CA就可以了。
证书验证完毕之后觉得这个服务端是可信的于是客户端计算产生随机数字Pre-master发送Client Key Exchange用证书中的公钥加密再发送给服务器服务器可以通过私钥解密出来。
接下来无论是客户端还是服务器都有了三个随机数分别是自己的、对端的以及刚生成的Pre-Master随机数。通过这三个随机数可以在客户端和服务器产生相同的对称密钥。
有了对称密钥客户端就可以说“Change Cipher Spec咱们以后都采用协商的通信密钥和加密算法进行加密通信了。”
然后客户端发送一个Encrypted Handshake Message将已经商定好的参数等采用协商密钥进行加密发送给服务器用于数据与握手验证。
同样服务器也可以发送Change Cipher Spec“没问题咱们以后都采用协商的通信密钥和加密算法进行加密通信了”并且也发送Encrypted Handshake Message的消息试试。
当双方握手结束之后,就可以通过对称密钥进行加密传输了。
真正的下单请求封装成网络包的发送过程,我们先放一放,我们来接着讲这个网络包的故事。
## 6.发送下单请求网络包,西行需要出网关
当客户端和服务端之间建立了连接后,接下来就要发送下单请求的网络包了。
在用户层发送的是HTTP的网络包因为服务端提供的是RESTful API因而HTTP层发送的就是一个请求。
```
POST /purchaseOrder HTTP/1.1
Host: www.geektime.com
Content-Type: application/json; charset=utf-8
Content-Length: nnn
{
&quot;order&quot;: {
&quot;date&quot;: &quot;2018-07-01&quot;,
&quot;className&quot;: &quot;趣谈网络协议&quot;,
&quot;Author&quot;: &quot;刘超&quot;,
&quot;price&quot;: &quot;68&quot;
}
}
```
HTTP的报文大概分为三大部分。第一部分是**请求行**,第二部分是**请求的首部**,第三部分才是**请求的正文实体**。
在请求行中URL就是 www.geektime.com/purchaseOrder 版本为HTTP 1.1。
请求的类型叫作POST它需要主动告诉服务端一些信息而非获取。需要告诉服务端什么呢一般会放在正文里面。正文可以有各种各样的格式常见的格式是JSON。
请求行下面就是我们的首部字段。首部是key value通过冒号分隔。
Content-Type是指正文的格式。例如我们进行POST的请求如果正文是JSON那么我们就应该将这个值设置为JSON。
接下来是正文这里是一个JSON字符串里面通过文本的形式描述了要买一个课程作者是谁多少钱。
这样HTTP请求的报文格式就拼凑好了。接下来浏览器或者移动App会把它交给下一层传输层。
怎么交给传输层呢也是用Socket进行程序设计。如果用的是浏览器这些程序不需要你自己写有人已经帮你写好了如果在移动APP里面一般会用一个HTTP的客户端工具来发送并且帮你封装好。
HTTP协议是基于TCP协议的所以它使用面向连接的方式发送请求通过Stream二进制流的方式传给对方。当然到了TCP层它会把二进制流变成一个个报文段发送给服务器。
在TCP头里面会有源端口号和目标端口号目标端口号一般是服务端监听的端口号源端口号在手机端往往是随机分配一个端口号。这个端口号在客户端和服务端用于区分请求和返回发给那个应用。
在IP头里面都需要加上自己的地址即源地址和它想要去的地方即目标地址。当一个手机上线的时候PGW会给这个手机分配一个IP地址这就是源地址而目标地址则是云平台的负载均衡器的外网IP地址。
在IP层客户端需要查看目标地址和自己是否是在同一个局域网计算是否是同一个网段往往需要通过CIDR子网掩码来计算。
对于这个下单场景目标IP和源IP不会在同一个网段因而需要发送到默认的网关。一般通过DHCP分配IP地址的时候同时配置默认网关的IP地址。
但是客户端不会直接使用默认网关的IP地址而是发送ARP协议来获取网关的MAC地址然后将网关MAC作为目标MAC自己的MAC作为源MAC放入MAC头发送出去。
一个完整的网络包的格式是这样的。
<img src="https://static001.geekbang.org/resource/image/99/49/99c282efaca15deb79c7821c9c577349.jpg" alt="">
真不容易啊,本来以为上篇就发送下单包了,结果到中篇这个包还没发送出去,只是封装了一个如此长的网络包。别着急,你可以自己先预想一下,接下来该做什么了?
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,301 @@
<audio id="audio" title="第39讲 | 知识串讲:用双十一的故事串起碎片的网络协议(下)" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/5f/94/5fe9b9e65108cda410e851c60897ea94.mp3"></audio>
上一节,我们封装了一个长长的网络包,“大炮”准备完毕,开始发送。
发送的时候可以说是重重关隘从手机到移动网络、互联网还要经过多个运营商才能到达数据中心到了数据中心就进入第二个复杂的过程从网关到VXLAN隧道到负载均衡到Controller层、组合服务层、基础服务层最终才下单入库。今天我们就来看这最后一段过程。
## 7.一座座城池一道道关,流控拥塞与重传
网络包已经组合完毕接下来我们来看如何经过一道道城关到达目标公网IP。
对于手机来讲默认的网关在PGW上。在移动网络里面从手机到SGW到PGW是有一条隧道的。在这条隧道里面会将上面的这个包作为隧道的乘客协议放在里面外面SGW和PGW在核心网机房的IP地址。网络包直到PGWPGW是隧道的另一端才将里面的包解出来转发到外部网络。
所以,从手机发送出来的时候,网络包的结构为:
<li>
源MAC手机也即UE的MAC
</li>
<li>
目标MAC网关PGW上面的隧道端点的MAC
</li>
<li>
源IPUE的IP地址
</li>
<li>
目标IPSLB的公网IP地址。
</li>
进入隧道之后,要封装外层的网络地址,因而网络包的格式为:
<li>
外层源MACE-NodeB的MAC
</li>
<li>
外层目标MACSGW的MAC
</li>
<li>
外层源IPE-NodeB的IP
</li>
<li>
外层目标IPSGW的IP
</li>
<li>
内层源MAC手机也即UE的MAC
</li>
<li>
内层目标MAC网关PGW上面的隧道端点的MAC
</li>
<li>
内层源IPUE的IP地址
</li>
<li>
内层目标IPSLB的公网IP地址。
</li>
当隧道在SGW的时候切换了一个隧道会从SGW到PGW的隧道因而网络包的格式为
<li>
外层源MACSGW的MAC
</li>
<li>
外层目标MACPGW的MAC
</li>
<li>
外层源IPSGW的IP
</li>
<li>
外层目标IPPGW的IP
</li>
<li>
内层源MAC手机也即UE的MAC
</li>
<li>
内层目标MAC网关PGW上面的隧道端点的MAC
</li>
<li>
内层源IPUE的IP地址
</li>
<li>
内层目标IPSLB的公网IP地址。
</li>
在PGW的隧道端点将包解出来转发出去的时候一般在PGW出外部网络的路由器上会部署NAT服务将手机的IP地址转换为公网IP地址当请求返回的时候再NAT回来。
因而在PGW之后相当于做了一次[欧洲十国游型](https://time.geekbang.org/column/article/8590)的转发,网络包的格式为:
<li>
源MACPGW出口的MAC
</li>
<li>
目标MACNAT网关的MAC
</li>
<li>
源IPUE的IP地址
</li>
<li>
目标IPSLB的公网IP地址。
</li>
在NAT网关相当于做了一次[玄奘西游型](https://time.geekbang.org/column/article/8590)的转发,网络包的格式变成:
<li>
源MACNAT网关的MAC
</li>
<li>
目标MACA2路由器的MAC
</li>
<li>
源IPUE的公网IP地址
</li>
<li>
目标IPSLB的公网IP地址。
</li>
<img src="https://static001.geekbang.org/resource/image/58/a0/582c9515a877a6fde85a6180186ba8a0.jpg" alt="">
出了NAT网关就从核心网到达了互联网。在网络世界每一个运营商的网络成为自治系统AS。每个自治系统都有边界路由器通过它和外面的世界建立联系。
对于云平台来讲它可以被称为Multihomed AS有多个连接连到其他的AS但是大多拒绝帮其他的AS传输包。例如一些大公司的网络。对于运营商来说它可以被称为Transit AS有多个连接连到其他的AS并且可以帮助其他的AS传输包比如主干网。
如何从出口的运营商到达云平台的边界路由器在路由器之间需要通过BGP协议实现BGP又分为两类eBGP和iBGP。自治系统之间、边界路由器之间使用eBGP广播路由。内部网络也需要访问其他的自治系统。
边界路由器如何将BGP学习到的路由导入到内部网络呢通过运行iBGP使内部的路由器能够找到到达外网目的地最好的边界路由器。
网站的SLB的公网IP地址早已经通过云平台的边界路由器让全网都知道了。于是这个下单的网络包选择的下一跳是A2也即将A2的MAC地址放在目标MAC地址中。
到达A2之后从路由表中找到下一跳是路由器C1于是将目标MAC换成C1的MAC地址。到达C1之后找到下一跳是C2将目标MAC地址设置为C2的MAC。到达C2后找到下一跳是云平台的边界路由器于是将目标MAC设置为边界路由器的MAC地址。
你会发现这一路都是只换MAC不换目标IP地址。这就是所谓下一跳的概念。
在云平台的边界路由器会将下单的包转发进来经过核心交换汇聚交换到达外网网关节点上的SLB的公网IP地址。
我们可以看到手机到SLB的公网IP是一个端到端的连接连接的过程发送了很多包。所有这些包无论是TCP三次握手还是HTTPS的密钥交换都是要走如此复杂的过程到达SLB的当然每个包走的路径不一定一致。
网络包走在这个复杂的道路上很可能一不小心就丢了怎么办这就需要借助TCP的机制重新发送。
既然TCP要对包进行重传就需要维护Sequence Number看哪些包到了哪些没到哪些需要重传传输的速度应该控制到多少这就是**TCP的滑动窗口协议**。
<img src="https://static001.geekbang.org/resource/image/8a/3d/8af90ec349e69f2bf13a565e4179903d.jpg" alt="">
整个TCP的发送一开始会协商一个Sequence Number从这个Sequence Number开始每个包都有编号。滑动窗口将接收方的网络包分成四个部分
<li>
已经接收已经ACK已经交给应用层的包
</li>
<li>
已经接收已经ACK未发送给应用层
</li>
<li>
已经接收尚未发送ACK
</li>
<li>
未接收,尚有空闲的缓存区域。
</li>
对于TCP层来讲每一个包都有ACK。ACK需要从SLB回复到手机端将上面的那个过程反向来一遍当然路径不一定一致可见ACK也不是那么轻松的事情。
如果发送方超过一定的时间没有收到ACK就会重新发送。只有TCP层ACK过的包才会发给应用层并且只会发送一份对于下单的场景应用层是HTTP层。
你可能会问了TCP老是重复发送会不会导致一个单下了两遍是否要求服务端实现幂等从TCP的机制来看是不会的。只有收不到ACK的包才会重复发发到接收端在窗口里面只保存一份所以在同一个TCP连接中不用担心重传导致二次下单。
但是TCP连接会因为某种原因断了例如手机信号不好这个时候手机把所有的动作重新做一遍建立一个新的TCP连接在HTTP层调用两次RESTful API。这个时候可能会导致两遍下单的情况因而RESTful API需要实现幂等。
当ACK过的包发给应用层之后TCP层的缓存就空了出来这会导致上面图中的大三角也即接收方能够容纳的总缓存整体顺时针滑动。小的三角形也即接收方告知发送方的窗口总大小也即还没有完全确认收到的缓存大小如果把这些填满了就不能再发了因为没确认收到所以一个都不能扔。
## 8.从数据中心进网关公网NAT成私网
包从手机端经历千难万险终于到了SLB的公网IP所在的公网网口。由于匹配上了MAC地址和IP地址因而将网络包收了进来。
<img src="https://static001.geekbang.org/resource/image/e0/95/e070a0bcbb249c36c80ee1300003f395.jpg" alt="">
在虚拟网关节点的外网网口上会有一个NAT规则将公网IP地址转换为VPC里面的私网IP地址这个私网IP地址就是SLB的HAProxy所在的虚拟机的私网IP地址。
当然为了承载比较大的吞吐量虚拟网关节点会有多个物理网络会将流量分发到不同的虚拟网关节点。同样HAProxy也会是一个大的集群虚拟网关会选择某个负载均衡节点将某个请求分发给它负载均衡之后是Controller层也是部署在虚拟机里面的。
当网络包里面的目标IP变成私有IP地址之后虚拟路由会查找路由规则将网络包从下方的私网网口发出来。这个时候包的格式为
<li>
源MAC网关MAC
</li>
<li>
目标MACHAProxy虚拟机的MAC
</li>
<li>
源IPUE的公网IP
</li>
<li>
目标IPHAProxy虚拟机的私网IP。
</li>
## 9.进入隧道打标签RPC远程调用下单
在虚拟路由节点上也会有OVS将网络包封装在VXLAN隧道里面VXLAN ID就是给你的租户创建VPC的时候分配的。包的格式为
<li>
外层源MAC网关物理机MAC
</li>
<li>
外层目标MAC物理机A的MAC
</li>
<li>
外层源IP网关物理机IP
</li>
<li>
外层目标IP物理机A的IP
</li>
<li>
内层源MAC网关MAC
</li>
<li>
内层目标MACHAProxy虚拟机的MAC
</li>
<li>
内层源IPUE的公网IP
</li>
<li>
内层目标IPHAProxy虚拟机的私网IP。
</li>
在物理机A上OVS会将包从VXLAN隧道里面解出来发给HAProxy所在的虚拟机。HAProxy所在的虚拟机发现MAC地址匹配目标IP地址匹配就根据TCP端口将包发给HAProxy进程因为HAProxy是在监听这个TCP端口的。因而HAProxy就是这个TCP连接的服务端客户端是手机。对于TCP的连接状态、滑动窗口等都是在HAProxy上维护的。
在这里HAProxy是一个四层负载均衡也即它只解析到TCP层里面的HTTP协议它不关心就将请求转发给后端的多个Controller层的一个。
HAProxy发出去的网络包就认为HAProxy是客户端了看不到手机端了。网络包格式如下
<li>
源MACHAProxy所在虚拟机的MAC
</li>
<li>
目标MACController层所在虚拟机的MAC
</li>
<li>
源IPHAProxy所在虚拟机的私网IP
</li>
<li>
目标IPController层所在虚拟机的私网IP。
</li>
当然这个包发出去之后还是会被物理机上的OVS放入VXLAN隧道里面网络包格式为
<li>
外层源MAC物理机A的MAC
</li>
<li>
外层目标MAC物理机B的MAC
</li>
<li>
外层源IP物理机A的IP
</li>
<li>
外层目标IP物理机B的IP
</li>
<li>
内层源MACHAProxy所在虚拟机的MAC
</li>
<li>
内层目标MACController层所在虚拟机的MAC
</li>
<li>
内层源IPHAProxy所在虚拟机的私网IP
</li>
<li>
内层目标IPController层所在虚拟机的私网IP。
</li>
在物理机B上OVS会将包从VXLAN隧道里面解出来发给Controller层所在的虚拟机。Controller层所在的虚拟机发现MAC地址匹配目标IP地址匹配就根据TCP端口将包发给Controller层的进程因为它在监听这个TCP端口。
在HAProxy和Controller层之间维护一个TCP的连接。
Controller层收到包之后它是关心HTTP里面是什么的于是解开HTTP的包发现是一个POST请求内容是下单购买一个课程。
## 10.下单扣减库存优惠券,数据入库返回成功
下单是一个复杂的过程因而往往在组合服务层会有一个专门管理下单的服务Controller层会通过RPC调用这个组合服务层。
假设我们使用的是Dubbo则Controller层需要读取注册中心将下单服务的进程列表拿出来选出一个来调用。
Dubbo中默认的RPC协议是Hessian2。Hessian2将下单的远程调用序列化为二进制进行传输。
Netty是一个非阻塞的基于事件的网络传输框架。Controller层和下单服务之间使用了Netty的网络传输框架。有了Netty就不用自己编写复杂的异步Socket程序了。Netty使用的方式就是咱们讲[Socket编程](https://time.geekbang.org/column/article/9293)的时候一个项目组支撑多个项目IO多路复用从派人盯着到有事通知这种方式。
Netty还是工作在Socket这一层的发送的网络包还是基于TCP的。在TCP的下层还是需要封装上IP头和MAC头。如果跨物理机通信还是需要封装的外层的VXLAN隧道里面。当然底层的这些封装Netty都不感知它只要做好它的异步通信即可。
在Netty的服务端也即下单服务中收到请求后先用Hessian2的格式进行解压缩。然后将请求分发到线程中进行处理在线程中会调用下单的业务逻辑。
下单的业务逻辑比较复杂往往要调用基础服务层里面的库存服务、优惠券服务等将多个服务调用完毕才算下单成功。下单服务调用库存服务和优惠券服务也是通过Dubbo的框架通过注册中心拿到库存服务和优惠券服务的列表然后选一个调用。
调用的时候统一使用Hessian2进行序列化使用Netty进行传输底层如果跨物理机仍然需要通过VXLAN的封装和解封装。
咱们以库存为例子的时候讲述过幂等的接口实现的问题。因为如果扣减库存仅仅是谁调用谁减一。这样存在的问题是如果扣减库存因为一次调用失败而多次调用这里指的不是TCP多次重试而是应用层调用的多次重试就会存在库存扣减多次的情况。
这里常用的方法是使用乐观锁Compare and Set简称CAS。CAS要考虑三个方面当前的库存数、预期原来的库存数和版本以及新的库存数。在操作之前查询出原来的库存数和版本真正扣减库存的时候判断如果当前库存的值与预期原值和版本相匹配则将库存值更新为新值否则不做任何操作。
这是一种基于状态而非基于动作的设计符合RESTful的架构设计原则。这样的设计有利于高并发场景。当多个线程尝试使用CAS同时更新同一个变量时只有其中一个线程能更新变量的值而其它线程都失败失败的线程并不会被挂起而是被告知这次竞争中失败并可以再次尝试。
最终,当下单更新到分布式数据库中之后,整个下单过程才算真正告一段落。
好了,经过了十个过程,下单终于成功了,你是否对这个过程了如指掌了呢?如果发现对哪些细节比较模糊,可以回去看一下相应的章节,相信会有更加深入的理解。
到此,我带着你用下单过程把网络协议的知识都复习了一遍。授人以鱼不如授人以渔。下一节,我将会带你来搭建一个网络实验环境,配合实验来说明理论。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,214 @@
<audio id="audio" title="第40讲 | 搭建一个网络实验环境:授人以鱼不如授人以渔" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/68/6a/680a1b85738d87a7208e9e386d52286a.mp3"></audio>
因为这门课是基础课程,而且配合音频的形式发布,所以我多以理论为主来进行讲解。在专栏更新的过程中,不断有同学让我推荐一些网络方面的书籍,还有同学说能不能配合一些实验来说明理论。
的确,网络是一门实验性很强的学科,就像我在开篇词里面说的一样:**一看觉得懂,一问就打鼓,一用就糊涂。** 在写专栏的过程中,我自己也深深体会到了。这个时候,我常常会拿一个现实的环境,上手操作一下,抓个包看看,这样心里就会有定论。
## 《TCP/IP详解》实验环境搭建
对于网络方面的书籍我当然首推Rechard Stevens的《[TCP/IP illustrated](https://book.douban.com/subject/1741925/)》《TCP/IP详解》。这本书把理论讲得深入浅出还配有大量的上手实践和抓包看到这些抓包原来不理解的很多理论一下子就能懂了。
这本书里有个拓扑图,书上的很多实验都是基于这个图的,但是这个拓扑图还是挺复杂的。我这里先不说,一会儿详细讲。
Rechard Stevens因为工作中有这么一个环境很方便做实验最终才写出了这样一本书而我们一般人学习网络没有这个环境应该怎么办呢
时代不同了咱们现在有更加强大的工具了。例如这里这么多的机器我们可以用Docker来实现多个网络可以用Open vSwitch来实现。你甚至不需要一台物理机只要一台1核2G的虚拟机就能将这个环境搭建起来。
搭建这个环境的时候,需要一些脚本。我把脚本都放在了[Github](https://github.com/popsuper1982/tcpipillustrated)里面,你可以自己取用。
### 1.创建一个Ubuntu虚拟机
在你的笔记本电脑上用VirtualBox创建就行。1核2G随便一台电脑都能搭建起来。
首先我们先下载一个Ubuntu的镜像。我是从[Ubuntu官方网站](https://www.ubuntu.com/download/alternative-downloads)下载的。
<img src="https://static001.geekbang.org/resource/image/39/87/390a5bfb153fc95bdac0939194bc7387.jpg" alt="" />
然后在VirtualBox里面安装Ubuntu。安装过程网上一大堆教程你可以自己去看我这里就不详细说了。
这里我需要说明的是网络的配置。
对于这个虚拟机我们创建两个网卡一个是Host-only只有你的笔记本电脑上能够登录进去。这个网卡上的IP地址也只有在你的笔记本电脑上管用。这个网卡的配置比较稳定用于在SSH上做操作。这样你的笔记本电脑就可以搬来搬去在公司里安装一半回家接着安装另一半都没问题。
<img src="https://static001.geekbang.org/resource/image/ab/ce/ab1974fc25fe902ff8eeda598eb9c3ce.jpg" alt="" />
这里有一个虚拟的网桥,这个网络可以在管理&gt;主机网络管理里面进行配置。
<img src="https://static001.geekbang.org/resource/image/49/13/49eea1f000c9a538271d46c25c0a7b13.jpg" alt="" />
在这里可以虚拟网桥的的IP地址同时启用一个DHCP服务器为新创建的虚拟机配置IP地址。
另一个网卡配置为NAT网络用于访问互联网。配置了NAT网络之后只要你的笔记本电脑能上网虚拟机就能上网。由于咱们在Ubuntu里面要安装一些东西因而需要联网。
你可能会问了,这个配置复杂吗?一点儿都不复杂。咱们讲[虚拟机网络](https://time.geekbang.org/column/article/10742)的时候,讲过这个。
<img src="https://static001.geekbang.org/resource/image/2d/c4/2dd447992fbf4901f92a3dfdf8086bc4.jpg" alt="" />
安装完了Ubuntu之后需要对Ubuntu里面的网卡进行配置。对于Ubuntu来讲网卡的配置在/etc/network/interfaces这个文件里面。在我的环境里NAT的网卡名称为enp0s3Host-only的网卡的名称为enp0s8都可以配置为自动配置。
```
auto lo
iface lo inet loopback
auto enp0s3
iface enp0s3 inet dhcp
auto enp0s8
iface enp0s8 inet dhcp
```
这样重启之后IP就配置好了。
### 2.安装Docker和Open vSwitch
接下来在Ubuntu里面以root用户安装Docker和Open vSwitch。
你可以按照Docker的[官方安装](https://docs.docker.com/install/linux/docker-ce/ubuntu/)[文档](https://docs.docker.com/install/linux/docker-ce/ubuntu/)来做。我这里也贴一下我的安装过程。
```
apt-get remove docker docker-engine docker.io
apt-get -y update
apt-get -y install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg &gt; gpg
apt-key add gpg
apt-key fingerprint 0EBFCD88
add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot;
apt-get -y update
apt-cache madison docker-ce
apt-get -y install docker-ce=18.06.0~ce~3-0~ubuntu
```
之后还需要安装Open vSwitch和Bridge。
```
apt-get -y install openvswitch-common openvswitch-dbg openvswitch-switch python-openvswitch openvswitch-ipsec openvswitch-pki openvswitch-vtep
apt-get -y install bridge-utils
apt-get -y install arping
```
### 3.准备一个Docker的镜像
<img src="https://static001.geekbang.org/resource/image/4c/1e/4c0e633ddc53f3feb98946518c0cf21e.jpg" alt="" />
每个节点都是一个Docker对应要有一个Docker镜像。这个镜像我已经打好了你可以直接使用。
```
docker pull hub.c.163.com/liuchao110119163/ubuntu:tcpip
```
当然你也可以自己打这个镜像。Dockerfile就像这样
```
FROM hub.c.163.com/public/ubuntu:14.04
RUN apt-get -y update &amp;&amp; apt-get install -y iproute2 iputils-arping net-tools tcpdump curl telnet iputils-tracepath traceroute
RUN mv /usr/sbin/tcpdump /usr/bin/tcpdump
ENTRYPOINT /usr/sbin/sshd -D
```
### 4.启动整个环境
启动这个环境还是比较复杂的我写成了一个脚本。在Git仓库里面有一个文件 [setupenv.sh](http://setupenv.sh) 可以执行这个脚本里面有两个参数一个参数是NAT网卡的名字一个是镜像的名称。
```
git clone https://github.com/popsuper1982/tcpipillustrated.git
cd tcpipillustrated
docker pull hub.c.163.com/liuchao110119163/ubuntu:tcpip
chmod +x setupenv.sh
./setupenv.sh enp0s3 hub.c.163.com/liuchao110119163/ubuntu:tcpip
```
这样整个环境就搭建起来了所有的容器之间都可以ping通而且都可以上网。
不过,我写的这个脚本对一些人来说可能会有点儿复杂,我这里也解释一下。
首先**每一个节点,都启动一个容器**。使用privileged=true方式网络先不配置net none。有两个二层网络使用ovs-vsctl的add-br命令创建两个网桥。
pipework是一个很好的命令行工具可以将容器连接到两个二层网络上。
但是我们上面那个图里有两个比较特殊的网络一个是从slip到bsdi的P2P网络需要创建一个peer的两个网卡然后两个Docker的网络namespace里面各塞进去一个。
有关操作Docker的网络namespace的方式咱们在[容器网络](https://time.geekbang.org/column/article/11465)那一节讲过ip netns命令。
这里需要注意的是P2P网络和下面的二层网络不是同一个网络。P2P网络的CIDR是140.252.13.64/27而下面的二层网络的CIDR是140.252.13.32/27。如果按照/24看起来是一个网络但是/27就不是了。至于[CIDR的计算方法](https://time.geekbang.org/column/article/7772),你可以回去复习一下。
另外需要**配置从sun到netb的点对点网络**方法还是通过peer网卡和ip netns的方式。
这里有个特殊的地方对于netb来讲不是一个普通的路由器因为netb两边是同一个二层网络所以需要配置arp proxy。
为了所有的节点之间互通要配置一下路由策略这里需要通过ip route命令。
<li>
对于slip来讲bsdi左面13.66这个网口是网关。
</li>
<li>
对于bsdi和svr4来讲如果去外网sun下面的网口13.33是网关。
</li>
<li>
对于sun来讲上面的网口1.29属于上面的二层网络了它如果去外网gateway下面的网口1.4就是外网网关。
</li>
<li>
对于aixsolarisgemini来讲如果去外网网关也是gateway下面的网口1.4。如果去下面的二层网口网关是sun上面的网口1.29。
</li>
配置完了这些,图中的所有的节点都能相互访问了,最后还要解决**如何访问外网**的问题。
我们还是需要创建一个peer网卡对。一个放在gateway里面一个放在gateway外面。外面的网卡去外网的网关。
在虚拟机上面还需要配置一个iptables的地址伪装规则MASQUERADE其实就是一个SNAT。因为容器里面要访问外网因为外网是不认的所以源地址不能用容器的地址需要SNAT成为虚拟机的地址出去回来的时候再NAT回来。
配置这个环境还是挺复杂的,要用到咱们学到的很多知识。如果没有学习前面那些知识,直接就做这个实验,你肯定会很晕。但是只学理论也不行,要把理论都学过一遍,再做一遍实验,这才是一个不断迭代、更新知识库的过程。
有了这个环境《TCP/IP详解》里面的所有实验都能做了而且我打的这个Docker镜像里面tcpdump等网络工具都安装了你可以“为所欲为”了。
## Open vSwitch的实验
做了TCP/IP详解的实验之后网络程序设计这部分你就有了坚实的基础。但是涉及到数据中心内部的一些网络技术什么VLAN、VXLAN、STP等偏运维方向的学习还是会比较困难。好在我们有Open vSwitch也可以做大量的实验。
Open vSwitch门槛比较高里面的概念也非常多可谓千头万绪。不过通过我这么多年研究的经验可以告诉你这里面有一个很好的线索那就是Open vSwitch会将自己对于网络的配置保存在一个本地库里面。这个库的表结构之间的关系就像这样
<img src="https://static001.geekbang.org/resource/image/43/d9/439a996b7f8de9477cb00876f13a28d9.jpg" alt="" />
这个库其实是一个JSON如果把这个JSON打印出来能够看到更加详细的特性。按照这些特性一一实验可以逐渐把Open vSwitch各个特性都掌握。
<img src="https://static001.geekbang.org/resource/image/f0/a0/f00657f044e5afb07332f7236054b8a0.jpg" alt="" />
这里面最重要的概念就是**网桥**。一个网桥会有流表控制网络包的处理过程会有控制器下发流表一个网桥上会有多个端口可以对端口进行流控一个端口可以设置VLAN一个端口可以包含多个网卡可以做绑定网卡可以设置成为GRE和VXLAN。
我写过一个Open vSwitch的实验教程也放在了Github里面。这里面有这么几个比较重要的实验你可以看一看。
<li>
实验一查看Open vSwitch的架构。我们在讲Open vSwitch的时候提过Open vSwitch的架构在这个实验中我们可以查看Open vSwitch的各个模块以及启动的参数。
</li>
<li>
实验五配置使用OpenFlow Controller体验一把作为小区物业在监控室里面管控整个小区道路的样子。
</li>
<li>
实验八测试Port的VLAN功能。看一下VLAN隔离究竟是什么样的。
</li>
<li>
实验十QoS功能。体验一把如果使用HTB进行网卡限流。
</li>
<li>
实验十一GRE和VXLAN隧道功能看虚拟网络如何进行租户隔离。
</li>
<li>
实验十五对Flow Table的操作体验流表对网络包随心所欲的处理。
</li>
好了,关于整个环境的搭建我就讲到这里了。
其实到这里,对于网络世界的探索才刚刚开始,只有经过你自己动手和思考产生的内容,才是真正属于你的知识!打开你的电脑,上手去实验吧!
欢迎你留言和我讨论。趣谈网络协议,我们下期见!