Docker的核心技术和实现原理:
Docker核心技术支撑:
- Namespace:(实现了进程和网络资源的隔离)
1.1 docker run创建容器时,将当前容器服务的pid和namespace绑定,从而通过namespace完成了容器服务与宿主机进程、网络的隔离;
1.2 每一个docker run启动的容器都有一个独立的网络空间,那么它是如何通过宿主机网络和外界通信的呢?
1.2.1 docker的网络桥接模式。docker服务器会在主机上创建虚拟网桥docker0,随后该主机上的所有服务默认和docker0相连;
1.2.2 容器创建时会创建一对虚拟网卡,一个放在容器中,一个加入docker0网桥中,这两个网卡构成了数据通道;
1.2.3 docker0会为每个容器分配一个新的IP地址,并将docker0的IP地址设置为默认的网关,这样所有容器的消息都可以通过docker0转发;
1.2.4 通过对宿主机设置iptables,比如docker run -d -p 6379:6379 redis命令启动了一个 新的redis容器:则iptables变更如下:
iptables新增一条记录: tcp -- anywhere anywhere tcp dpt:6379 to:192.168.0.4:6379
1.2.5 可以看到,当有docker的服务需要暴露给宿主机时:
1.2.5.1 首先docker0先为redis容器分配一个IP:192.168.0.4;
1.2.5.2 然后,docker在iptables中追加了一条NAT规则,使得所有发送到宿主机6379端口的TCP包,都会转发到192.168.0.4:6379端口上;
- Control Groups:(解决了CPU、内存等资源的隔离)
2.1 linux下CGroups包含哪些子系统?/sys/fs/cgroup/cpu 、memory、device等;
2.2 docker每创建一个容器都会在对应的子系统下创建docker/containerId子目录,该目录下以文件的形式维持配额;
- Union FileSystem:
3.1 docker使用了不同的存储驱动管理镜像文件系统并运行容器;
3.2 Dockerfile中的每一个命令都会在已有的只读层上创建一个新的只读层;当镜像被docker run命令创建时就会在镜像的最上层添加一个可读写的层;
3.3 AUFS——Union FileSystem的升级版,作为一个联合文件系统,能够将不同文件夹中的层联合到同一个文件夹中;
- 改变容器挂载点:(实现了文件系统的隔离)
4.1 在新的进程中创建隔离的挂载点命名空间需要在clone函数中传入CLONE_NEWNS,这样子进程就能得到父进程挂载点的拷贝,否则子进程对文件的读写会同步到父进程和整个文件系统;
4.2 通过lincontainer提供的chroot(path)可以改进当前系统根目录结构,这样新的根目录结构下,用户不能访问旧的根目录结构和文件;
4.3 通过上面两点docker完成了对容器和宿主机目录的隔离;
iptables:
- 专业词汇:
1.1 netfilter-内核空间的防火墙对应的安全框架;
1.2 iptables——用户空间的客户端代理,将用户的安全设定执行到对应的安全框架中;
- 功能:(iptables + netfileter)
2.0 iptables其实是按照规则(链+表)处理(方法)数据包;
2.0.1 规则:是存储在内核空间的信息过滤表中,这些规则指定了源地址、目的地址、传输协议和服务类型等信息,当数据包与规则匹配时,iptablse就根据规则所定义的方法来处理数据包;
2.0.2 方法:放行(accept)、拒绝(reject)和丢弃(drop)等;
2.1 封包过滤;
2.2 封包重定向;
2.3 网络地址转换;
3.原理:
3.1 iptables链:iptables中的“关卡”,每一条链上设置了多个规则,如果网络包走到某个关卡时,按照规则的优先级匹配到了任意一项,则iptables按对应的方法处理数据包;
3.1.1 PREROUTING:路由前;
3.1.2 INPUT:本地主机输入;
3.1.3 FORWARD:转发;
3.1.4 OUTPUT:本地主机输出;
3.1.5 POSTROUTING:路由后;
3.2 iptables表:对每条链上的规则按功能归类,则将规则归类出了4种表:
3.2.1 filter表:负责过滤功能,防火墙;内核模块:iptables_filter;
3.2.2 net表:网络地址转换功能;内核模块:iptable_net;
3.2.3 mangle表:拆解、修改、重组报文功能;内核模块: iptable_mangle;
3.2.4 raw表:关闭nat表上启用的链接追踪机制;iptable_raw;
计算机网络经典问题:
- 慢启动:
1.0 拥塞窗口大小(congestion window);
慢启动阈值(ssthreash);
1.1 客户端能同时传输的最大数据段的数量是min(接收窗口大小,发送窗口大小),即min(rwnd, cwnd);
1.2 一般发送窗口大小设为10;
1.2 当使用TCP慢启动时,发送方没收到响应方的1个ACK,拥塞窗口大小就会加1,。当拥塞窗口大小 > 慢启动阈值时,就会启动拥塞避免算法:
- 拥塞避免:(线增积减算法)
2.1 初始时,cwnd < (ssthreash),启动慢启动算法;
2.1.0 发送方每收到1个ACK,cwnd + 1,因为客户端能同时发送 <= min(cwnd, rwnd)个数据段,所以初始时cwnd呈指数式增长;
2.2 若当前cwnd >= ssthreash, 则启用拥塞避免算法;
2.2.0 即发送端的cwnd,每经过一个RTT只增加1;此时cwnd按照线性方式增长;
2.3 当发送端发生超时(丢包)时,ssthreash更新为当前cwnd的一半;
- TCP三次握手带来的额外开销:
3.1 三次握手增加了1.5RTT(RTT == 数据包在网络中一个来回的时间开销);
3.2 每个应用层的数据包需要通过TCP、IP、以太网头部封装,3个来回,至少增加3*(14 + 20 + 20 )= 162字节的额外开销;
- TCP消息的重传机制:
4.1 当TCP传输一个数据段时,它会将该数据段的副本放到重传队列上并开启重传定时器;
4.2 如果发送方接收到了该数据段对应的ACK,则从重传队列中删除该数据段;
4.3 如果计时器超时,则重新发送该数据段;
4.4 如果接收方漏掉了部分数据,或者收到了乱序数据,TCP是如何解决的?
4.4.0 例接收方接收到了序号为2-5的数据包,漏掉了序号为1的数据包,由于TCP ACK的语义是“当前数据段的全部数据段都已经全部被接收和处理”,因此接收方不会返回ACK = 6
4.4.1 快速重传:在4.4.0情况下,接收方连续发送 ack 1,TCP发送端认为多次收到同一个ack seq,代表需要快速重新发送seq对应的这个数据段;
- TCP协议为什么要有TIME_WAIT状态?(TCP与不确定的网络延迟斗争的结果)
5.1 客户端在收到服务端发来的FIN消息后,进入TIME_WAIT状态并向服务端发送ACK消息,服务端收到后会直接进入CLOSED状态;
5.2 (重要)客户端在等待两个最大数据段生命周期(Maximum Segment lifetime-MSL)后,也会进入CLOSED状态;
5.3 TCP协议需要TIME_WAIT状态和客户端需要2MSL才进入CLOSED状态的原因一样:
5.3.1 阻止延迟数据段;(由于网络问题,客户端可能收到上一次TCP链接中未被接收的数据段)
5.3.1.1 TCP在分配新的序列号之前需要至少静默数据段在网络中能够存活的最长时间,即MSL;
5.3.1.2 TIME_WAIT需要等待2MSL——网络中可能存在来自发起方的数据段,从发出到客户端收到ACK,需要2个MSL;
5.3.2 保证服务端发出的FIN请求得到客户端的ACK。(如果服务端每收到ACK,此时处于LAST_ACK的服务端,它会回复RST消息终止新链接的建立)
5.4 如何处理单机上的TIME_WAIT状态(Linux上关闭链接需要等待60s,另外主机上可分配的端口号32768 ~ 61000)
5.4.1 使用net.ipv4.tcp_tw_reuse选项,通过TCP的时间戳选项允许内核重用处于TIME_WAIT状态的TCP链接;
5.4.2 修改net.ipv4.ip_local_port_range选项中的可用端口范围,增加可同时存在的TCP链接数上限;
- 为什么UDP协议头部只有8字节?
6.1 UDP协议利用下层的IP协议提供基本的数据传输能力,它的作用是引入了端口号的概念;
6.2 TCP和UDP协议中都有端口号这一概念,但是两者的端口不在一个命名空间下,所以,TCP和UDP可以同时使用相同的端口号,如:53/TCP,53/UDP;
6.2.1 所以,IP层头部20字节不仅包含了源IP、目的IP,还包含了上层协议类型以及TTL等信息;
6.3 UDP协议头部中包含的dataLength字段是否有必要?为什么TCP协议不需要?
6.3.1 UDP数据包包含了一条完整的数据,它是自包含的,而每个TCP数据包只是整个消息流的一段,所以TCP包不知道整个消息流的长度;
6.3.2 虽然IP包头部包含了dataLength,但是IP栈需要包长度仅仅是用来确认TCP栈中对应socket buffer长度是否足够用来放入IP包;
6.3.2.1 当一个完整的IP包传送给TCP协议栈时,协议栈通过IP包头部(目的IP + 端口号 + 协议类型)找到对应的socket,然后将IP包头部后的数据追加到socket buffer尾部;
6.3.2.2 另外,UDP协议不一定总数运行在IP协议之上,那么通过IP包头部dataLength来推导UDP数据包长度是不确定的;
- 为什么TCP/IP协议会拆分数据?
7.1 IP协议会分片传输过大的数据包以避免物理设备的限制;
7.1.1 作为网络层协议,能够提供数据的路由和寻址功能;IP协议在网络中不同设备间传输数据时,需要先确定一个IP数据包的大小上限,即MTU;
7.1.2 如何确定网络中两台主机传输路径MTU的限制?——Path MTU Discovery,PMTUD;
7.1.2.1 向目的主机发送IP头中DF控制位为1的数据包,DF是不分片(Don't Fragment)的缩写;
7.1.2.2 路径上的网络设备根据数据包的大小和自己的MTU做出不同的决定:
7.1.2.2.1 如果数据包大于设备的MTU,就会丢弃该数据包并发回一个包含该设备MTU的ICMP消息;
7.1.2.2.2 如果数据包小于设备的MTU,就会继续向目的主机传送数据包;
7.1.2.3 源主机收到ICMP消息后,会不断使用新的MTU发送IP数据包,直到IP数据包到达目的主机;
7.2 TCP协议拆分数据段是为了保证传输的可靠性和顺序;
7.2.1 因为物理设备的限制,IP协议会对数据包分片,作为上层协议,TCP需要根据MTU做一些限制:
7.2.1.1 如果TCP协议不对IP分片做限制,将会导致部分数据包失去传输层协议头,一旦数据包丢失就只能丢弃全部数据;
7.2.1.2 当IP协议传输数据丢包时,TCP协议的接收方无法组包,导致整个TCP数据段都要重传;(会有额外的重传和重组开销)
7.2.2 TCP协议为了保证可靠性,引入了最大分段大小(Maximum segment size, MSS)概念,它是TCP数据段能够携带的数据上限;
7.2.2.1 正常情况下,MSS = MTU - 40字节,默认是536字节;
7.2.2.2 TCP协议的MSS是操作系统内核层面的限制,通信双方会在三次握手时确定这次链接的MSS;
7.3 TCP协议中,如果MSS大于MTU,除了第一个数据包有TCP协议头,其他都没有,接收方该如何组包?
UDP协议,如果数据包大小超过MTU,接收方该如何拼接多个数据包?
——对于上面两个问题,都可以通过IP层对片进行拆分和重组保证:
7.3.1 IP层会根据网络中物理设备的最小MTU对数据片进行拆分,这个大家都知道,但是如何拆分呢?
7.3.2 IP对数据包分片和重组依赖的核心数据结构——IP协议头:标识符(16位) + 标志(3位) + 片偏移(13位);
7.3.2.1 IP协议头第4~5字节:标识符——如果IP层对数据包分了多个片,则随机生成的标识符用来标识他们同属一个数据包;(组同一个数据包依据)
7.3.2.2 IP协议头第6~7字节(高3位):标志位(从高到底依次为)——MF、DF、默认0;
7.3.2.2.1 MF = 1标识后面还有分片,MF= 0 表示最后一个分片;(组包是用来标识最后一个片)
7.3.2.2.2 DF = 0表示允许分片;
7.3.2.3 片偏移(13位),举例说明:如果一个数据包的MTU = 1200,这个数据包包含4000字节,则:
7.3.2.3.1 分片包大小为:1176(1176 + 20 < 1200 & 1176 % 8 == 0);——对应分成4片;
7.3.2.3.1 对应片偏移为:0, 1176 / 8 = 147, 11762/8 = 294, 11763/8 = 441;(组包时片顺序)
- TCP四次挥手中,如果服务器宕机或断网,当服务器重启和重新联网,这个挥手还会继续吗?
8.1 如果是服务器宕机,服务器重启后,如果网络进程没有重启,就算客户端有超时重传,当请求到来时,服务端判断目的端口没有被监听,会直接回复RST重新发起关闭链接;
8.2 如果1.1的情景中,客户端开启了keepalive选项(TCP默认关闭),那么无论网络进程是否重启,服务端在收到keepalive检测报文后,都会直接回复RST重置链接;
8.3 如果是中间网络出问题,那么无论服务端、网络进程是否异常,keepalive报文都不可达,客户端达到最大等待次数后关闭链接:
8.3.1 如果第一次FIN得不到ACK,客户端达到最大重传次数后,直接进入closed状态;
8.3.2 客户端接收到服务端第二次挥手(ACK)后,进入FIN_WAIT_2,默认等待60s,如果超时等不到服务端的FIN请求,则客户端直接进入closed状态;
8.3.3 客户端按照指数退避计算重传周期,如果第一次为1s,后面的周期依次为2s, 4s, 8s等等。
为什么数据库会丢失数据?
- 人为因素导致的运维和配置错误;
- 数据库存储数据使用的磁盘损坏导致的数据丢失;
- 数据库的功能和实现复杂,数据没有及时刷入磁盘;
1 Redis的RDB和AOF机制什么时候会将数据落盘?
2 数据成功写入数据库究竟应该如何定义?
2.1 系统调用write和fsync;
2.2 使用数据库存储核心业务数据时也不能完全新人数据库的稳定性,可以考虑使用热备和快照等方式容灾;
网友评论