打开抖音互联网会发生什么(八)|青训营笔记

打开抖音互联网会发生什么(八)|青训营笔记

这是我参与「第三届青训营 -后端场」笔记创作活动的的第八篇笔记。

里面有一些链接,建议都点开看看,能拓展很多知识。

本章预备知识:
image-20220605131437358

  1. Linux因为本人之前买的腾讯云的服务器被人黑了然后挖矿,就被封禁(痛失300元),重新搞了个VirtualBox,再下个镜像安装。
  2. wireshark是一款网络抓包分析工具,非常强大。百度安装。
  3. Socket网络编程开发有关知识,Java版本

本章主要内容:计算机网络相关知识。

课程收益:
image-20220605132852593

image-20220605133338306

课程目录:
image-20220605133353604

刷抖音网络是怎么交互的?

网络接入

互联网

image-20220605133515029

路由

同网段一般是通过一个交换机来实现,但这个交换机有可能不是物理上的交换机,也可以是UDP虚拟网络上的一个交换机(右下)。
image-20220605134012994
SDN:软件定义网络(Software Defined Network,SDN)是网络虚拟化的一种实现方式。其核心技术OpenFlow通过将网络设备的控制面与数据面分离开来,从而实现了网络流量的灵活控制,使网络作为管道变得更加智能,为核心网络及应用的创新提供了良好的平台。

image-20220605134546288
右上图:左侧想发到右侧需要经过路由的更改目标,左下的两个就是两个路由协议。

路由可以对称也可以不对称。
路由可以说工作在IP层也就是网络层,但动态路由协议这种是属于传输层。

目标IP地址应当是一直不变的,路由只是为了找到下一跳的路,然后通过ARP请求问下一跳你的Mac地址是多少,然后走到下一跳,循环走。
原Mac和目标Mac一直在变化,原IP和目标IP不变。

发包或者路由的一个伪代码流程(右下侧代码):函数名发一个包,先根据dst(一般是出口的IP)找到主机出口网关和下一跳,然后找到dst的mac(即要去的mac地址,就是下一跳的mac,这个过程也找到了出口的网卡),发包需要找到网卡的端口,发包的时候是以网卡为单位。
动态路由BGP/OSPF类似,不过是动态的配置。

ARP协议

找到下一跳的MAC就是通过ARP协议。
image-20220605140345712

ARP是类似于广播,广播不能跨网段,寻找B(广播),B听到在找他就回应一下(应答单播),因此这种只能在同一网段(逻辑)的时候用,不同网段不能ARP。

免费ARP:当一个网段新增了一个机器,因为没有ARP缓存,所以大家访问他会比较慢,因此每次新增机器的时候搞一个免费ARP,让大家更新一下MAC表,这样下次有人访问新机器的话就很快。
IPV6也会有类似的做法,新增一个IP的时候,会免费的都问一遍,防止新增的IP跟别人重了(可能同一时间新增了同一IP)。

ARP代理:劫持一个ARP请求,伪造一下发到另一个地方。

ARP本质上是查找下一跳的MAC,不是请求目标地址。

IP协议

image-20220605141548979

Mac地址不能代替IP地址吗?
不能,因为涉及到向下兼容问题,MAC协议是二层以太网协议,二层以太网协议还有很多种,很多类似于拨号可能用的不是MAC协议,这样就没法上网了。。。所以不能代替。所以美国军方想了个法子,把二层网络封装一下,弄成了IP协议。

IPv4不够用,一般怎么解决?
第一,IPv6。那不支持IPv6的设备咋办?用NAT。

NAT

NAT(Network Address Translation),是指网络地址转换,1994年提出的。当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址),但又想和因特网上的主机通信(并不需要加密)时,可使用NAT方法。
image-20220605142016419
家里的路由器就是一个NAT。

NAT不只改了IP地址,还改了端口,否则外网的包回来的时候找不到对应的服务,因为IP是大IP,端口都一样就冲突了,路由不知道给谁了。

上面内容讲的是如何将网络打通,通过NAT,ARP,MAC,路由等等。
下面讲打通后如何传输。

网络传输

image-20220605142430406

数据包

image-20220605142502153

经典七层模型,不过实际工作中很少提到表示层和会话层,一般是直接用应用层替代。

image-20220605142614016
用wireshark抓包。

image-20220605142712898
需要按层去封装。

数据包发送

image-20220605142740701

先请求DNS

image-20220605143312594
域名系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS使用UDP端口53。

这个DNS的过程是逆解析的,流程如图中文字。

DNS的传输协议UDP

image-20220605143630871

payload:有效载荷。

发包每次发多少?怎么避免分片?
每次发包要小于路径上每个路由器的MTU(什么是MTU,这里还介绍了分片的知识),这样可以避免分片。

怎么知道没丢包?
因为UDP不可靠,所以没法知道。

怎么权衡效率和质量?
没法搞,弄好了就是自己写一个TCPhh。

TCP三次握手

image-20220605145035735

Tcpdump与Wireshark

这里简单尝试了一下,Tcpdump可以在Linux上实现抓包:tcpdump抓包与Wireshark分析,这里在虚拟机上抓了一下ping百度的
image-20220607135916180
image-20220607135947727
image-20220607140012254

,然后保存到文件file1中,放入Wireshark中分析:
image-20220607140034212

这里大概可以看到ICMP请求,emmm因为我不会发TCP,所以对TCP的抓包回头再弄吧。。。

Linux的虚拟机安装和图形化界面可以看我的另一篇文章:Linux笔记

TCP连接可以理解为一个状态,拔了网线,如果服务器没有探活机制(TCP_KEEPALIVE,这篇文章讲得很好:TCP的KeepAlive 与HTTP的 Keep-Alive),那这个链接还会一直持续。
这文章有一些基础概念:LVS是Linux Virtual Server的简写,意即Linux虚拟服务器,是一个虚拟的服务器集群系统。VIP 是LVS的接收IP -- virtual IP;DIP 是LVS的分发IP -- distribute IP;RIP 是真实目标服务器IP -- Real IP;不同的模型 他们的配置也不一样。

什么是MSS 这篇文章可以弄懂MSS是什么,是在三次握手的时候确定的(在TCP Option中,取双方MSS最小值,作为TCP的MSS),MSS是传输层的,配合网络层的MTU一起用,为了减少丢包和分片的。

image-20220605172713447
SYN这次的第一位位置,ACK对方的下一次SYN是多少。
前三次不传数据,但是SYN会+1,ACK不会。更详细一点的可以见这片文章:小谈TCP协议中的Ack和Seq号。主要还是通过Tcpdump和Wireshark来抓包,分析seq和ack的变化。

image-20220605180110182

Timewait

滑动窗口和流量控制 复习的时候再看吧 内容太多啦

HTTP/HTTP1.1

image-20220606114648636

为什么不直接用TCP通信?
因为TCP太麻烦了,所以为了方便封装了HTTP。
可以类比中文和军事专用语的区别,而且HTTP是长连接,不用每次都重新握手。

HTTPS

image-20220606140110289
当明文被窃取的时候,可以通过HTTPS加密。

SSL/TLS握手

image-20220606140052950

对称加密:如果交流加密算法也被窃听就寄了。
非对称加密:再对交流加密算法加密一层。

非对称加密的盒子怎么做呢?
通过CA进行第三方的授权。

CA是什么
网络安全中ca是Certification Authority的缩写,一般是指CA证书,它是CA机构发行的一种电子文档,是一串能够表明网络用户身份信息的数字,它提供了一种在计算机网络上验证网络用户身份的方式。

总结

image-20220606140526361

网络架构怎么给抖音提质

网络提速

HTTP2.0

image-20220606140559882

多路复用,可以在TCP上同时跑多个stream。

怎么理解多路复用/stream?

image-20220606140723968

如果TCP丢包怎么办?
对头阻塞,(或者通过ACK标记重传位置,但没解决本质问题)。

QUIC/HTTP3.0

对 对头阻塞的解决方式,谷歌的解决方式(2.0 3.0 go 云计算都是他做的)。这里有篇文章讲得很好:QUIC 和 HTTP/3 队头阻塞的细节,里面的彩蛋部分没看。

image-20220606150756384
TCP因为Linux不可拔插,嵌入进去了,所以用UDP。
因为系统内核有很多种,所以坐在了用户态。
TLS/SSL握手优化,做0 RTT传输.
弱网优势:弱网情况下,3比2丢包情况优化很多。

image-20220606151058613

路径优化算法:CDN。。

数据中心分布

image-20220606151515829

POP接入:更接近核心机房
边缘机房:更接近用户 全国都有,不过越繁华的地段越多

同运营商访问

image-20220606151724865
通过解析去做,解析发现客户端是电信就用电信的。。。

静态资源(图片视频)路径优化(CDN)

CDN主要是对静态资源做缓存,如果边缘机房里有这个视频的缓存,那就直接发过去了,不用往里面走了。
image-20220606151902961

动态API(播放/评论接口)路径优化(DSA)

image-20220606151945263

机房ABCDE都对其他的机房进行一次延测速,记录到表里,就很好计算最优路径。

网络稳定

容灾概念

image-20220606152131600

一套流程下来就是容灾。

网络容灾的具体案例一

image-20220606152246775

专线:没走外部的Internet,而是抖音自己内部的网线。
外网:走的外部的Internet。

网络容灾的具体案例二

image-20220606152446160
本来域名是解析成两个的,1.1.1.1和2.2.2.2。如果一个崩了,改成域名只能解析成一个(这里需要做一次自动化的确认,确认B能够承载A的全部流量,通过CDM?)。如果B也崩了,就是雪崩。。。

网络容灾的具体案例三

image-20220606152704849

云控:从客户端的SDK控制,不去访问已经崩了的A机房。
但是有可能用户从浏览器访问,就没法更改域名了。。

网络容灾的具体案例四

image-20220606153042532

兜底:如果后面寄了,就用之前的缓存。

image-20220606153212707

故障排查

image-20220606153233286

  1. 故障明确
    image-20220606153518138
  2. 故障止损
    image-20220606153651922
  3. 分段排查
    image-20220606153731063

网络故障排查常用命令

image-20220606153912489

网络故障排查案例一

image-20220606153948590

中间节点的误判断,把正常的节点摘掉了。

网络故障排查案例二

image-20220606154035254

用户防火墙把所有的ip都禁了。。单个的问题

网络故障排查案例三

image-20220606154135174

网络故障排查案例四

image-20220606154246830

没有办法的办法,线上去debug。Fast发包:默认是路由对称,目标mac和源mac默认掉转一下就完事了,但实际情况不一样,只能改代码了。

故障预防很重要

image-20220606154541424

总结

image-20220606154555384

课后作业1

image-20220606155550655

Socket编程可以回顾一下之前的文章:Socket5代理服务器

TCP的编程

目录结构如下:

TCP服务器

一个TCP服务端可以同时连接很多个客户端,例如世界各地的用户使用自己电脑上的浏览器访问淘宝网。因为Go语言中创建多个goroutine实现并发非常方便和高效,所以我们可以每建立一次链接就创建一个goroutine去处理。

TCP服务端程序的处理流程:

  1. 监听端口
  2. 接收客户端请求建立链接
  3. 创建goroutine处理链接。

我们使用Go语言的net包实现的TCP服务端代码如下:

package main

import (
	"bufio"
	"fmt"
	"net"
)

// tcp/server/main.go
// TCP server端
// 处理函数
func process(conn net.Conn) {
	defer conn.Close() // 关闭连接
	for {
		reader := bufio.NewReader(conn)
		var buf [128]byte
		n, err := reader.Read(buf[:]) // 读取数据
		if err != nil {
			fmt.Println("read from client failed, err:", err)
			break
		}
		recvStr := string(buf[:n])
		fmt.Println("收到client端发来的数据:", recvStr)
		conn.Write([]byte(recvStr+"asdasd")) // 发送数据  添加一点内容
	}
}

func main() {
	listen, err := net.Listen("tcp", "127.0.0.1:20000")//通过这个去听客户端
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	for {
		conn, err := listen.Accept() // 建立连接
		if err != nil {
			fmt.Println("accept failed, err:", err)
			continue
		}
		go process(conn) // 启动一个goroutine处理连接
	}
}

TCP客户端

一个TCP客户端进行TCP通信的流程如下:

  1. 建立与服务端的链接
  2. 进行数据收发
  3. 关闭链接

使用Go语言的net包实现的TCP客户端代码如下:

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)

// tcp/client/main.go

// 客户端
func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:20000")//Dial相当于拨号
	if err != nil {
		fmt.Println("err :", err)
		return
	}
	defer conn.Close() // 关闭连接
	inputReader := bufio.NewReader(os.Stdin)
	for {
		input, _ := inputReader.ReadString('\n') // 读取用户输入
		inputInfo := strings.Trim(input, "\r\n")
		if strings.ToUpper(inputInfo) == "Q" { // 如果输入q就退出
			return
		}
		_, err = conn.Write([]byte(inputInfo)) // 发送数据
		if err != nil {
			return
		}
		buf := [512]byte{}
		n, err := conn.Read(buf[:])//接收数据
		if err != nil {
			fmt.Println("recv failed, err:", err)
			return
		}
		fmt.Println(string(buf[:n]))//将接收到的打印
	}
}

执行效果:

UDP的编程

UDP服务端

使用Go语言的net包实现的UDP服务端代码如下:

package main

import (
	"fmt"
	"net"
)
// UDP/server/main.go
// UDP server端
func main() {
	listen, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	defer listen.Close()
	for {
		var data [1024]byte
		n, addr, err := listen.ReadFromUDP(data[:]) // 接收数据
		if err != nil {
			fmt.Println("read udp failed, err:", err)
			continue
		}
		fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
		_, err = listen.WriteToUDP(data[:n], addr) // 发送数据
		if err != nil {
			fmt.Println("write to udp failed, err:", err)
			continue
		}
	}
}

UDP客户端
package main

import (
	"fmt"
	"net"
)

// UDP 客户端
func main() {
	socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("连接服务端失败,err:", err)
		return
	}
	defer socket.Close()
	sendData := []byte("Hello server This is Client!!!")
	_, err = socket.Write(sendData) // 发送数据
	if err != nil {
		fmt.Println("发送数据失败,err:", err)
		return
	}
	data := make([]byte, 4096)
	n, remoteAddr, err := socket.ReadFromUDP(data) // 接收数据
	if err != nil {
		fmt.Println("接收数据失败,err:", err)
		return
	}
	fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}

目录结构同TCP,效果也类似,不过服务器常开,客户端只会运行一次。

作业1中的 对UDP的ACK的处理,感觉是从服务器端入手,得先打包吧应该,大致思路应该是先打包(TCP粘包解决,这里也有打包的思路),在首部添加长度字段4字节防止粘包,然后放入8字节分别控制seq和ack,当seq和ack对应不上的时候即丢包。重传效率,感觉是服务器告诉客户端我少了哪到哪的数据,让客户端在发送一遍。。。有点像http2.0?

第一个作业答案大致如上。。。但是动手能力残疾,下次一定搞。

image-20220606155613595

作业二感觉有点太难了。。。socket编程实在是懂得太少 日后精进了一定回来搞。

posted @ 2022-06-07 14:05  杀戒之声  阅读(154)  评论(0编辑  收藏  举报