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                       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快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

应急响应红蓝工程师白帽子取证Linux和windows入侵排查还原攻击痕迹,追溯攻击者,以及各种木马和病毒以及恶意脚本文件排查和清除

应急响应红蓝工程师白帽子取证Linux入侵排查还原攻击痕迹,追溯攻击者,以及各种木马和病毒以及恶意脚本文件排查和清除。 一般服务器被入侵的迹象,包括但不局限于:由内向外发送大量数据包(DDOS肉鸡)、服务器资源被耗尽(挖矿程序)、不正常的端口连接(反向shell等)、服务…

#常见问题总结#在docker中跑前端vue项目

目录 前言一、no such file or directory, open...总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 记录在docker中跑前端项目过程中&#xff0c;我遇到的问题以及解决方法 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一…

github 推送报错 ssh: connect to host github.com port 22: Connection timed out 解决

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

伦敦金技术分析如何避免事后诸葛的尴尬

技术分析是我们分析伦敦金走势的常用工具&#xff0c;但技术分析长期因为“事后诸葛”的特点而备受一些投资者责难。那有没有什么办法可以避免这种事后诸葛的尴尬呢&#xff1f;下面我们就来讨论一下。 技术分析的事后诸葛是指&#xff0c;在当下用技术分析方法进行分析判断时经…

js - - - - - 如何给自己的网站添加中英文切换(多语言也可)

如何给自己的网站添加中英文切换 1. 需求描述2. 解决方案3. 方案实施3.1 简单实现&#xff08;第一版&#xff09; 3.2 样式微调&#xff08;第二版&#xff09;3.3 重载页面&#xff08;第三版&#xff09;3.4 自动翻译&#xff08;第四版&#xff09;3.5 限定适用范围&#x…

程序员如何保持身心健康

程序员要保持身心健康&#xff0c;可以注意以下几个方面&#xff1a; 饮食健康&#xff1a;保持均衡的饮食&#xff0c;多吃蔬菜水果&#xff0c;减少油腻和高热量食物的摄入。同时&#xff0c;适当饮水&#xff0c;避免因长时间坐着工作而导致的脱水。尽量不要吃街边摊、大排…

人工智能(AI)技术应用:解锁未来的无限可能

随着信息时代的来临&#xff0c;人工智能(AI)技术成为推动社会进步的重要力量。在医疗领域&#xff0c;人工智能技术已经开始发挥重要作用。通过大数据分析和机器学习&#xff0c;人工智能可以帮助医生更准确地诊断疾病、制定治疗方案&#xff0c;缩短治疗时间&#xff0c;提高…

JuiceSSH结合内网穿透实现移动端设备公网远程访问Linux虚拟机

文章目录 1. Linux安装cpolar2. 创建公网SSH连接地址3. JuiceSSH公网远程连接4. 固定连接SSH公网地址5. SSH固定地址连接测试 处于内网的虚拟机如何被外网访问呢?如何手机就能访问虚拟机呢? cpolarJuiceSSH 实现手机端远程连接Linux虚拟机(内网穿透,手机端连接Linux虚拟机) …

js计算皮尔逊相关系数

代码如下; let XGX {correlationCoefficient(pA, pB) {let covXY -pA * pBlet varX pA * (1-pA) let varY (1-pB)* pBlet res covXY / (Math.sqrt(varX*varY, 2))return res},correlation(x,y){x[0.3,50.2,99.5,199.3,299,398];y[0.1,50,99.9,200,300,400];// 计算均值con…

字典树-Python

字典树 字典树又叫前缀树、单词查找树&#xff0c;树形结构&#xff0c;是哈希树的变种。能够统计、排序和保存大量的字符串&#xff0c;经常被搜索引擎系统用于文本词频统计。优点是利用字符串的公共前缀来减少查询时间&#xff0c;最大程度减少无谓字符串的比较&#xff0c;…

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

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

【量化交易】股市舞者:小明的撮合交易之旅

马西森AES撮合交易系统 在繁华的都市中&#xff0c;小明&#xff0c;一个普通的青年&#xff0c;刚刚赚到了人生的第一桶金——20万。这笔意外的财富&#xff0c;点燃了他对股市的强烈兴趣。他开始如饥似渴地学习金融知识&#xff0c;钻研各种交易策略。 一天&#xff0c;小…

现货黄金做日内交易和波段交易有何差异?

在现货黄金投资中&#xff0c;日内交易和波段交易都是投资者常用的手段。但投资者其实搞不懂两者有何区别&#xff0c;有时甚至不清楚自己做的是日内交易还是波段交易&#xff0c;下面我们就来讨论一下这两种交易方法的异同。 两者的区别主要是在持仓的时间上。日内交易顾名思义…

Python算法题集_接雨水

本文为Python算法题集之一的代码示例 题目42&#xff1a;接雨水 说明&#xff1a;给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,…

【计算机网络】【练习题】【新加坡南洋理工大学】【Computer Control Network】

说明&#xff1a; 仅供学习使用。 一、题目描述 该题目描述一个网络中传播时延&#xff08;Transmission Delay&#xff09;的例子。题目如下&#xff1a; 二、问题解答&#xff08;个人&#xff09; 笔者第3问采用均值不等式求解。标答中采用求导数的方法求极值。似乎均值…

el-table 动态渲染多级表头;一级表头根据数据动态生成,二级表头固定

一、表格需求&#xff1a; 实现一个动态表头&#xff0c;一级表头&#xff0c;根据数据动态生成&#xff0c;二级表头固定&#xff0c;每列的数据不一样&#xff0c;难点在于数据的处理。做这种表头需要两组数据&#xff0c;一组数据是实现表头的&#xff0c;另一组数据是内容…

WinSCP如何使用公网TCP地址访问本地服务器

文章目录 1. 简介2. 软件下载安装&#xff1a;3. SSH链接服务器4. WinSCP使用公网TCP地址链接本地服务器5. WinSCP使用固定公网TCP地址访问服务器 1. 简介 ​ Winscp是一个支持SSH(Secure SHell)的可视化SCP(Secure Copy)文件传输软件&#xff0c;它的主要功能是在本地与远程计…

文件名翻译工具,文件名称翻译软件

无论是工作、学习还是生活&#xff0c;我们时常会遇到文件名称难以理解的情况。这时&#xff0c;一款优秀的文件名称翻译软件就显得尤为重要。今天&#xff0c;我要为大家介绍一个备受好评软件——文件批量改名高手&#xff0c;这款软件自带翻译功能&#xff0c;可以帮你轻松实…

分布式锁实现(mysql,以及redis)以及分布式的概念(续)redsync包使用

道生一&#xff0c;一生二&#xff0c;二生三&#xff0c;三生万物 这张尽量结合上一章进行使用&#xff1a;上一章 这章主要是讲如何通过redis实现分布式锁的 redis实现 这里我用redis去实现&#xff1a; 技术&#xff1a;golang&#xff0c;redis&#xff0c;数据结构 …

2024 年 10 款顶级的数据恢复软件榜单

2024年&#xff0c;随着人工智能、云计算等技术的不断发展&#xff0c;数据已经成为我们生活中不可或缺的一部分。然而&#xff0c;数据丢失的风险仍然存在。删除文件、病毒攻击、硬件损坏和其他情况都可能导致数据丢失。而数据恢复软件就成为解决这一问题的有效方案。 2024 年…