只有程序员才能读懂的西游记(下)

五、唐王素酒送三藏

玄奘这个网络包要发出了。

太宗设朝,聚集文武,要去送行。李世民送给玄奘三个东西。

上一节说了太宗是应用层,关注保大唐江山永固,玄奘是TCP层,要通过坚定的意志到达西天。

李世民给的第一个东西是通关文牒,这个是IP层的,将来要通过这个文牒通过一个个城关。

第二个东西是紫金钵盂,这个用于玄奘法师到了某个城市里面化斋,同时打听路的时候使用,这个是一个MAC层的。

第三个东西是白马一匹,作为远程脚力,这个是物理层的。

最后,太宗敬了玄奘一杯素酒,言道:宁恋本乡一捻土,莫爱他乡万两金。三藏方悟捻土之意,复谢恩饮尽,辞谢出关而去。

当客户端和服务端之间建立了连接后,接下来就要发送下单请求的网络包了。

在用户层发送的是HTTP的网络包,因为服务端提供的是RESTful API,因而HTTP层发送的就是一个请求。

POST /purchaseOrder HTTP/1.1Host: www.geektime.comContent-Type: application/json; charset=utf-8Content-Length: nnn { "order": { "date": "2018-07-01", "className": "趣谈网络协议", "Author": "刘超", "price": "68" }}

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头,发送出去。

只有IT人才能读懂的西游记(下)

一个完整的网络包的格式是这样的。

只有IT人才能读懂的西游记(下)

接下来,网络包就正式发出了。

如果你是用手机打开APP,下单购物发送网络包,一般通过手机运营商的网络。

只有IT人才能读懂的西游记(下)

客户的手机开机以后,在附近寻找基站eNodeB,发送请求,申请上网。基站将请求发给MME,MME对手机进行认证和鉴权,还会请求HSS看有没有钱,看看是在哪里上网。

当MME通过了手机的认证之后,开始建立隧道,建设的数据通路分两段路,其实是两个隧道。一段是从eNodeB到SGW,第二段是从SGW到PGW,在PGW之外,就是互联网。

PGW会为手机分配一个IP地址,手机上网都是带着这个IP地址的。

对于手机来讲,默认的网关在PGW上。在移动网络里面,从手机到SGW,到PGW是有一条隧道的。在这条隧道里面,会将上面的这个包作为隧道的乘客协议放在里面,外面SGW和PGW在核心网机房的IP地址。网络包直到PGW(PGW是隧道的另一端)才将里面的包解出来,转发到外部网络。

所以,从手机发送出来的时候,网络包的结构为:

  • 源MAC:手机也即UE的MAC;
  • 目标MAC:网关PGW上面的隧道端点的MAC;
  • 源IP:UE的IP地址;
  • 目标IP:SLB的公网IP地址。

进入隧道之后,要封装外层的网络地址,因而网络包的格式为:

  • 外层源MAC:E-NodeB的MAC;
  • 外层目标MAC:SGW的MAC;
  • 外层源IP:E-NodeB的IP;
  • 外层目标IP:SGW的IP;
  • 内层源MAC:手机也即UE的MAC;
  • 内层目标MAC:网关PGW上面的隧道端点的MAC;
  • 内层源IP:UE的IP地址;
  • 内层目标IP:SLB的公网IP地址。

当隧道在SGW的时候,切换了一个隧道,为从SGW到PGW的隧道,因而网络包的格式为:

  • 外层源MAC:SGW的MAC;
  • 外层目标MAC:PGW的MAC;
  • 外层源IP:SGW的IP;
  • 外层目标IP:PGW的IP;
  • 内层源MAC:手机也即UE的MAC;
  • 内层目标MAC:网关PGW上面的隧道端点的MAC;
  • 内层源IP:UE的IP地址;
  • 内层目标IP:SLB的公网IP地址。

在PGW的隧道端点将包解出来,转发出去的时候,一般在PGW出外部网络的路由器上,会部署NAT服务,将手机的IP地址转换为公网IP地址,当请求返回的时候,再NAT回来。

因而在PGW之后,相当于做了一次欧洲十国游型的转发,网络包的格式为:

  • 源MAC:PGW出口的MAC;
  • 目标MAC:NAT网关的MAC;
  • 源IP:UE的IP地址;
  • 目标IP:SLB的公网IP地址。

在NAT网关,相当于做了一次玄奘西游型的转发,网络包的格式变成:

  • 源MAC:NAT网关的MAC;
  • 目标MAC:A2路由器的MAC;
  • 源IP:UE的公网IP地址;
  • 目标IP:SLB的公网IP地址。

在手机运营商的网络里面,网络状况是比较好的。

对于玄奘法师,在大唐国境之内,还是比较平安的。原文说:们行了数日,到了巩州城。早有巩州合属官吏人等,迎接入城中。安歇一夜,次早出城前去。一路饥餐渴饮,夜住晓行,两三日,又至河州卫。早有镇边的总兵与本处僧道,闻得是钦差御弟法师上西方见佛,无不恭敬,接至里面供给了,着僧纲请往福原寺安歇。本寺僧人,一一参见,安排晚斋。斋毕,吩咐二从者饱喂马匹,天不明就行。

真的是有接有送。

行经半日,只见对面处,有一座大山,真个是高接青霄,崔巍险峻。此山唤做两界山,东半边属我大唐所管,西半边乃是鞑靼的地界。过了这座山,就不是大唐的土地了。

只有IT人才能读懂的西游记(下)

只有IT人才能读懂的西游记(下)

只有IT人才能读懂的西游记(下)

六、历经千山与万险

离开大唐的国土,接下来的路应该怎么走呢?

好在此去西天,要经过一个个国家,每个国家有一个个城关,玄奘法师只要到处问路,只要这些城关的守门人知道大概路怎么走,就能一个个国家的走下去,如果遇到国家,还有通关文牒,还能保护玄奘法师在国内的安全。

只有IT人才能读懂的西游记(下)

只有IT人才能读懂的西游记(下)

这里有两个问题要解决,第一个是每个城关的守门人和每个国家,是怎么知道去西天怎么走的。第二个问题是玄奘如何问路,如何走。

我们先第一个问题,这个观音菩萨从西天来东土的时候,已经通过一种法术告诉这些国家和城关了。

菩萨的法术主要分两种情况,一种情况是在一个国家内部如何走,另一种情况在国家之间,在野外如何走的问题。

在一个国家内部,菩萨主要遵循最短路径原则,就是走得路越少越好,道路越短越好。

但是国家之间,菩萨不但要考虑远近的问题,还要考虑政策的问题。例如有的国家路近,但是路过的国家看不惯僧人,见了僧人就抓。例如灭法国,连光头都要抓。这样的情况即便路近,也最好绕远点走。

菩萨的法术是什么呢?咱们在大学里面学习计算机网络与数据结构的时候,知道求最短路径常用的有两种方法,一种是 Bellman-Ford 算法,一种是 Dijkstra 算法。在计算机网络中基本也是用这两种方法计算的。

距离矢量路由(distance vector routing),它是基于 Bellman-Ford 算法的。

链路状态路由(link state routing),基于 Dijkstra 算法。

最常用的两种路由协议:

OSPF(Open Shortest Path First,开放式最短路径优先)就是这样一个基于链路状态路由协议,广泛应用在数据中心中的协议,称为内部网关协议(Interior Gateway Protocol,简称IGP)

BGP 协议使用的算法是路径矢量路由协议(path-vector protocol)。它是距离矢量路由协议的升级版,称为外网路由协议(Border Gateway Protocol,简称BGP)

路由协议是城关之间相互沟通到哪里应该怎么走的协议。

只有IT人才能读懂的西游记(下)

第二个问题,也就是玄奘如何问路,如何走。这就是IP协议。

这就要靠通关文牒了,里面写着贫僧来自东土大唐(就是源IP地址),欲往西天拜佛求经(指的是目标IP地址)。路过宝地,借宿一晚,明日启行,请问接下来该怎么走啊?

只有IT人才能读懂的西游记(下)

只有IT人才能读懂的西游记(下)

只有IT人才能读懂的西游记(下)

在解决第一个问题的时候,每个城关已经通过菩萨的法术,和邻近的城关进行沟通,知道了下面的信息。

只有IT人才能读懂的西游记(下)

这个叫路由表,根据这个表格,可以告诉唐僧怎么走。

接下来我们看完整故事。

只有IT人才能读懂的西游记(下)

出了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的滑动窗口协议。

只有IT人才能读懂的西游记(下)

整个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层的缓存就空了出来,这会导致上面图中的大三角,也即接收方能够容纳的总缓存,整体顺时针滑动。小的三角形,也即接收方告知发送方的窗口总大小,也即还没有完全确认收到的缓存大小,如果把这些填满了,就不能再发了,因为没确认收到,所以一个都不能扔。

七、功成行满见真如

唐僧经历九九八十一难,终于到达了西天。发现金顶大仙已经在等他们了。

只有IT人才能读懂的西游记(下)

网络包从手机端经历千难万险,终于到了SLB的公网IP所在的公网网口。由于匹配上了MAC地址和IP地址,因而将网络包收了进来。

到了西天,唐僧度过最后一条河凌云仙渡的时候,发现滚浪飞流,约有八九里宽阔,四无人迹。好不容易盼来一条船,还没有底。原来驾船的是接引佛祖,玄奘法师的肉体随着河水飘走,从而脱胎换骨,成就金身。

只有IT人才能读懂的西游记(下)

在虚拟网关节点的外网网口上,会有一个NAT规则,将公网IP地址转换为VPC里面的私网IP地址,这个私网IP地址就是SLB的HAProxy所在的虚拟机的私网IP地址。

从而网络包也脱胎换骨,实现公网IP到私有网络IP的转换。

只有IT人才能读懂的西游记(下)

当然为了承载比较大的吞吐量,虚拟网关节点会有多个,物理网络会将流量分发到不同的虚拟网关节点。同样HAProxy也会是一个大的集群,虚拟网关会选择某个负载均衡节点,将某个请求分发给它,负载均衡之后是Controller层,也是部署在虚拟机里面的。

当网络包里面的目标IP变成私有IP地址地址之后,虚拟路由会查找路由规则,将网络包从下方的私网网口发出来。这个时候包的格式为:

  • 源MAC:网关MAC;
  • 目标MAC:HAProxy虚拟机的MAC;
  • 源IP:UE的公网IP;
  • 目标IP:HAProxy虚拟机的私网IP。

在第一部分,我们 说佛经是存放在一个虚拟空间里面的,要打开这个虚拟空间,解读经文,需要一个芝麻开门的ID。接引佛祖会给玄奘法师一个ID。

在虚拟路由节点上,也会有OVS,将网络包封装在VXLAN隧道里面,VXLAN ID就是给你的租户创建VPC的时候分配的。VXLAN ID就是VPC虚拟空间的ID,OVS就是那个能够封装和解开私密空间的法宝。

包的格式为:

  • 外层源MAC:网关物理机MAC;
  • 外层目标MAC:物理机A的MAC;
  • 外层源IP:网关物理机IP;
  • 外层目标IP:物理机A的IP;
  • 内层源MAC:网关MAC;
  • 内层目标MAC:HAProxy虚拟机的MAC;
  • 内层源IP:UE的公网IP;
  • 内层目标IP:HAProxy虚拟机的私网IP。

在物理机A上,OVS会将包从VXLAN隧道里面解出来,发给HAProxy所在的虚拟机。HAProxy所在的虚拟机发现MAC地址匹配,目标IP地址匹配,就根据TCP端口,将包发给HAProxy进程,因为HAProxy是在监听这个TCP端口的。因而HAProxy就是这个TCP连接的服务端,客户端是手机。对于TCP的连接状态,滑动窗口等,都是在HAProxy上维护的。

在这里HAProxy是一个四层负载均衡,也即他只解析到TCP层,里面的HTTP协议他不关心,就将请求转发给后端的多个Controller层的一个。

HAProxy发出去的网络包就认为HAProxy是客户端了,看不到手机端了。网络包格式如下:

  • 源MAC:HAProxy所在虚拟机的MAC;
  • 目标MAC:Controller层所在虚拟机的MAC;
  • 源IP:HAProxy所在虚拟机的私网IP;
  • 目标IP:Controller层所在虚拟机的私网IP。

当然这个包发出去之后,还是会被物理机上的OVS放入VXLAN隧道里面,网络包格式为:

  • 外层源MAC:物理机A的MAC;
  • 外层目标MAC:物理机B的MAC;
  • 外层源IP:物理机A的IP;
  • 外层目标IP:物理机B的IP;
  • 内层源MAC:HAProxy所在虚拟机的MAC;
  • 内层目标MAC:Controller层所在虚拟机的MAC;
  • 内层源IP:HAProxy所在虚拟机的私网IP;
  • 内层目标IP:Controller层所在虚拟机的私网IP。

在物理机B上,OVS会将包从VXLAN隧道里面解出来,发给Controller层所在的虚拟机。Controller层所在的虚拟机发现MAC地址匹配,目标IP地址匹配,就根据TCP端口,将包发给Controller层的进程,因为他是在监听这个TCP端口的。

在HAProxy和Controller层之间,维护一个TCP的连接。

Controller层收到包之后,他是关心HTTP里面是什么的,于是解开HTTP的包,发现是一个POST请求,内容是下单购买一个课程。

八、取得真经成金身

玄奘法师终于到达西天大雷音寺,见到了我佛如来。

只有IT人才能读懂的西游记(下)

佛祖愿意传经给玄奘,于是让玄奘去藏经楼取经文,谁知道西天也有西天的规矩,如果不懂这里的规矩,就很难和管理经文的人沟通,取不到真经。

只有IT人才能读懂的西游记(下)

只有IT人才能读懂的西游记(下)

只有IT人才能读懂的西游记(下)

同理,在电商服务里面,往往在组合服务层会有一个专门管理下单的服务,Controller层虽然对外暴露的是标准的RESTful协议,但是对内会通过RPC协议调用这个组合服务层。如果不懂这个协议,就没法通信。

假设我们使用的是Dubbo,则Controller层需要读取注册中心,将下单服务的进程列表拿出来,选出一个来调用。

只有IT人才能读懂的西游记(下)

Dubbo中默认的RPC协议是Hessian2。Hessian2将下单的远程调用序列化为二进制进行传输。

Netty是一个非阻塞的基于事件的网络传输框架。Controller层和下单服务之间,使用了Netty的网络传输框架。有了Netty,就不用自己编写复杂的异步Socket程序了。Netty使用的方式,就是咱们讲Socket编程的时候,一个项目组支撑多个项目(IO多路复用,从派人盯着到有事通知)这种方式。

Netty还是工作在Socket这一层的,发送的网络包还是基于TCP的。在TCP的下层,还是需要封装上IP头和MAC头。如果跨物理机通信,还是需要封装的外层的VXLAN隧道里面。当然底层的这些封装,Netty都不感知,它只要做好它的异步通信即可。

在Netty的服务端,也即下单服务中,收到请求后,先用Hessian2的格式进行解压缩。然后将请求分发到线程中进行处理,在线程中,会调用下单的业务逻辑。

玄奘师徒好在后来碰到了懂得内情的注册中心——弥勒佛,从而会到灵山,还是按照人家的规矩办了,才将无字经文,换成有字经文。

只有IT人才能读懂的西游记(下)

只有IT人才能读懂的西游记(下)

只有IT人才能读懂的西游记(下)

下单的业务逻辑比较复杂,往往要调用基础服务层里面的库存服务、优惠券服务等,将多个服务调用完毕,才算下单成功。下单服务调用库存服务和优惠券服务,也是通过Dubbo的框架,通过注册中心拿到库存服务和优惠券服务的列表,然后选一个调用。

调用的时候,统一使用Hessian2进行序列化,使用Netty进行传输,底层如果跨物理机,仍然需要通过VXLAN的封装和解封装。

咱们以库存为例子的时候,讲述过幂等的接口实现的问题。因为如果扣减库存,仅仅是谁调用谁减一。这样存在的问题是,如果扣减库存因为一次调用失败,而多次调用,这里指的不是TCP多次重试,而是应用层调用的多次重试,就会存在库存扣减多次的情况。

这里常用的方法是,使用乐观锁(Compare and Set,简称CAS)。CAS要考虑三个方面,当前的库存数、预期原来的库存数和版本,以及新的库存数。在操作之前,查询出原来的库存数和版本,真正扣减库存的时候,判断如果当前库存的值与预期原值和版本相匹配,则将库存值更新为新值,否则不做任何操作。

这是一种基于状态而非基于动作的设计,符合REST的架构设计原则。这样的设计有利于高并发场景。当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

最终,当下单更新到分布式数据库中之后,整个下单过程才算真正告一段落。

只有IT人才能读懂的西游记(下)

当然,这个下单调用要返回一个结果。

我们下单成功啦!!!!!!

欢迎工作一到五年的Java工程师朋友们加入Java高并发: 957734884,群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

猜你喜欢

转载自blog.csdn.net/kukelook/article/details/89360279