Docker 魔法解密:探索 UnionFS 与 OverlayFS

news2024/9/28 21:51:01

本文主要介绍了 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                       a
      b                       c

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

 dir3
    /
      a
      b
      c

需要注意的是:在 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                    /bin
      myconf.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.04

 RUN 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.coloradmin.cn/o/1410427.html

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

相关文章

2023.1.23 关于 Redis 哨兵模式详解

目录 引言 人工恢复主节点故障 ​编辑 主从 哨兵模式 Docker 模拟部署哨兵模式 关于端口映射 展现哨兵机制 哨兵重新选取主节点的流程 哨兵模式注意事项 引言 人工恢复主节点故障 1、正常情况 2、主节点宕机 3、程序员主动恢复 先看看该主节点还能不能抢救如果不好定…

[GDMEC-无人机遥感研究小组]无人机遥感小组-000-数据集制备

基于labelme的无人机语义分割数据集制备 文章目录 基于labelme的无人机语义分割数据集制备1. 数据获取2. 安装labelme3.利用labelme进行标注 1. 数据获取 数据集制备需要利用无人机飞行并采集标注。使用录制模式&#xff0c;镜头垂直向下进行拍摄&#xff0c;得到DJI_XXXX.MP4…

如何实现动态代理(俩种方式)

文章目录 基于接口的动态代理&#xff1a;基于类的动态代理&#xff1a; 基于接口的动态代理&#xff1a; 通过Java的反射机制来动态创建代理对象&#xff0c;代理对象实现了一个或多个接口。 Java提供了java.lang.reflect包中的Proxy类和InvocationHandler接口来实现基于接口…

仰暮计划|“他说,他是出生于两个时代的人”

凌保庆老爷爷&#xff0c;1942年10月4日出生&#xff0c;今年82岁&#xff0c;家住在河南省登封市唐庄乡磨沟村。7月28日&#xff0c;我作为仰暮计划小队的一员去拜访了这位老人&#xff0c;听凌爷爷讲述了他的故事。 走进这户人家的时候&#xff0c;凌爷爷正在书房里。虽然家…

Spring Cloud 系列:基于Seata 实现 XA模式

https://seata.io/zh-cn/docs/user/mode/xa https://seata.io/zh-cn/docs/dev/mode/xa-mode XA 规范 是 X/Open 组织定义的分布式事务处理&#xff08;DTP&#xff0c;Distributed Transaction Processing&#xff09;标准&#xff0c;XA 规范 描述了全局的TM与局部的RM之间的…

【深度学习】线性回归模型与梯度下降法

线性回归模型与梯度下降法 线性回归模型与枚举法 线性回归模型定义: w:权重b:偏置#mermaid-svg-ZAxF27Mw5dXNQgw2 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ZAxF27Mw5dXNQgw2 .error-icon{fill:#552222;}…

大数据开发之Spark(完整版)

第 1 章&#xff1a;Spark概述 1.1 什么是spark 回顾&#xff1a;hadoop主要解决&#xff0c;海量数据的存储和海量数据的分析计算。 spark是一种基于内存的快速、通用、可扩展的大数据分析计算引擎。 1.2 hadoop与spark历史 hadoop的yarn框架比spark框架诞生的晚&#xff…

JRT的无源码发布

之前介绍过JRT最大的特点就是业务脚本化。老javaer就会说你业务代码都在发布环境放着&#xff0c;那怎么代码保密&#xff0c;在发布环境别人随便改了启不是不安全&#xff0c;或者一些代码我就是不想让人看源码呢。 其实JRT的业务脚本化只是特性&#xff0c;不是代表就必须要…

【域名解析】如何将域名指向对应服务器IP

目录 &#x1f337;一、域名解析基本概念 &#x1f33c;1. 定义 &#x1f33c;2. 域名解析类型 &#x1f337;二、域名解析服务器IP地址 &#x1f33c;1. 操作步骤 &#x1f33c;2. 验证 &#x1f337;一、域名解析基础知识 &#x1f33c;1. 基本概念 定义&#xff1a; …

【操作系统】实验八 proc文件系统

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

破解不了WIFI?也许你应该试试社工...

以下案例为虚拟环境,请勿模仿 做什么? 由于工作出差在该某某企业出差,手机和电脑都没办法用流量…流量包1G1块…太贵了…我勒个豆啊…发现WIFI密码难以破解&#xff08;小kali上过了&#xff09;。 出去逛逛吧…发现楼道有海康威视摄像头,学过交换机的一般都看得出来这个摄像…

jquery多选框

使用hbuilder <!DOCTYPE html> <html><head><meta charset"GBK"><title></title></head><body><table id"myTable"> <tr> <td>黄1</td> </tr> <tr> <td>…

C语言通过IXMLHttpRequest以get或post方式发送http请求获取服务器文本或xml数据

做过网页设计的人应该都知道ajax。 Ajax即Asynchronous Javascript And XML&#xff08;异步的JavaScript和XML&#xff09;。使用Ajax的最大优点&#xff0c;就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作&#xff0c;并避免了在网络上发…

计算机网络——网络层(1)

计算机网络——网络层(1&#xff09; 小程一言专栏链接: [link](http://t.csdnimg.cn/ZUTXU) 网络层&#xff1a;数据平面网络层概述核心功能协议总结 路由器工作原理路由器的工作步骤总结 网际协议IPv4主要特点不足IPv6主要特点现状 通用转发和SDN通用转发SDN&#xff08;软件…

《WebKit技术内幕》学习之十三(3):移动WebKit

3 其他机制 3.1 新渲染机制 为了移动领域更好的用户体验&#xff0c;渲染机制所做的改进主要是提升渲染性能来增加响应的速度&#xff0c;甚至不惜牺牲一些跟规范定义的行为不一致的地方。在这一小节中主要介绍三个方面的技术&#xff0c;其一是Tiled Backing Store&#x…

【LLM】FuseLLM:大模型融合trick-知识融合LLMs

前言 传统的模型融合方法分为集成的方法和权重合并的方法&#xff0c;这两种方法在以往的NLP的比赛中非常常见&#xff0c;是一种提分手段。然而&#xff0c;上述两种方法都需要预训练或者微调相应的模型。在大模型场景下&#xff0c;对每个源模型都进行初始化成本太高&#x…

是用原生js创建红包雨效果

需求: 创建红包雨 答案: // 红包雨代码 开始 // 添加遮罩层 const addOverlay () > {const overlay document.createElement(div);overlay.className overlay;overlay.style.position fixed;overlay.style.top 0;overlay.style.left 0;overlay.style.width 100%;o…

华为机考入门python3--(0)测试题1-句子平均重量

分类&#xff1a;字符串 知识点&#xff1a; 获取输入 input().strip().split(" ") 拼接列表 " ".join(list) 输出指定位数的浮点数 print("%.2f" % value) len() 函数对于很多内置的数据类型都适用&#xff0c;它返回对象的元素个数或长度。…

Qt6入门教程 11:父子对象关系

在上一篇中的纯手写部分&#xff0c;不管是创建菜单、工具栏还是状态栏&#xff0c;我们new完之后都未显式的调用delete进行销毁&#xff0c;这样难道不会有内存泄漏么&#xff1f; QMenuBar *menuBar new QMenuBar(this); QToolBar *toolBar new QToolBar(this); QStatusBa…

yolov8上使用gpu教程

yolov8上使用gpu教程 安装Cuda和Cudnnyolov8上使用gpu 安装Cuda和Cudnn 1.查看支持的cuda版本&#xff0c;并去官网下载。 nvidia-smi2.网址&#xff1a;https://developer.nvidia.com/cuda-toolkit-archive 3.安装细节 安装的前提基础是&#xff0c;有vs的C环境。我电脑有…