Kubernetes教程(二)---集群网络之 Flannel 核心原理

news2024/11/16 21:38:18

来自:指月 https://www.lixueduan.com

原文:https://www.lixueduan.com/posts/kubernetes/02-cluster-network/

本文主要记录了 Kubernetes 集群网络方案之 Flannel 核心原理详解,包括其隧道方案中的两种:UDP 实现和 VXLAN 实现。

本文写于 2021-03-20,(感觉自己会了),(现在看来之前并不是真的会)

第二次更新于 2022-09-03,增加了一些自己的理解,(感觉自己又行了)

1. 概述

Docker 的默认配置下,不同宿主机上的容器通过 IP 地址进行互相访问是根本做不到的。

Docker 单机容器网络具体实现可以参考: Docker教程(四)—容器网络实现分析 以及 Docker教程(十)—Docker 单机(桥接)网络实现 这两篇文章。

为了解决这个容器跨主通信的问题,k8s 制定了 CNI 规范,然后社区里依据该规范出现了各种各样的容器网络方案。其中 Flannel 是最早实现的,也是最简单的一个,因此我们使用 Flannel 来分析。

Flannel 项目是 CoreOS 公司主推的容器网络方案。目前,Flannel 支持三种后端实现,分别是:

  • 1)VXLAN;
  • 2)host-gw;
  • 3)UDP。

其中 UDP 和 VXLAN 都是隧道模式,host-gw 则是纯三层网络方案。

UDP 模式,是 Flannel 项目最早支持的一种方式,却也是性能最差的一种方式。所以,这个模式目前已经被弃用。不过,Flannel 之所以最先选择 UDP 模式,就是因为这种模式是最直接、也是最容易理解的容器跨主网络实现。

2. Flannel UDP 模式

0. 例子

在这个例子中,我有两台宿主机。

  • 宿主机 Node 1 上有一个容器 container-1,它的 IP 地址是 100.96.1.2,对应的 docker0 网桥的地址是:100.96.1.1/24。
  • 宿主机 Node 2 上有一个容器 container-2,它的 IP 地址是 100.96.2.3,对应的 docker0 网桥的地址是:100.96.2.1/24。

我们现在的任务,就是让 container-1 访问 container-2。

1. 大致流程

UDP 方案具体流程如下图所示:

flannel-udp

2. flannel0

该方案使用时会在各个 Work 节点上运行一个Flannel 进程,同时创建一个 flannel0 设备 ,而这个 flannel0 它是一个 TUN 设备(Tunnel 设备)。

在 Linux 中,TUN 设备是一种工作在三层(Network Layer)的虚拟网络设备。TUN 设备的功能非常简单,即:在操作系统内核和用户应用程序之间传递 IP 包

当操作系统将一个 IP 包发送给 flannel0 设备之后,flannel0 就会把这个 IP 包,交给创建这个设备的应用程序,也就是 Flannel 进程。

这是一个从内核态向用户态的流动方向。

反之,如果 Flannel 进程向 flannel0 设备发送了一个 IP 包,那么这个 IP 包就会出现在宿主机网络栈中,然后根据宿主机的路由表进行下一步处理。

这是一个从用户态向内核态的流动方向。

3. Subnet

子网(Subnet) 是 Flannel 项目里一个非常重要的概念。

事实上,在由 Flannel 管理的容器网络里,一台宿主机上的所有容器,都属于该宿主机被分配的一个“子网”。

在我们的例子中,Node 1 的子网是 100.96.1.0/24,container-1 的 IP 地址是 100.96.1.2。Node 2 的子网是 100.96.2.0/24,container-2 的 IP 地址是 100.96.2.3。

而这些子网与宿主机的对应关系,正是保存在 Etcd 当中,如下所示:

$ etcdctl ls /coreos.com/network/subnets
/coreos.com/network/subnets/100.96.1.0-24
/coreos.com/network/subnets/100.96.2.0-24
/coreos.com/network/subnets/100.96.3.0-24

所以,flanneld 进程在处理由 flannel0 传入的 IP 包时,就可以根据目的 IP 的地址(比如 100.96.2.3),匹配到对应的子网(比如 100.96.2.0/24),然后从 Etcd 中找到这个子网对应的宿主机的 IP 地址,如下所示:

$ etcdctl get /coreos.com/network/subnets/100.96.2.0-24
{"PublicIP":"10.168.0.3"}

即根据容器 IP 确定子网,根据子网确定目标宿主机 IP。

4. 具体步骤

step 1:容器到宿主机

container-1 容器里的进程发起的 IP 包,其源地址就是 100.96.1.2,目的地址就是 100.96.2.3。

由于目的地址 100.96.2.3 并不在 Node 1 的 docker0 网桥的网段里,所以这个 IP 包会被交给默认路由规则,通过容器的网关进入 docker0 网桥(如果是同一台宿主机上的容器间通信,走的是直连规则),从而出现在宿主机上。

step 2:宿主机路由到 flannel0 设备

这时候,这个 IP 包的下一个目的地,就取决于宿主机上的路由规则了。

Flannel 已经在宿主机上创建出了一系列的路由规则。

以 Node 1 为例,如下所示:

# 在Node 1上
$ ip route
default via 10.168.0.1 dev eth0
100.96.0.0/16 dev flannel0  proto kernel  scope link  src 100.96.1.0
100.96.1.0/24 dev docker0  proto kernel  scope link  src 100.96.1.1
10.168.0.0/24 dev eth0  proto kernel  scope link  src 10.168.0.2

由于我们的 IP 包的目的地址是 100.96.2.3,只能匹配到第二条、也就是 100.96.0.0/16 对应的这条路由规则,从而进入到一个叫作 flannel0 的设备中。

step 3:flanneld 进程转发给 Node2

flannel0 设备收到 IP 包后转给 flanned 进程。然后,flanneld 根据这个 IP 包的目的地址,是 100.96.2.3,去 etcd 中查询到对应的宿主机IP,就是 Node2,因此会把它发送给了 Node 2 宿主机,不过发送之前会对该 IP 包进行封装。

因为当前这个包源地址是 container-1 的 IP 100.96.1.2,目的地址是 container-2 的 IP 100.96.2.3,这样直接发送出去肯定是到不了的。

step 4:封装UDP包

flanneld 进程会把这个 IP 包直接封装在一个 UDP 包里,然后发送给 Node 2。不难理解,这个 UDP 包的源地址,就是 flanneld 所在的 Node 1 的地址,而目的地址,则是 container-2 所在的宿主机 Node 2 的地址。

由于 flanneld 进程监听的是 8285 端口,所以会发送给 Node2 的 8285 端口。

step 5:Node2 解析并处理UDP包

Node2 上的 flanneld 进程收到这个 UDP 包之后就可以从里面解析出container-1 发来的原 IP 包。

解析后将其发送给 flannel0 设备,flannel0 则会将其转发给操作系统内核。

step 6:内核处理IP包

内核收到这个 IP 包之后,内核网络栈就会负责处理这个 IP 包。具体的处理方法,就是通过本机的路由表来寻找这个 IP 包的下一步流向。

该路由规则同样由 Flannel 维护。

而 Node 2 上的路由表,跟 Node 1 非常类似,如下所示:

# 在Node 2上
$ ip route
default via 10.168.0.1 dev eth0
100.96.0.0/16 dev flannel0  proto kernel  scope link  src 100.96.2.0
100.96.2.0/24 dev docker0  proto kernel  scope link  src 100.96.2.1
10.168.0.0/24 dev eth0  proto kernel  scope link  src 10.168.0.3

由于这个 IP 包的目的地址是 100.96.2.3,它跟第三条、也就是 100.96.2.0/24 网段对应的路由规则匹配更加精确。所以,Linux 内核就会按照这条路由规则,把这个 IP 包转发给 docker0 网桥。

step 7:容器网络

IP 包到 docker0 网桥后的流程就属于容器网络了,这里不在过多讲解,具体参考开篇提到的两篇文章。

5. 分析

实际上,相比于两台宿主机之间的直接通信,基于 Flannel UDP 模式的容器通信多了一个额外的步骤,即 flanneld 的处理过程。

而这个过程,由于使用到了 flannel0 这个 TUN 设备,仅在发出 IP 包的过程中,就需要经过三次用户态与内核态之间的数据拷贝,如下所示:

flannel-udp-tun

  • 1)第一次,用户态的容器进程发出的 IP 包经过 docker0 网桥进入内核态;
  • 2)第二次,IP 包根据路由表进入 TUN(flannel0)设备,从而回到用户态的 flanneld 进程;
  • 3)第三次,flanneld 进行 UDP 封包之后重新进入内核态,将 UDP 包通过宿主机的 eth0 发出去。

此外,我们还可以看到,Flannel 进行 UDP 封装(Encapsulation)和解封装(Decapsulation)的过程,也都是在用户态完成的。在 Linux 操作系统中,上述这些上下文切换和用户态操作的代价其实是比较高的,这也正是造成 Flannel UDP 模式性能不好的主要原因。

所以说,我们在进行系统级编程的时候,有一个非常重要的优化原则,就是要减少用户态到内核态的切换次数,并且把核心的处理逻辑都放在内核态进行。这也是为什么,Flannel 后来支持的VXLAN 模式,逐渐成为了主流的容器网络方案的原因。

3. Flannel VXLAN

VXLAN,即 Virtual Extensible LAN(虚拟可扩展局域网),是 Linux 内核本身就支持的一种网络虚似化技术。

所以说,VXLAN 可以完全在内核态实现上述封装和解封装的工作,从而通过与前面相似的“隧道”机制,构建出覆盖网络(Overlay Network)。

VXLAN 的覆盖网络的设计思想是:在现有的三层网络之上,“覆盖”一层虚拟的、由内核 VXLAN 模块负责维护的二层网络,使得连接在这个 VXLAN 二层网络上的“主机”(虚拟机或者容器都可以)之间,可以像在同一个局域网(LAN)里那样自由通信

当然,实际上,这些“主机”可能分布在不同的宿主机上,甚至是分布在不同的物理机房里。

简单理解就是:把 二层数据包 封装之后通过 三层网络发送,然后对应设备收到后进行解包拿到里面的 二层数据包,这样只要 三层网络连通就可以实现了,相当于把 二层网络的范围扩大到了 三层网络。

1. VTEP

而为了能够在二层网络上打通“隧道”,VXLAN 会在宿主机上设置一个特殊的网络设备作为“隧道”的两端。这个设备就叫作 VTEP,即:VXLAN Tunnel End Point(虚拟隧道端点)

而 VTEP 设备的作用,其实跟前面的 flanneld 进程非常相似。只不过,它进行封装和解封装的对象,是二层数据帧(Ethernet frame);而且这个工作的执行流程,全部是在内核里完成的(因为 VXLAN 本身就是 Linux 内核中的一个模块)。

因此 VXLAN 模式的效率会比 UDP 模式高不少

具体流程如下:

flannel-vxlan

可以看到,图中每台宿主机上名叫 flannel.1 的设备,就是 VXLAN 所需的 VTEP 设备,它既有 IP 地址,也有 MAC 地址。

2. 路由规则

每台宿主机上的 flanneld 进程会负责维护相关的路由规则。比如,当 Node 2 启动并加入 Flannel 网络之后,在 Node 1(以及所有其他节点)上,flanneld 就会添加一条如下所示的路由规则:

$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
...
10.1.16.0       10.1.16.0       255.255.255.0   UG    0      0        0 flannel.1

这条规则的意思是:凡是发往 10.1.16.0/24 网段的 IP 包,都需要经过 flannel.1 设备发出,并且,它最后被发往的网关地址是:10.1.16.0。

10.1.16.0 正是 Node 2 上的 VTEP 设备(也就是 flannel.1 设备)的 IP 地址。

即:Flannel1 设备会在当前宿主机增加指向 flannel 网络中其他节点的路由规则

类似于 UDP 模式中的 Subnet,前者是把对应节点 IP 存储在 etcd中,后者则是直接通过路由规则指定。

3. ARP 记录

flanneld 进程启动时,会自动把当前节点上的 ARP 记录发送给当前 flannel 网络中的其他节点。

后续将 IP 包封装成 二层数据帧的时候,用到的目的 MAC 地址就是从这里查询的。

4. 网桥设备

flannel.1 设备实际还要扮演一个“网桥”的角色,在二层网络进行 UDP 包的转发。而在 Linux 内核里面,“网桥”设备进行转发的依据,来自于一个叫作 FDB(Forwarding Database)的转发数据库。

flannel.1 设备的 FDB 则由 flanneld 进程维护。

5. 具体步骤

step 1:容器到宿主机

和 UDP 模式一样,当 container-1 发出请求之后,这个目的地址是 10.1.16.3 的 IP 包,根据容器内的路由规则,会先出现在 docker0 网桥。

step 2:宿主机路由到 flannel1 设备

然后被路由到本机 flannel.1 设备进行处理。也就是说,来到了“隧道”的入口

为了能够将“原始 IP 包”封装并且发送到正确的宿主机,VXLAN 就需要找到这条“隧道”的出口,即:目的宿主机的 VTEP 设备。

根据前面提到的 路由规则,知道这个 IP 包要发给 10.1.16.0 ,即 Node 2 上的 VTEP 设备(也就是 flannel.1 设备)的 IP 地址。

step 3:封装为 2 层数据帧

为了方便叙述,后续把 Node 1 和 Node 2 上的 flannel.1 设备分别称为“源 VTEP 设备”和“目的 VTEP 设备”。

而这些 VTEP 设备之间,就需要想办法组成一个虚拟的二层网络,即:通过二层数据帧进行通信。

所以在我们的例子中,“源 VTEP 设备”收到“原始 IP 包”后,就要想办法把“原始 IP 包”加上一个目的 MAC 地址,封装成一个二层数据帧,然后发送给“目的 VTEP 设备”。

在正常网络里是由 内核网络栈进行封装的,比如某机器收到一个 三层数据包,对比目的 IP 地址,发现就是内部某局域网的,eth0 设备就会使用下一跳地址对应的 MAC 地址,作为该数据帧的目的 MAC 地址,将这个 三层数据包 封装成 二层数据帧。

但是这里的 二层网络 使我们虚拟出来的,因此为了让 VTEP 设备收到的是二层数据帧,我们需要自己来处理封包的逻辑。

flannel-vxlan-inner-data-frame

前面路由记录中我们知道了“目的 VTEP 设备”的 IP 地址,这里就可以使用 IP 地址查询对应的 MAC 地址,这正是 ARP(Address Resolution Protocol )表的功能。

这也就是为什么 flanneld 进程启动后要把本地 ARP 记录发送给其他节点。

# 在Node 1上
$ ip neigh show dev flannel.1
10.1.16.0 lladdr 5e:f8:4f:00:e3:37 PERMANENT

可以看到:IP 地址 10.1.16.0,对应的 MAC 地址是 5e:f8:4f:00:e3:37。

有了这个“目的 VTEP 设备”的 MAC 地址,Linux 内核就可以开始二层封包工作了

step 4:将二层数据帧封装为外部数据帧,通过 UDP 发送出去

上面提到的这些 VTEP 设备的 MAC 地址,对于宿主机网络来说并没有什么实际意义。所以上面封装出来的这个数据帧,并不能在我们的宿主机二层网络里传输。为了方便叙述,我们把它称为**“内部数据帧”(Inner Ethernet Frame)**。

所以接下来,Linux 内核还需要再把“内部数据帧”进一步封装成为宿主机网络里的一个普通的数据帧,好让它“载着”“内部数据帧”,通过宿主机的 eth0 网卡进行传输。

我们把这次要封装出来的、宿主机对应的数据帧称为**“外部数据帧”(Outer Ethernet Frame)**。

为了实现这个“搭便车”的机制,Linux 内核会在“内部数据帧”前面,加上一个特殊的 VXLAN 头,用来表示这个“乘客”实际上是一个 VXLAN 要使用的数据帧。

而这个 VXLAN 头里有一个重要的标志叫作 VNI,它是 VTEP 设备识别某个数据帧是不是应该归自己处理的重要标识。而在 Flannel 中,VNI 的默认值是 1。

这也是为何,宿主机上的 VTEP 设备都叫作 flannel.1 的原因,这里的“1”,其实就是 VNI 的值

然后,Linux 内核会把这个数据帧封装进一个 UDP 包里发出去

step 5:flannel1 设备转发 UDP 包

flannel.1 设备实际上要扮演一个“网桥”的角色,在二层网络进行 UDP 包的转发。而在 Linux 内核里面,“网桥”设备进行转发的依据,来自于一个叫作 FDB(Forwarding Database)的转发数据库。

通过 bridge fdb 命令查看 flannel.1 设备的FDB

# 在Node 1上,使用“目的VTEP设备”的MAC地址进行查询
$ bridge fdb show flannel.1 | grep 5e:f8:4f:00:e3:37
5e:f8:4f:00:e3:37 dev flannel.1 dst 10.168.0.3 self permanent

可以看到,在上面这条 FDB 记录里,指定了这样一条规则,即:

往我们前面提到的“目的 VTEP 设备”(MAC 地址是 5e:f8:4f:00:e3:37)的二层数据帧,应该通过 flannel.1 设备,发往 IP 地址为 10.168.0.3 的主机。显然,这台主机正是 Node 2,UDP 包要发往的目的地就找到了。

所以接下来的流程,就是一个正常的、宿主机网络上的封包工作

step 6: 宿主机封包并发送

宿主机把我们准备好的 UDP 包,增加 IP 头组成一个IP包,IP 头中的IP则是前面通过 FDB 查询出来的目的主机的 IP 地址,即 Node 2 的 IP 地址 10.168.0.3。

然后增加二层数据帧头,并把 Node 2 的 MAC 地址填进去。这个 MAC 地址本身,是 Node 1 的 ARP 表要学习的内容,无需 Flannel 维护。

flannel-vxlan-outer-data-frame

接下来,Node 1 上的 flannel.1 设备就可以把这个数据帧从 Node 1 的 eth0 网卡发出去。显然,这个帧会经过宿主机网络来到 Node 2 的 eth0 网卡。

step 7:Node 2 解包

Node 2 的内核网络栈会发现这个数据帧里有 VXLAN Header,并且 VNI=1。所以 Linux 内核会对它进行拆包,拿到里面的内部数据帧,然后根据 VNI 的值,把它交给 Node 2 上的 flannel.1 设备。

而 flannel.1 设备则会进一步拆包,取出“原始 IP 包”。接下来就回到了单机容器网络的处理流程。最终,IP 包就进入到了 container-2 容器的 Network Namespace 里。

6. 分析

与 UDP 实现相比,VXLAN 方式所有封包工作都在内核态完成,省去了 内核态用户态切换的消耗,拥有较高的效率。

4. 小结

本章主要分析了 Flannel 网络中的 UDP 和 VXLAN 实现。

具体实现

通过在现有的 三层网络 上构建虚拟的 二层网络,将容器中发出的三层数据包封装为而层数据帧,然后通过宿主机网卡发送出去。接收方接收到之后再由对应的程序进行解包处理,得到原始数据包。

两种实现方式差异

相同点:都是在各个节点上运行 flanneld 进程和 flannel 设备来处理相关网络包。

差异点: UDP 方式实现有 3 次内核态用户态切换导致效率低,而 VXLAN 方式则全在内核态处理,效率较高。

VXLAN 模式组建的覆盖网络,其实就是一个由不同宿主机上的 VTEP 设备,也就是 flannel.1 设备组成的虚拟二层网络。对于 VTEP 设备来说,它发出的“内部数据帧”就仿佛是一直在这个虚拟的二层网络上流动。这,也正是覆盖网络的含义。

5. 参考

flannel-docs-backends.md

https://kubernetes.io/docs/concepts/cluster-administration/networking/

https://github.com/flannel-io/flannel

https://blog.laputa.io/kubernetes-flannel-networking-6a1cb1f8ec7c

https://feisky.gitbooks.io/kubernetes/content/network/flannel/flannel.html

深入剖析 Kubernetes 专栏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164459.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mysql之增删改查

这里的增删改查主要是对应表中的数据,不像前一篇那个列类型,耳机具体的哪一条数据 Insert 其实我们前面都用过好多次了 比如下面那个 可以 关于那个表名后面加不加(列类型),下面有解释 INSERT INTO shanpin VALUES…

关于yolov8一些训练的情况

U神出品了最新的yolov8,从公开的参数量来看确实很优秀!!!!比如下图得一些指标: 可以看到s模型640得map已经达到了44.9,v8n得map也已经达到了37.3,很强了,但是实际上是怎么…

Python爬虫之Scrapy框架系列(3)——项目实战【某瓣top250电影信息获取】

目录:1. 某瓣电影top250首页电影信息的获取!1.创建项目:2.创建爬虫文件:3.运行爬虫文件:4.设置请求头:5.获取到电影名字:5.1 使用shell交互式平台:5.1.1 首先:打开我们的…

239页10万字“联、管、用”三位一体雪亮工程整体建设方案

【版权声明】本资料来源网络,知识分享,仅供个人学习,请勿商用。【侵删致歉】如有侵权请联系小编,将在收到信息后第一时间删除!完整资料领取见文末,部分资料内容: 目录 1、 项目概述 1.1 项目背…

用R语言绘制泰勒级数的逼近过程

文章目录泰勒级数是如何被发现的用图像理解Taylor级数的逼近过程前情提要 R语言微积分极限π,e,γ\pi, e, \gammaπ,e,γ洛必达法则连续性和导数数值导数差商与牛顿插值方向导数 泰勒级数是如何被发现的 如果我是泰勒,我会把思考的起点建立在这样的一个等式上 f(n…

Windows10电脑重装系统详细步骤(纯净版)

目录 前言: 一、准备工作 二、下载pe工具 三、下载系统镜像ISO文件 获取方式一 获取方式二 获取方式三 四、进入pe系统 1.检查以上的准备工作是否完成 2.然后拔出来u盘插入要重装的电脑上面 3.然后按电源键开机(不能点击重启!&…

【Git 从入门到精通】使用Git将本地代码推送到Github

文章目录一、创建远程库二、Git操作远程库1.推送代码2.克隆代码3.拉取代码4.Pull request5.常用命令总结一、创建远程库 打开github.com,点击右上角加号,点击第一个选项。 填写库的基本信息,如果你想代码开源就选择public,否则就…

开发模型和测试模型

开发模型瀑布模型特点:线性结构,每个阶段只执行一次,必须完成上一个才能执行下一个。是其他模型的基础框架缺点:测试后置,1)前面各个阶段的遗留的风险推迟到测试阶段才被发现,导致项目大面积返工…

【7】SCI易中期刊推荐——图像处理领域(中科院4区)

🚀🚀🚀NEW!!!SCI易中期刊推荐栏目来啦 ~ 📚🍀 SCI即《科学引文索引》(Science Citation Index, SCI),是1961年由美国科学信息研究所(Institute for Scientific Information, ISI)创办的文献检索工具,创始人是美国著名情报专家尤金加菲尔德(Eugene Garfield…

【LGR-(-17)】洛谷入门赛 #8个人思考

T306713 Hello, 2023 题目背景 Goodbye, 2022 Hello, 2023 题目描述 某 E 在 2022 年的幸运数字是 xxx,这个数可能是正的,也可能是负的。 某 E 想要知道 xmod2023x \bmod 2023xmod2023 的值。其中,mod\bmodmod 是取模操作。也就是说&am…

数据结构:线性表的顺序表示和实现

在实际应用程序中涉及的线性表的基本操作都需要针对线性表的具体存储结构加以实现。线性表可以有两种存储表示方法:顺序存储表示和链式存储表示。下面我们先说说顺序存储表示。 1、顺序表——线性表的顺序存储表示 在计算机中表示线性表的最简单的方法是用一组地址连续的存储…

Linux:自动化构建工具make/Makefile

文章目录一.前言二.Makefile如何写入/make命令使用2.1清楚依赖关系和依赖方法2.2删除文件2.3Makefile中的关键字.PHONY2.4一个小补充一.前言 在此之前我们已经可以用vim编写代码和用gcc编译代码。但是如果现在要写一个大型项目,一下子写了很多源文件,在…

C. Zero Path(DP)

Problem - 1695C - Codeforces 给你一个有n行和m列的网格。我们用(i,j)表示第i(1≤i≤n)行和第j(1≤j≤m)列的方格,用aij表示那里的数字。所有的数字都等于1或等于-1。 你从方格(1,1)开始,每次可以向下或向右移动一个方格。最后&…

基于结点的数据结构——链表(单链表双向循环链表)| 附完整源码 | C语言版

本章内容 1.什么是链表 2.链表常见几种形式 3.无头单向非循环链表的实现 3.1结点结构的定义 3.2函数接口的实现 3.2.1尾插 3.2.2尾删 4. 带头双向循环链表的实现 4.1结点结构的定义 4.2函数接口的实现 5.两种链表的差异 ①尾插与尾删的时间复杂度 ②头插与头删的时…

Ai 作图 stable-diffusion-webui prompt

文章参考了 prompt指导手册 : https://strikingloo.github.io/stable-diffusion-vs-dalle-2 https://prompthero.com/stable-diffusion-prompt-guide 一般来说,最好的稳定扩散提示会有这样的形式: “ [主要主题]的[图片类型] &#xff0…

C语言-文件操作(13.1)

目录 思维导图: 1. 为什么使用文件 2. 什么是文件 2.1 程序文件 2.2 数据文件 2.3 文件名 3. 文件的打开和关闭 3.1 文件指针 3.2 文件的打开和关闭 4. 文件的顺序读写 4.1 对比一组函数 5. 文件的随机读写 5.1 fseek 5.2 ftell 5.3 rewind 6. 文本…

FeignClient调用源码解析

文章目录一、FeignClient二、整体流程1.使用FeignClient2.FeignClient整体调用流程三、源码解析1. 注解EnableFeignClients2. FeignClientsRegistrar3. Feign其他配置4. FactoryBean5. 方法调用一、FeignClient FeignClient作为SrpingCloud进行http请求的一个基础组件&#xf…

IP地址和MAC地址是什么?Dhcp和arp又是什么?

本期武汉海翎光电的小编和大家聊聊 计算机是如何通信的?IP地址和MAC地址是什么?Dhcp和arp又是什么?在我们的家庭网络中,有许多的网络设备,比如我们可以有两台计算机A和B, 一台手机一台电视机,他们都连接到了…

【尚硅谷】Java数据结构与算法笔记09 - 哈希表

文章目录一、哈希表引入二、基本介绍三、Google公司的一个上机题3.1 题目描述3.2 代码实现一、哈希表引入 1)看一个实际需求, google 公司的一个上机题: 2)有一个公司, 当有新的员工来报道时, 要求将该员工的信息加入(id,性别,年龄, 住址…), 当输入该员…

【Linux】理解文件系统——软硬链接

我们之前讨论的都是进程和被打开文件的关系,而如果一个文件是没有被打开呢?没有被打开的文件操作系统如何管理? 没有被打开的文件在磁盘上,所以磁盘上有大量的文件,这些文件要被静态管理起来,方便我们随时…