Docker 魔法解密:探索 UnionFS 与 OverlayFS

本文主要介绍了 Docker 的另一个核心技术:Union File System。主要包括对 overlayfs 的演示,以及分析 docker 是如何借助 ufs 实现容器 rootfs 的。


1. 概述

Union File System

Union File System ,简称 UnionFS 是一种为 Linux FreeBSD NetBSD 操作系统设计的,把其他文件系统联合到一个联合挂载点的文件系统服务

它使用 branch 不同文件系统的文件和目录“透明地”覆盖,形成一个单一一致的文件系统。

这些 branches 或者是 read-only 或者是 read-write 的,所以当对这个虚拟后的联合文件系统进行写操作的时候,系统是真正写到了一个新的文件中。看起来这个虚拟后的联合文件系统是可以对任何文件进行操作的,但是其实它并没有改变原来的文件,这是因为 unionfs 用到了一个重要的资管管理技术叫写时复制。

写时复制(copy-on-write,下文简称 CoW),也叫隐式共享,是一种对可修改资源实现高效复制的资源管理技术。

它的思想是,如果一个资源是重复的,但没有任何修改,这时候并不需要立即创建一个新的资源,这个资源可以被新旧实例共享。

创建新资源发生在第一次写操作,也就是对资源进行修改的时候。通过这种资源共享的方式,可以显著地减少未修改资源复制带来的消耗,但是也会在进行资源修改的时候增减小部分的开销。

UnionFS,最主要的功能是将多个不同位置的目录联合挂载(union mount)到同一个目录下。

比如,我现在有两个目录 A 和 B,它们分别有两个文件:

$ tree
.
├── A
│  ├── a
│  └── x
└── B├── b└── x

然后,我使用联合挂载的方式,将这两个目录挂载到一个公共的目录 C 上:

$ mkdir C
$ mount -t aufs -o dirs=./A:./B none ./C

这时,我再查看目录 C 的内容,就能看到目录 A 和 B 下的文件被合并到了一起:

$ tree ./C
./C
├── a
├── b
└── x

可以看到,在这个合并后的目录 C 里,有 a、b、x 三个文件,并且 x 文件只有一份。这,就是“合并”的含义。

这就是联合文件系统,目的就是将多个文件联合在一起成为一个统一的视图

常见实现

AUFS

AuFS 的全称是 Another UnionFS,后改名为 Alternative UnionFS,再后来干脆改名叫作 Advance UnionFS。

AUFS 完全重写了早期的 UnionFS 1.x,其主要目的是为了可靠性和性能,并且引入了一些新的功能,比如可写分支的负载均衡。

AUFS 的一些实现已经被纳入 UnionFS 2.x 版本。

AUFS 只是 Docker 使用的存储驱动的一种,除了 AUFS 之外,Docker 还支持了不同的存储驱动,包括 aufsdevicemapperoverlay2zfs 和 vfs 等等,在最新的 Docker 中,overlay2 取代了 aufs 成为了推荐的存储驱动,但是在没有 overlay2 驱动的机器上仍然会使用 aufs 作为 Docker 的默认驱动。

overlayfs

Overlayfs 是一种类似 aufs 的一种堆叠文件系统,于 2014 年正式合入 Linux-3.18 主线内核,目前其功能已经基本稳定(虽然还存在一些特性尚未实现)且被逐渐推广,特别在容器技术中更是势头难挡。

Overlayfs 是一种堆叠文件系统,它依赖并建立在其它的文件系统之上(例如 ext4fs 和 xfs 等等),并不直接参与磁盘空间结构的划分,仅仅将原来底层文件系统中不同的目录进行“合并”,然后向用户呈现。

简单的总结为以下 3 点:

  • 1)上下层同名目录合并;

  • 2)上下层同名文件覆盖;

  • 3)lower dir 文件写时拷贝。

这三点对用户都是不感知的。

假设我们有 dir1 和 dir2 两个目录:

  dir1                    dir2/                       /a                       ab                       c

然后我们可以把 dir1 和 dir2 挂载到 dir3 上,就像这样:

 dir3/abc

需要注意的是:在 overlay 中 dir1 和 dir2 是有上下关系的。lower 和 upper 目录不是完全一致,有一些区别,具体见下一节。

2. overlayfs 演示

当前 overlayfs 比较主流,因此使用 overlayfs 进行演示。

环境准备

具体演示如下:

创建一个如下结构的目录:

.
├── lower
│   ├── a
│   └── c
├── merged
├── upper
│   ├── a
│   └── b
└── work

具体命令如下:

mkdir ./{merged,work,upper,lower}
touch ./upper/{a,b}
touch ./lower/{a,c}

然后进行 mount 操作:

# -t overlay 表示文件系统为 overlay
# -o lowerdir=./lower,upperdir=./upper,workdir=./work 指定 lowerdir、upperdir以及 workdir这3个目录。
# 其中 lowerdir 是自读的,upperdir是可读写的,sudo mount \-t overlay \overlay \-o lowerdir=./lower,upperdir=./upper,workdir=./work \./merged

此时目录结构如下:

.
├── lower
│   ├── a
│   └── c
├── merged
│   ├── a
│   ├── b
│   └── c
├── upper
│   ├── a
│   └── b
└── work└── work

可以看到,merged 目录已经可以同时看到 lower 和 upper 中的文件了,而由于文件 a 同时存在于 lower 和 upper 中,因此 lower 中的被覆盖了,只显示了一个 a。

linux-ufs-read

修改文件

虽然 lower 和 upper 中的文件都出现在了 merged 目录,但是二者还是有区别的。

lower 为底层目录,只提供数据,不能写。

upper 为上层目录,是可读写的。

测试:

# 分别对 merged 中的文件b和c写入数据
# 其中文件 c 来自 lower,b来自 upper
echo "will-persist"  > ./merged/b
echo "wont-persist"  > ./merged/c

修改后从 merged 这个视图进行查看:

$ cat ./merged/b
will-persist
$ cat ./merged/c
wont-persist

可以发现,好像两个文件都被更新了,难道上面的结论是错的?

再从 upper 和 lower 视角进行查看:

$ cat ./upper/b
will-persist
$ cat ./lower/c
(empty)

可以发现 lower 中的文件 c 确实没有被改变。

那么 merged 中查看的时候,文件 c 为什么有数据呢?

由于 lower 是不可写的,因此采用了 CoW 技术,在对 c 进行修改时,复制了一份数据到 overlay 的 upper dir,即这里的 upper 目录,进入 upper 目录查看是否存在 c 文件:

[root@iZ2zefmrr626i66omb40ryZ upper]$ ll
total 8
-rw-r--r-- 1 root root  0 Jan 18 18:50 a
-rw-r--r-- 1 root root 13 Jan 18 19:10 b
-rw-r--r-- 1 root root 13 Jan 18 19:10 c
[root@iZ2zefmrr626i66omb40ryZ upper]$ cat c
wont-persist

可以看到,upper 目录中确实存在了 c 文件,

因为是从 lower copy 到 upper,因此也叫做 copy_up。

linux-ufs-edit

删除文件

首先往 lower 目录中写入一个文件 f

[root@iZ2zefmrr626i66omb40ryZ ufs]$  cd lower/
[root@iZ2zefmrr626i66omb40ryZ lower]$ echo fff >> f

然后到 merge 目录查看,能否看到文件 f

[root@iZ2zefmrr626i66omb40ryZ lower]$ ls ../merged/
f

果然 lower 中添加后,merged 中也能直接看到了。

然后再 merged 中去删除文件 f:

[root@iZ2zefmrr626i66omb40ryZ lower]$ cd ../merged/
[root@iZ2zefmrr626i66omb40ryZ merged]$ rm -rf f
# merged 中删除后 lower 中文件还在
[root@iZ2zefmrr626i66omb40ryZ merged]$ ls ../lower/
a  c  e  f
# 而 upper 中出现了一个大小为0的c类型文件f
[root@iZ2zefmrr626i66omb40ryZ merged]# ls -l ../upper/
total 0
c--------- 1 root root 0, 0 Jan 18 19:28 f

可以发现,overlay 中删除 lower 中的文件,其实也是在 upper 中创建一个标记,表示这个文件已经被删除了,而不会真正删除 lower 中的文件。

测试一下:

[root@iZ2zefmrr626i66omb40ryZ merged]$ rm -rf ../upper/f
[root@iZ2zefmrr626i66omb40ryZ merged]$ ls
f
[root@iZ2zefmrr626i66omb40ryZ merged]$ cat f
fff

把 upper 中的大小为 0 的 f 文件给删掉后,merged 中又可以看到 lower 中 f 了,而且内容也是一样的。

说明 overlay 中的删除其实是标记删除。再 upper 中添加一个删除标记,这样该文件就被隐藏了,从 merged 中看到的效果就是文件被删除了。

删除文件或文件夹时,会在 upper 中添加一个同名的 c 标识的文件,这个文件叫 whiteout 文件。

当扫描到此文件时,会忽略此文件名。

linux-ufs-delete

添加文件

最后再试一下添加文件

# 首先在 merged 中创建文件 g
[root@iZ2zefmrr626i66omb40ryZ merged]$ echo ggg >> g
[root@iZ2zefmrr626i66omb40ryZ merged]$ ls
g
# 然后查看 upper,发现也存在文件 g
[root@iZ2zefmrr626i66omb40ryZ merged]$ ls ../upper/
g
# 在查看内容,发送是一样的
[root@iZ2zefmrr626i66omb40ryZ merged]$ cat ../upper/g
ggg

说明 overlay 中添加文件其实就是在 upper 中添加文件。

测试一下删除会怎么样呢:

[root@iZ2zefmrr626i66omb40ryZ merged]$ rm -rf ../upper/g
[root@iZ2zefmrr626i66omb40ryZ merged]$ ls
f

把 upper 中的文件 g 删除了,果然 merged 中的文件 g 也消失了。

linux-ufs-add

3. docker 是如何使用 overlay 的?

上一节分析了 overlayfs 具体使用,这里分享一下 docker 是怎么使用 overlayfs。

大致流程

每一个 Docker image 都是由一系列的 read-only layers 组成:

  • image layers 的内容都存储在 Docker hosts filesystem 的 /var/lib/docker/aufs/diff 目录下

  • 而 /var/lib/docker/aufs/layers 目录则存储着 image layer 如何堆栈这些 layer 的 metadata。

docker 支持多种 graphDriver,包括 vfs、devicemapper、overlay、overlay2、aufs 等等,其中最常用的就是 aufs 了,但随着 linux 内核 3.18 把 overlay 纳入其中,overlay 的地位变得更重。

docker info命令可以查看 docker 的文件系统。

$ docker info
# ...Storage Driver: overlay2
#...

比如这里用的就是 overlay2。

例如,假设我们有一个由两层组成的容器镜像:

   layer1:                 layer2:/etc                    /binmyconf.ini              my-binary

然后,在容器运行时将把这两层作为 lower 目录,创建一个空upper目录,并将其挂载到某个地方:

sudo mount \-t overlay \overlay \-o lowerdir=/layer1:/layer2,upperdir=/upper,workdir=/work \/merged

最后将/merged用作容器的 rootfs。

这样,容器中的文件系统就完成了。

具体分析

以构建镜像方式演示以下 docker 是如何使用 overlayfs 的。

先拉一下 Ubuntu:20.04 的镜像:

$ docker pull ubuntu:20.04
20.04: Pulling from library/ubuntu
Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
Status: Downloaded newer image for ubuntu:20.04
docker.io/library/ubuntu:20.04

然后写个简单的 Dockerfile :

 FROM ubuntu:20.04RUN echo "Hello world" > /tmp/newfile

开始构建:

$ docker build -t hello-ubuntu .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM ubuntu:20.04---> ba6acccedd29
Step 2/2 : RUN echo "Hello world" > /tmp/newfile---> Running in ee79bb9802d0
Removing intermediate container ee79bb9802d0---> 290d8cc1f75a
Successfully built 290d8cc1f75a
Successfully tagged hello-ubuntu:latest

查看构建好的镜像:

$ docker images
REPOSITORY                                             TAG            IMAGE ID       CREATED          SIZE
hello-ubuntu                                           latest         290d8cc1f75a   13 minutes ago   72.8MB
ubuntu                                                 20.04          ba6acccedd29   3 months ago     72.8MB

使用docker history命令,查看镜像使用的 image layer 情况:

$ docker history hello-ubuntu
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
290d8cc1f75a   22 seconds ago   /bin/sh -c echo "Hello world" > /tmp/newfile    12B
ba6acccedd29   3 months ago     /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>      3 months ago     /bin/sh -c #(nop) ADD file:5d68d27cc15a80653…   72.8MB

带 missing 标记的 layer 是自 Docker 1.10 之后,一个镜像的 image layer image history 数据都存储在 个文件中导致的,这是 Docker 官方认为的正常行为。

可以看到,290d8cc1f75a 这一层在最上面,只用了 12Bytes,而下面的两层都是共享的,这也证明了 AUFS 是如何高效使用磁盘空间的。

然后去找一下具体的文件:

docker 默认的存储目录是/var/lib/docker,具体如下:

[root@iZ2zefmrr626i66omb40ryZ docker]$ ls -al
total 24
drwx--x--x  13 root root   167 Jul 16  2021 .
drwxr-xr-x. 42 root root  4096 Oct 13 15:07 ..
drwx--x--x   4 root root   120 May 24  2021 buildkit
drwx-----x   7 root root  4096 Jan 17 20:25 containers
drwx------   3 root root    22 May 24  2021 image
drwxr-x---   3 root root    19 May 24  2021 network
drwx-----x  53 root root 12288 Jan 17 20:25 overlay2
drwx------   4 root root    32 May 24  2021 plugins
drwx------   2 root root     6 Jul 16  2021 runtimes
drwx------   2 root root     6 May 24  2021 swarm
drwx------   2 root root     6 Jan 17 20:25 tmp
drwx------   2 root root     6 May 24  2021 trust
drwx-----x   5 root root   266 Dec 29 14:31 volumes

在这里,我们只关心imageoverlay2就足够了。

  • image:镜像相关

  • overlay2:docker 文件所在目录,也可能不叫这个名字,具体和文件系统有关,比如可能是 aufs 等。

先看 image目录:

docker 会在/var/lib/docker/image目录下按每个存储驱动的名字创建一个目录,如这里的overlay2

[root@iZ2zefmrr626i66omb40ryZ docker]$ cd image/
[root@iZ2zefmrr626i66omb40ryZ image]$ ls
overlay2
# 看下里面有哪些文件
[root@iZ2zefmrr626i66omb40ryZ image]$ tree -L 2 overlay2/
overlay2/
├── distribution
│   ├── diffid-by-digest
│   └── v2metadata-by-diffid
├── imagedb
│   ├── content
│   └── metadata
├── layerdb
│   ├── mounts
│   ├── sha256
│   └── tmp
└── repositories.json

这里的关键地方是imagedblayerdb目录,看这个目录名字,很明显就是专门用来存储元数据的地方。

  • layerdb:docker image layer 信息

  • imagedb:docker image 信息

因为 docker image 是由 layer 组成的,而 layer 也已复用,所以分成了 layerdb 和 imagedb。

先去 imagedb 看下刚才构建的镜像:

$  cd overlay2/imagedb/content/sha256
$ ls
[root@iZ2zefmrr626i66omb40ryZ sha256]# ls
0c7ea9afc0b18a08b8d6a660e089da618541f9aa81ac760bd905bb802b05d8d5  61ad638751093d94c7878b17eee862348aa9fc5b705419b805f506d51b9882e7
// .... 省略
b20b605ed599feb3c4757d716a27b6d3c689637430e18d823391e56aa61ecf01
60d84e80b842651a56cd4187669dc1efb5b1fe86b90f69ed24b52c37ba110aba  ba6acccedd2923aee4c2acc6a23780b14ed4b8a5fa4e14e252a23b846df9b6c1

可以看到,都是 64 位的 ID,这些就是具体镜像信息,刚才构建的镜像 ID 为290d8cc1f75a,所以就找290d8cc1f75a开头的文件:

[root@iZ2zefmrr626i66omb40ryZ sha256]$ cat 290d8cc1f75a4e230d645bf03c49bbb826f17d1025ec91a1eb115012b32d1ff8
{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["bash"],"Image":"sha256:ba6acccedd2923aee4c2acc6a23780b14ed4b8a5fa4e14e252a23b846df9b6c1","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"ee79bb9802d0ff311de6d606fad35fa7e9ab0c1cb4113837a50571e79c9454df","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","echo \"Hello world\" \u003e /tmp/newfile"],"Image":"sha256:ba6acccedd2923aee4c2acc6a23780b14ed4b8a5fa4e14e252a23b846df9b6c1","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"created":"2022-01-17T12:25:14.91890037Z","docker_version":"20.10.6","history":[{"created":"2021-10-16T00:37:47.226745473Z","created_by":"/bin/sh -c #(nop) ADD file:5d68d27cc15a80653c93d3a0b262a28112d47a46326ff5fc2dfbf7fa3b9a0ce8 in / "},{"created":"2021-10-16T00:37:47.578710012Z","created_by":"/bin/sh -c #(nop)  CMD [\"bash\"]","empty_layer":true},{"created":"2022-01-17T12:25:14.91890037Z","created_by":"/bin/sh -c echo \"Hello world\" \u003e /tmp/newfile"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b","sha256:b3cce2ce0405ffbb4971b872588c5b7fc840514b807f18047bf7d486af79884c"]}}

这就是 image 的 metadata,这里主要关注 rootfs:

# 和 docker inspect 命令显示的内容差不多
// ...
"rootfs":{"type":"layers","diff_ids":
[
"sha256:9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b",
"sha256:b3cce2ce0405ffbb4971b872588c5b7fc840514b807f18047bf7d486af79884c"
]
}
// ...

可以看到 rootfs 的 diff_ids 是一个包含了两个元素的数组,这两个元素就是组成 hello-ubuntu 镜像的两个 Layer 的diffID

从上往下看,就是底层到顶层,即9f54eef412...是 image 的最底层。

然后根据 layerID 去layerdb目录寻找对应的 layer:

[root@iZ2zefmrr626i66omb40ryZ overlay2]# tree -L 2 layerdb/
layerdb/
├── mounts
├── sha256
└── tmp

在这里我们只管mountssha256两个目录,先打印以下 sha256 目录

$ cd /var/lib/docker/image/overlay2/layerdb/sha256/
$ ls
05dd34c0b83038031c0beac0b55e00f369c2d6c67aed11ad1aadf7fe91fbecda
// ... 省略
6aa07175d1ac03e27c9dd42373c224e617897a83673aa03a2dd5fb4fd58d589f

可以看到,layer 里也是 64 位随机 ID 构成的目录,找到刚才 hello-ubuntu 镜像的最底层 layer:

$ cd 9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b
[root@iZ2zefmrr626i66omb40ryZ 9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b]$ ls
cache-id  diff  size  tar-split.json.gz

文件含义如下:

  • cache-id:为具体/var/lib/docker/overlay2/<cache-id>存储路径

  • diff:diffID,用于计算 ChainID

  • size:当前 layer 的大小

docker 使用了 chainID 的方式来保存 layer,layer.ChainID 只用本地,根据 layer.DiffID 计算,并用于 layerdb 的目录名称。

chainID 唯一标识了一组(像糖葫芦一样的串的底层)diffID 的 hash 值,包含了这一层和它的父层(底层),

  • 当然这个糖葫芦可以有一颗山楂,也就是 chainID(layer0)==diffID(layer0);

  • 对于多颗山楂的糖葫芦,ChainID(layerN) = SHA256hex(ChainID(layerN-1) + " " + DiffID(layerN))。

# 查看 diffID,
$ cat diff
sha256:9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b

由于这是 layer0,所以 chainID 就是 diffID,然后开始计算 layer1 的 chainID:

ChainID(layer1) = SHA256hex(ChainID(layer0) + " " + DiffID(layer1))

layer0 的 chainID 是9f54...,而 layer1 的 diffID 根据 rootfs 中的数组可知,为b3cce...

计算 ChainID:

$ echo -n "sha256:9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b sha256:b3cce2ce0405ffbb4971b872588c5b7fc840514b807f18047bf7d486af79884c" | sha256sum| awk '{print $1}'
6613b10b697b0a267c9573ee23e54c0373ccf72e7991cf4479bd0b66609a631c

一定注意要加上 “sha256:”和中间的空格“ ” 这两部分。

因此 layer1 的 chainID 就是6613...

找到 layerdb 里面以sha256+6613 开头的目录

$ cd /var/lib/docker/image/overlay2/layerdb/sha2566613b10b697b0a267c9573ee23e54c0373ccf72e7991cf4479bd0b66609a631c
# 根据这个大小可以知道,就是hello-ubuntu 镜像的最上面层 layer
[root@iZ2zefmrr626i66omb40ryZ 6613b10b697b0a267c9573ee23e54c0373ccf72e7991cf4479bd0b66609a631c]$ cat size
12
# 查看 cache-id 找到 文件系统中的具体位置
[root@iZ2zefmrr626i66omb40ryZ 6613b10b697b0a267c9573ee23e54c0373ccf72e7991cf4479bd0b66609a631c]$ cat cache-id
83b569c0f5de093192944931e4f41dafb2d7f80eae97e4bd62425c20e2079f65

根据 cache-id 进入具体数据存储目录:

格式为 /var/lib/docker/overlay2/<cache-id>

# 进入刚才生成的目录
$ cd /var/lib/docker/overlay2/83b569c0f5de093192944931e4f41dafb2d7f80eae97e4bd62425c20e2079f65
[root@iZ2zefmrr626i66omb40ryZ 83b569c0f5de093192944931e4f41dafb2d7f80eae97e4bd62425c20e2079f65]# ls -al
total 24
drwx-----x  4 root root    55 Jan 17 20:25 .
drwx-----x 53 root root 12288 Jan 17 20:25 ..
drwxr-xr-x  3 root root    17 Jan 17 20:25 diff
-rw-r--r--  1 root root    26 Jan 17 20:25 link
-rw-r--r--  1 root root    28 Jan 17 20:25 lower
drwx------  2 root root     6 Jan 17 20:25 work
# 查看 diff 目录
[root@iZ2zefmrr626i66omb40ryZ
83b569c0f5de093192944931e4f41dafb2d7f80eae97e4bd62425c20e2079f65]$ cd diff/
[root@iZ2zefmrr626i66omb40ryZ diff]$ ls
tmp
[root@iZ2zefmrr626i66omb40ryZ diff]$ cd tmp/
[root@iZ2zefmrr626i66omb40ryZ tmp]$ ls
newfile
[root@iZ2zefmrr626i66omb40ryZ tmp]# cat newfile
Hello world

可以看到,我们新增的 newfile 就在这里。

文章转载自:探索云原生

原文链接:https://www.cnblogs.com/KubeExplorer/p/17974386

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

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

相关文章

2024年美赛数学建模思路 - 案例:退火算法

文章目录 1 退火算法原理1.1 物理背景1.2 背后的数学模型 2 退火算法实现2.1 算法流程2.2算法实现 建模资料 ## 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 退火算法原理 1.1 物理背景 在热力学上&a…

Vue+OpenLayers7:html原生网页如何使用OpenLayers7地图

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7 前言 尽管现在大部分网页都是使用Vue或者React开发了,但是还是有不少开发者使用的是网页原生html进行开发,或者是老项目维护的需要,所以为了照顾使用html原生网页的同学们,本章简单讲解一下如何使用原始html网页情况下…

关于网络协议的笔记

简介&#xff1a; 协议&#xff0c; 网络协议的简称&#xff0c;网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连 接、怎么样互相识别等。只有遵守这个约定&#xff0c;计算机之间才能相互通信交流。它的 三要素是&#xff1a;语 法、语义、时序。 为了使数…

外网ssh远程连接服务器

文章目录 外网ssh远程连接服务器一、前言二、配置流程1. 在服务器上安装[cpolar](https://www.cpolar.com/)客户端2. 查看版本号&#xff0c;有正常显示版本号即为安装成功3. token认证4. 简单穿透测试5. 向系统添加服务6. 启动cpolar服务7. 查看服务状态8. 登录后台&#xff0…

常用shell脚本命令总结

可以将shell脚本 当做终端命令的集合&#xff0c;终端可以运行shell就可以 shell 脚本 声明(第一行加) #!/bin/bash 设置变量 FILE_PATH_BASE/home/gitlab-runner/apk_download/ 使用变量 "$FILE_PATH_BASE" 1、mv命令 文件移动 mv ./build/web/ ${FILE_PATH_BASE} …

innodb底层原理和MySQL日志机制

server层 1. 连接器 客户端连接数据库需要输入账号、密码。连接器进行校验账号密码以及权限。 2. 查询缓存 连接器连接以后&#xff0c;比如输入一个select语句&#xff0c;这时候第一步就会先根据sql语句作为key给查询缓存中查看这条sql有没有已经被查询过&#xff0c;如果…

k8s图形化管理工具之rancher

前言 在前面的k8s基础学习中&#xff0c;我们学习了各种资源的搭配运用&#xff0c;以及命令行&#xff0c;声明式文件创建。这些都是为了k8s管理员体会k8s的框架&#xff0c;内容基础。在真正的生产环境中&#xff0c;大部分的公司还是会选用图形化管理工具来管理k8s集群&…

Mybatis 全局配置文件(三)

文章目录 第一章&#xff1a;概述第二章&#xff1a;properties (了解)第三章&#xff1a;settings第四章&#xff1a;typeAliases (别名处理器)第五章&#xff1a;typeHandlers (类型处理器)第六章&#xff1a;plugins(插件)第七章&#xff1a;environments (环境)第八章&…

顺序表和链表【数据结构】【基于C语言实现】【一站式速通】

目录 顺序表 顺序表的优点 顺序表的实现 1.结构体的定义 2.初始化数组 3.插入数据 4.其余接口函数的实现 5.释放内存 顺序表的缺陷 单向链表 单向链表的优点 单向链表的实现 1.链表的定义 2.链表的初始化 3.其余接口函数的实现 5.释放内存 单向链表的缺陷 双…

Docker(九)Docker Buildx

作者主页&#xff1a; 正函数的个人主页 文章收录专栏&#xff1a; Docker 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01; Docker Buildx Docker Buildx 是一个 docker CLI 插件&#xff0c;其扩展了 docker 命令&#xff0c;支持 [Moby BuildKit] 提供的功能。提…

day04-CSS进阶

01-复合选择器 定义&#xff1a;由两个或多个基础选择器&#xff0c;通过不同的方式组合而成。 作用&#xff1a;更准确、更高效的选择目标元素&#xff08;标签&#xff09;。 后代选择器 后代选择器&#xff1a;选中某元素的后代元素。 选择器写法&#xff1a;父选择器 …

Java设计模式-桥接模式(9)

馆长准备了很多学习资料,其中包含java方面,jvm调优,spring / spring boot /spring cloud ,微服务,分布式,前端,js书籍资料,视频资料,以及各类常用软件工具,破解工具 等资源。请关注“IT技术馆”公众号,进行关注,馆长会每天更新资源和更新技术文章等。请大家多多关注…

Java线程池七大参数详解和配置(面试重点!!!)

一、corePoolSize核心线程数 二、maximunPoolSize最大线程数 三、keepAliveTime空闲线程存活时间 四、unit空闲线程存活时间的单位 五、workQueue线程工作队列 1、ArrayBlockingQueue FIFO有界阻塞队列 2、LinkedBlockingQueue FIFO无限队列 3、PriorityBlockingQueue V…

竞赛保研 车道线检测(自动驾驶 机器视觉)

0 前言 无人驾驶技术是机器学习为主的一门前沿领域&#xff0c;在无人驾驶领域中机器学习的各种算法随处可见&#xff0c;今天学长给大家介绍无人驾驶技术中的车道线检测。 1 车道线检测 在无人驾驶领域每一个任务都是相当复杂&#xff0c;看上去无从下手。那么面对这样极其…

教学改进措施及方法

在教育的世界里&#xff0c;每一位教师都是一位探险家&#xff0c;探索着如何更好地点燃学生的求知欲望&#xff0c;帮助他们展翅飞翔。我&#xff0c;作为一位拥有多年教学经验的教师&#xff0c;也在这条路上不断摸索。今天&#xff0c;我想分享一些我在教学实践中的改进措施…

ai伪原创生成器app,一键生成原创文章

近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;AI伪原创生成器App已经成为了许多写手和创作者们的新宠。这款AI伪原创生成器App以其一键生成原创文章的快速便捷性&#xff0c;正在引起广泛的关注和使用。下面跟随小编一起来了解下吧&#xff01; 随着互联网的普及&…

Transformer and Pretrain Language Models3-5

Transformer结构&#xff08;优化Tricks&#xff09; Transformer在训练和生成过程中&#xff0c;采用了很多小技巧&#xff1a; 首先是训练过程&#xff0c;训练过程中采用了一种叫checkpoint average技术&#xff0c;以及ADAM的一个优化器来进行参数更新&#xff0c;另外的…

【QML-Qt Design Studio】

QML编程指南 ■ Qt Design Studio &#xff08;Qt Quick UI设计工具&#xff09;■ 安装Qt Design Studio■ ■ Qt Design Studio &#xff08;Qt Quick UI设计工具&#xff09; Qt Design Studio是一个用于创建酷炫、优美UI的工具。 简单概括其功能就是让UI设计转换为qml&…

《WebKit 技术内幕》学习之十二(2):安全机制

2 沙箱模型 2.1 原理 一般而言&#xff0c;对于网络上的网页中的JavaScript代码和插件是不受信的&#xff08;除非是经过认证的网站&#xff09;&#xff0c;特别是一些故意设计侵入浏览器运行的主机代码更是非常危险&#xff0c;通过一些手段或者浏览器中的漏洞&#xff0c…

每日一道算法题 16(2023-12-29)

package com.tarena.test.B20; import java.util.Arrays; import java.util.Scanner; /** * * 题目描述&#xff1a; 输入一个由n个大小写字母组成的字符串&#xff0c;按照Ascii码从小到大的排序规则&#xff0c;查找字符串中第k个最小ascii码值的字母&#xff08;k>…