gitbook/趣谈网络协议/docs/13099.md
2022-09-03 22:05:03 +08:00

246 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 第39讲 | 知识串讲:用双十一的故事串起碎片的网络协议(下)
上一节,我们封装了一个长长的网络包,“大炮”准备完毕,开始发送。
发送的时候可以说是重重关隘从手机到移动网络、互联网还要经过多个运营商才能到达数据中心到了数据中心就进入第二个复杂的过程从网关到VXLAN隧道到负载均衡到Controller层、组合服务层、基础服务层最终才下单入库。今天我们就来看这最后一段过程。
## 7.一座座城池一道道关,流控拥塞与重传
网络包已经组合完毕接下来我们来看如何经过一道道城关到达目标公网IP。
对于手机来讲默认的网关在PGW上。在移动网络里面从手机到SGW到PGW是有一条隧道的。在这条隧道里面会将上面的这个包作为隧道的乘客协议放在里面外面SGW和PGW在核心网机房的IP地址。网络包直到PGWPGW是隧道的另一端才将里面的包解出来转发到外部网络。
所以,从手机发送出来的时候,网络包的结构为:
* 源MAC手机也即UE的MAC
* 目标MAC网关PGW上面的隧道端点的MAC
* 源IPUE的IP地址
* 目标IPSLB的公网IP地址。
进入隧道之后,要封装外层的网络地址,因而网络包的格式为:
* 外层源MACE-NodeB的MAC
* 外层目标MACSGW的MAC
* 外层源IPE-NodeB的IP
* 外层目标IPSGW的IP
* 内层源MAC手机也即UE的MAC
* 内层目标MAC网关PGW上面的隧道端点的MAC
* 内层源IPUE的IP地址
* 内层目标IPSLB的公网IP地址。
当隧道在SGW的时候切换了一个隧道会从SGW到PGW的隧道因而网络包的格式为
* 外层源MACSGW的MAC
* 外层目标MACPGW的MAC
* 外层源IPSGW的IP
* 外层目标IPPGW的IP
* 内层源MAC手机也即UE的MAC
* 内层目标MAC网关PGW上面的隧道端点的MAC
* 内层源IPUE的IP地址
* 内层目标IPSLB的公网IP地址。
在PGW的隧道端点将包解出来转发出去的时候一般在PGW出外部网络的路由器上会部署NAT服务将手机的IP地址转换为公网IP地址当请求返回的时候再NAT回来。
因而在PGW之后相当于做了一次[欧洲十国游型](https://time.geekbang.org/column/article/8590)的转发,网络包的格式为:
* 源MACPGW出口的MAC
* 目标MACNAT网关的MAC
* 源IPUE的IP地址
* 目标IPSLB的公网IP地址。
在NAT网关相当于做了一次[玄奘西游型](https://time.geekbang.org/column/article/8590)的转发,网络包的格式变成:
* 源MACNAT网关的MAC
* 目标MACA2路由器的MAC
* 源IPUE的公网IP地址
* 目标IPSLB的公网IP地址。
![](https://static001.geekbang.org/resource/image/58/a0/582c9515a877a6fde85a6180186ba8a0.jpg)
出了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的滑动窗口协议**。
![](https://static001.geekbang.org/resource/image/8a/3d/8af90ec349e69f2bf13a565e4179903d.jpg)
整个TCP的发送一开始会协商一个Sequence Number从这个Sequence Number开始每个包都有编号。滑动窗口将接收方的网络包分成四个部分
* 已经接收已经ACK已经交给应用层的包
* 已经接收已经ACK未发送给应用层
* 已经接收尚未发送ACK
* 未接收,尚有空闲的缓存区域。
对于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地址因而将网络包收了进来。
![](https://static001.geekbang.org/resource/image/e0/95/e070a0bcbb249c36c80ee1300003f395.jpg)
在虚拟网关节点的外网网口上会有一个NAT规则将公网IP地址转换为VPC里面的私网IP地址这个私网IP地址就是SLB的HAProxy所在的虚拟机的私网IP地址。
当然为了承载比较大的吞吐量虚拟网关节点会有多个物理网络会将流量分发到不同的虚拟网关节点。同样HAProxy也会是一个大的集群虚拟网关会选择某个负载均衡节点将某个请求分发给它负载均衡之后是Controller层也是部署在虚拟机里面的。
当网络包里面的目标IP变成私有IP地址之后虚拟路由会查找路由规则将网络包从下方的私网网口发出来。这个时候包的格式为
* 源MAC网关MAC
* 目标MACHAProxy虚拟机的MAC
* 源IPUE的公网IP
* 目标IPHAProxy虚拟机的私网IP。
## 9.进入隧道打标签RPC远程调用下单
在虚拟路由节点上也会有OVS将网络包封装在VXLAN隧道里面VXLAN ID就是给你的租户创建VPC的时候分配的。包的格式为
* 外层源MAC网关物理机MAC
* 外层目标MAC物理机A的MAC
* 外层源IP网关物理机IP
* 外层目标IP物理机A的IP
* 内层源MAC网关MAC
* 内层目标MACHAProxy虚拟机的MAC
* 内层源IPUE的公网IP
* 内层目标IPHAProxy虚拟机的私网IP。
在物理机A上OVS会将包从VXLAN隧道里面解出来发给HAProxy所在的虚拟机。HAProxy所在的虚拟机发现MAC地址匹配目标IP地址匹配就根据TCP端口将包发给HAProxy进程因为HAProxy是在监听这个TCP端口的。因而HAProxy就是这个TCP连接的服务端客户端是手机。对于TCP的连接状态、滑动窗口等都是在HAProxy上维护的。
在这里HAProxy是一个四层负载均衡也即它只解析到TCP层里面的HTTP协议它不关心就将请求转发给后端的多个Controller层的一个。
HAProxy发出去的网络包就认为HAProxy是客户端了看不到手机端了。网络包格式如下
* 源MACHAProxy所在虚拟机的MAC
* 目标MACController层所在虚拟机的MAC
* 源IPHAProxy所在虚拟机的私网IP
* 目标IPController层所在虚拟机的私网IP。
当然这个包发出去之后还是会被物理机上的OVS放入VXLAN隧道里面网络包格式为
* 外层源MAC物理机A的MAC
* 外层目标MAC物理机B的MAC
* 外层源IP物理机A的IP
* 外层目标IP物理机B的IP
* 内层源MACHAProxy所在虚拟机的MAC
* 内层目标MACController层所在虚拟机的MAC
* 内层源IPHAProxy所在虚拟机的私网IP
* 内层目标IPController层所在虚拟机的私网IP。
在物理机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同时更新同一个变量时只有其中一个线程能更新变量的值而其它线程都失败失败的线程并不会被挂起而是被告知这次竞争中失败并可以再次尝试。
最终,当下单更新到分布式数据库中之后,整个下单过程才算真正告一段落。
好了,经过了十个过程,下单终于成功了,你是否对这个过程了如指掌了呢?如果发现对哪些细节比较模糊,可以回去看一下相应的章节,相信会有更加深入的理解。
到此,我带着你用下单过程把网络协议的知识都复习了一遍。授人以鱼不如授人以渔。下一节,我将会带你来搭建一个网络实验环境,配合实验来说明理论。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!