这篇文章主要介绍了 Docker 如何利用 Linux 的 Control Groups(cgroups)实现容器的资源隔离和管理。

最后通过简单 Demo 演示了如何使用 Go 和 cgroups 交互。


如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。

搜索公众号【探索云原生】即可订阅


1.Docker 是如何使用 Cgroups 的

我们知道 Docker 是通过 Cgroups 实现容器资源限制和监控的,那么具体是怎么用的呢?

演示

包含以下步骤:

  • 1)创建容器,指定内存限制
  • 2)查看 cgroup 情况
  • 3)停止容器
  • 4)再次查看 cgroup 情况

先启动一个容器:

[root@iZ2zefmrr626i66omb40ryZ memory]# docker run -itd -m 128m nginx
da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416

这里通过-m参数设置了内存限制为 128M。

该命令执行后 docker 会在 memory cgroup 上(也就是 /sys/fs/cgroup/memory 路径下)创建一个叫 docker 的子 cgroup,具体如下:

$ ls -l /sys/fs/cgroup/memory/docker/
-rw-r--r-- 1 root root 0 Jan 6 19:53 cgroup.clone_children
--w--w--w- 1 root root 0 Jan 6 19:53 cgroup.event_control
-rw-r--r-- 1 root root 0 Jan 6 19:53 cgroup.procs
# 可以发现这一长串ID和创建容器时打印的是一致的
drwxr-xr-x 2 root root 0 Jan 6 19:56 da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416
# 省略其他文件

内部除了 cgroup 相关的文件外,还有很多目录,使用容器 ID 作为目录名,其中每个目录即对应一个容器

其中,da82f9e...这个目录名称和容器 ID 一致,说明 docker 是为每个容器创建了一个子 cgroup 来单独限制。

查看一下里面的具体配置:

[root@iZ2zefmrr626i66omb40ryZ docker]# cd da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416/
[root@iZ2zefmrr626i66omb40ryZ da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416]# cat memory.limit_in_bytes
134217728

可以发现,memory.limit_in_bytes 中配置的值为 134217728,转换一下134217728/1024/1024=128M, 刚好就是我们指定的 128M。

然后我们停止该容器

docker stop da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416

再次查看 cgroup 情况

ls -l /sys/fs/cgroup/memory/docker/|grep da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416

发现目录已经被删除,说明容器对应的子 cgroup 也同步被回收。

把停止的容器 start 一下看看

docker start da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416

再次查看 cgroup 情况

[root@docker ~]# ls -l /sys/fs/cgroup/memory/docker/|grep da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416
drwxr-xr-x 2 root root 0 Jan 6 19:58 da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416

可以看到,同名目录又被创建出来了。

至此,演示完成。

结论:Docker 容器启动时创建容器 ID 同名子 group 以实现资源控制,容器停止时删除该子 cgroup。

Demo 中只演示了内存限制,其他资源也是类似的

小结

所以 docker 使用 cgroup 其实很简单,

  • 1)为每个容器创建一个子 cgroup
  • 2)根据 docker run 时提供的参数调整 cgroup 中的配置
  • 3)容器被删除时同步删除对应子 cgroup

2.Cgroups 相关操作命令

这里记录一下 cgroups 的一些常用操作命令。

hierarchy

创建

由于 Linux Cgroups 是基于内核中的 cgroup virtual filesystem 的,所以创建 hierarchy 其实就是将其挂载到指定目录。

语法为: mount -t cgroup -o subsystems name /cgroup/name

  • 其中 subsystems 表示需要挂载的 cgroups 子系统
  • /cgroup/name 表示挂载点(一般为具体目录)

这条命令同在内核中创建了一个 hierarchy 以及一个默认的 root cgroup。

例如:

$ mkdir cg1
$ mount -t cgroup -o cpuset cg1 ./cg1

比如以上命令就是挂载一个 cg1 的 hierarchy 到 ./cg1 目录,如果指定的 hierarchy 不存在则会新建。

hierarchy 创建的时候就会就会自动创建一个 cgroup 以作为 cgroup 树中的 root 节点。

删除

删除 hierarchy 则是卸载。

语法为:umount /cgroup/name

  • /cgroup/name 表示挂载点(一般为具体目录)

例如:

$ umount ./cg1

以上命令就是卸载 ./cg1 这个目录上挂载的 hierarchy,也就是前面挂载的 cg。

hierarchy 卸载后,相关的 cgroup 都会被删除。

不过 cg1 目录需要手动删除。

默认文件含义

hierarchy 挂载后会生成一些文件,具体如下:

为了避免干扰,未关联任何 subsystem

$ mkdir cg1
$ mount -t cgroup -o none,name=cg1 cg1 ./cg1
$ tree cg1
cg1
├── cgroup.clone_children
├── cgroup.procs
├── cgroup.sane_behavior
├── notify_on_release
├── release_agent
└── tasks

具体含义如下:

  • cgroup.clone_children:这个文件只对 cpuset subsystem 有影响,当该文件的内容为 1 时,新创建的 cgroup 将会继承父 cgroup

    的配置,即从父 cgroup 里面拷贝配置文件来初始化新

    cgroup,可以参考cgroup.clone_children
  • cgroup.procs:当前 cgroup 中的所有进程ID,系统不保证 ID 是顺序排列的,且 ID 有可能重复
  • cgroup.sane_behavior:这个文件只会存在于 root cgroup 下面,用于控制某些特性的开启和关闭。
    • 由于 cgroup 一直再发展,很多子系统有很多不同的特性,因此内核用CGRP_ROOT_SANE_BEHAVIOR来控制
  • notify_on_release:该文件的内容为 1 时,当 cgroup 退出时(不再包含任何进程和子 cgroup),将调用 release_agent 里面配置的命令。
    • 新 cgroup 被创建时将默认继承父 cgroup 的这项配置。
  • release_agent:里面包含了 cgroup 退出时将会执行的命令,系统调用该命令时会将相应 cgroup 的相对路径当作参数传进去。
    • 注意:这个文件只会存在于 root cgroup 下面,其他 cgroup 里面不会有这个文件。
    • 相当于配置一个回调用于清理资源。
  • tasks:当前 cgroup 中的所有线程 ID,系统不保证 ID 是顺序排列的

cgroup.procs 和 tasks 的区别见 cgroup 操作章节。

release_agent 演示

当一个 cgroup 里没有进程也没有子 cgroup 时,release_agent 将被调用来执行 cgroup 的清理工作。

具体操作流程:

  • 首先需要配置 notify_on_release 以开启该功能。
  • 然后将脚本内容写入到 release_agent 中去。
  • 最后 cgroup 退出时(不再包含任何进程和子 cgroup)就会执行 release_agent 中的命令。
#创建新的cgroup用于演示
dev@ubuntu:~/cgroup/demo$ sudo mkdir test
#先enable release_agent
dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 1 > ./test/notify_on_release' #然后创建一个脚本/home/dev/cgroup/release_demo.sh,
#一般情况下都会利用这个脚本执行一些cgroup的清理工作,但我们这里为了演示简单,仅仅只写了一条日志到指定文件
dev@ubuntu:~/cgroup/demo$ cat > /home/dev/cgroup/release_demo.sh << EOF
#!/bin/bash
echo \$0:\$1 >> /home/dev/release_demo.log
EOF #添加可执行权限
dev@ubuntu:~/cgroup/demo$ chmod +x ../release_demo.sh #将该脚本设置进文件release_agent
dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo /home/dev/cgroup/release_demo.sh > ./release_agent'
dev@ubuntu:~/cgroup/demo$ cat release_agent
/home/dev/cgroup/release_demo.sh #往test里面添加一个进程,然后再移除,这样就会触发release_demo.sh
dev@ubuntu:~/cgroup/demo$ echo $$
27597
dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 27597 > ./test/cgroup.procs'
dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 27597 > ./cgroup.procs' #从日志可以看出,release_agent被触发了,/test是cgroup的相对路径
dev@ubuntu:~/cgroup/demo$ cat /home/dev/release_demo.log
/home/dev/cgroup/release_demo.sh:/test

cgroup

创建

创建 cgroup 很简单,在父 cgroup 或者 hierarchy 目录下新建一个目录就可以了。

具体层级关系就和目录层级关系一样。

# 创建子cgroup cgroup-cpu
$ mkdir cgroup-cpu
$ cd cgroup-cpu
# 创建cgroup-cpu的子cgroup
$ mkdir cgroup-cpu-1

删除

删除也很简单,删除对应目录即可。

注意:是删除目录 rmdir,而不是递归删除目录下的所有文件。

如果有多层 cgroup 则需要先删除子 cgroup,否则会报错:

$ rmdir cgroup-cpu
# 如果cgroup中有进程正在本限制,也会出现这个错误,需要先停掉对应进程,或者把进程移动到另外的 cgroup 中(比如父cgroup)
rmdir: failed to remove 'cgroup-cpu': Device or resource busy

先删除子 cgroup 就可以了:

$ rmdir cg1
$ cd ../
$ rmdir cgroup-cpu

也可以借助 libcgroup 工具来创建或删除。

使用 libcgroup 工具前,请先安装 libcgroup 和 libcgroup-tools 数据包

redhat 系统安装:

$ yum install libcgroup
$ yum install libcgroup-tools

ubuntu 系统安装:

$ apt-get install cgroup-bin
# 如果提示cgroup-bin找不到,可以用 cgroup-tools 替换
$ apt-get install cgroup-tools

具体语法:

# controllers就是subsystem
# path可以用相对路径或者绝对路径
$ cgdelete controllers:path

例如:

$ cgcreate cpu:./mycgroup
$ cgdelete cpu:./mycgroup

添加进程

创建新的 cgroup 后,就可以往里面添加进程了。注意下面几点:

  • 在一颗 cgroup 树里面,一个进程必须要属于一个 cgroup

    • 所以不能凭空从一个 cgroup 里面删除一个进程,只能将一个进程从一个 cgroup 移到另一个 cgroup
  • 新创建的子进程将会自动加入父进程所在的 cgroup。
    • 这也就是 tasks 和 cgroup.proc 的区别。
  • 从一个 cgroup 移动一个进程到另一个 cgroup 时,只要有目的 cgroup 的写入权限就可以了,系统不会检查源 cgroup 里的权限。
  • 用户只能操作属于自己的进程,不能操作其他用户的进程,root 账号除外。
#--------------------------第一个shell窗口----------------------
#创建一个新的cgroup
dev@ubuntu:~/cgroup/demo$ sudo mkdir test
dev@ubuntu:~/cgroup/demo$ cd test #将当前bash加入到上面新创建的cgroup中
dev@ubuntu:~/cgroup/demo/test$ echo $$
1421
dev@ubuntu:~/cgroup/demo/test$ sudo sh -c 'echo 1421 > cgroup.procs'
#注意:一次只能往这个文件中写一个进程ID,如果需要写多个的话,需要多次调用这个命令 #--------------------------第二个shell窗口----------------------
#重新打开一个shell窗口,避免第一个shell里面运行的命令影响输出结果
#这时可以看到cgroup.procs里面包含了上面的第一个shell进程
dev@ubuntu:~/cgroup/demo/test$ cat cgroup.procs
1421 #--------------------------第一个shell窗口----------------------
#回到第一个窗口,随便运行一个命令,比如 top
dev@ubuntu:~/cgroup/demo/test$ top
#这里省略输出内容 #--------------------------第二个shell窗口----------------------
#这时再在第二个窗口查看,发现top进程自动加入了它的父进程(1421)所在的cgroup
dev@ubuntu:~/cgroup/demo/test$ cat cgroup.procs
1421
16515
dev@ubuntu:~/cgroup/demo/test$ ps -ef|grep top
dev 16515 1421 0 04:02 pts/0 00:00:00 top
dev@ubuntu:~/cgroup/demo/test$ #在一颗cgroup树里面,一个进程必须要属于一个cgroup,
#所以我们不能凭空从一个cgroup里面删除一个进程,只能将一个进程从一个cgroup移到另一个cgroup,
#这里我们将1421移动到root cgroup
dev@ubuntu:~/cgroup/demo/test$ sudo sh -c 'echo 1421 > ../cgroup.procs'
dev@ubuntu:~/cgroup/demo/test$ cat cgroup.procs
16515
#移动1421到另一个cgroup之后,它的子进程不会随着移动 #--------------------------第一个shell窗口----------------------
##回到第一个shell窗口,进行清理工作
#先用ctrl+c退出top命令
dev@ubuntu:~/cgroup/demo/test$ cd ..
#然后删除创建的cgroup
dev@ubuntu:~/cgroup/demo$ sudo rmdir test

cgroup.procs vs tasks

#创建两个新的cgroup用于演示
dev@ubuntu:~/cgroup/demo$ sudo mkdir c1 c2 #为了便于操作,先给root账号设置一个密码,然后切换到root账号
dev@ubuntu:~/cgroup/demo$ sudo passwd root
dev@ubuntu:~/cgroup/demo$ su root
root@ubuntu:/home/dev/cgroup/demo# #系统中找一个有多个线程的进程
root@ubuntu:/home/dev/cgroup/demo# ps -efL|grep /lib/systemd/systemd-timesyncd
systemd+ 610 1 610 0 2 01:52 ? 00:00:00 /lib/systemd/systemd-timesyncd
systemd+ 610 1 616 0 2 01:52 ? 00:00:00 /lib/systemd/systemd-timesyncd
#进程610有两个线程,分别是610和616 #将616加入c1/cgroup.procs
root@ubuntu:/home/dev/cgroup/demo# echo 616 > c1/cgroup.procs
#由于cgroup.procs存放的是进程ID,所以这里看到的是616所属的进程ID(610)
root@ubuntu:/home/dev/cgroup/demo# cat c1/cgroup.procs
610
#从tasks中的内容可以看出,虽然只往cgroup.procs中加了线程616,
#但系统已经将这个线程所属的进程的所有线程都加入到了tasks中,
#说明现在整个进程的所有线程已经处于c1中了
root@ubuntu:/home/dev/cgroup/demo# cat c1/tasks
610
616 #将616加入c2/tasks中
root@ubuntu:/home/dev/cgroup/demo# echo 616 > c2/tasks #这时我们看到虽然在c1/cgroup.procs和c2/cgroup.procs里面都有610,
#但c1/tasks和c2/tasks中包含了不同的线程,说明这个进程的两个线程分别属于不同的cgroup
root@ubuntu:/home/dev/cgroup/demo# cat c1/cgroup.procs
610
root@ubuntu:/home/dev/cgroup/demo# cat c1/tasks
610
root@ubuntu:/home/dev/cgroup/demo# cat c2/cgroup.procs
610
root@ubuntu:/home/dev/cgroup/demo# cat c2/tasks
616
#通过tasks,我们可以实现线程级别的管理,但通常情况下不会这么用,
#并且在cgroup V2以后,将不再支持该功能,只能以进程为单位来配置cgroup #清理
root@ubuntu:/home/dev/cgroup/demo# echo 610 > ./cgroup.procs
root@ubuntu:/home/dev/cgroup/demo# rmdir c1
root@ubuntu:/home/dev/cgroup/demo# rmdir c2
root@ubuntu:/home/dev/cgroup/demo# exit
exit

结论:将线程 ID 加到 cgroup1 的 cgroup.procs 时,会把线程对应进程 ID 加入 cgroup.procs 且还会把当前进程下的全部线程 ID 加入到

tasks 中。

这里看起来,进程和线程好像效果是一样的。

区别来了,如果此时把某个线程 ID 移动到另外的 cgroup2 的 tasks 中,会自动把 线程 ID 对应的进程 ID 加入到 cgroup2 的

cgroup.procs 中,且只把对应线程加入 tasks 中。

此时 cgroup1 和 cgroup2 的 cgroup.procs 都包含了同一个进程 ID,但是二者的 tasks 中却包含了不同的线程 ID。

这样就实现了线程粒度的控制。但通常情况下不会这么用,并且在 cgroup V2 以后,将不再支持该功能,只能以进程为单位来配置

cgroup。

3.如何使用 Go 和 Cgroups 交互

其实挺简单的,就是用 Go 翻译了一遍上面的命令。

后续则是按照这个流程实现自己的 docker。

具体代码如下:

// cGroups cGroups初体验
func cGroups() {
// /proc/self/exe是一个符号链接,代表当前程序的绝对路径
if os.Args[0] == "/proc/self/exe" {
// 第一个参数就是当前执行的文件名,所以只有fork出的容器进程才会进入该分支
fmt.Printf("容器进程内部 PID %d\n", syscall.Getpid())
// 需要先在宿主机上安装 stress 比如 apt-get install stress
cmd := exec.Command("sh", "-c", `stress --vm-bytes 200m --vm-keep -m 1`)
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
} else {
// 主进程会走这个分支
cmd := exec.Command("/proc/self/exe")
cmd.SysProcAttr = &syscall.SysProcAttr{Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWNS | syscall.CLONE_NEWPID}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
fmt.Println(err)
os.Exit(1)
}
// 得到 fork 出来的进程在外部namespace 的 pid
fmt.Println("fork 进程 PID:", cmd.Process.Pid)
// 在默认的 memory cgroup 下创建子目录,即创建一个子 cgroup
err := os.Mkdir(filepath.Join(cgroupMemoryHierarchyMount, "testmemorylimit"), 0755)
if err != nil {
fmt.Println(err)
}
// 将容器加入到这个 cgroup 中,即将进程PID加入到cgroup下的 cgroup.procs 文件中
err = ioutil.WriteFile(filepath.Join(cgroupMemoryHierarchyMount, "testmemorylimit", "cgroup.procs"),
[]byte(strconv.Itoa(cmd.Process.Pid)), 0644)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// 限制进程的内存使用,往 memory.limit_in_bytes 文件中写入数据
err = ioutil.WriteFile(filepath.Join(cgroupMemoryHierarchyMount, "testmemorylimit", "memory.limit_in_bytes"),
[]byte("100m"), 0644)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cmd.Process.Wait()
}
}

首先是一个 if 判断,区分主进程和子进程,分别执行不同逻辑。

  • 主进程:fork 出子进程,并创建 cgroup,然后将子进程加入该 cgrouop
  • 子进程:执行 stress 命令,以消耗内存,便于查看 memory cgroup 的效果

运行并测试:

lixd  ~/projects/docker/mydocker main $ go build main.go
lixd  ~/projects/docker/mydocker main $ sudo ./main
fork 进程 PID: 21827
当前进程 pid 1
stress: info: [7] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

根据输出可以知道,我们 fork 出的进程,pid 为 21827。

通过pstree -pl查看进程关系:

$pstree -pl
init(1)─┬─init(8)───init(9)───fsnotifier-wsl(10)
├─init(12)───init(13)─┬─exe(20618)─┬─sh(20623)───stress(20624)───stress(20625)
│ │ ├─{exe}(20619)
│ │ ├─{exe}(20620)
│ │ ├─{exe}(20621)
│ │ └─{exe}(20622)
└─zsh(14)───sudo(21821)───main(21822)─┬─exe(21827)─┬─sh(21832)───stress(21833)───stress(21834)

可以看到 21827 进程 最终启动了一个 21834 的 stress 进程。

top查看以下内存占用:

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
21834 root 20 0 208664 101564 272 D 35.2 1.3 0:14.38 stress

可以看到 RES 101564,也就是刚好 100M,说明我们的 cgroup 是有效果的。

4. 小结

本文主要介绍了

  • 1)Docker 是如何使用 cgroups 的;
  • 2) hierarchy 和 cgroup 相关的操作,如创建删除等;
  • 3)最后则是简单演示了如何使用 Go 和 cgroups 进行交互。

至此,cgroups 的相关内容就告一段落了,加上本文一共包括 3 篇文章:

初探 Linux Cgroups:资源控制的奇妙世界

深入剖析 Linux Cgroups 子系统:资源精细管理

包括以下内容:

  • 1)cgroups 怎么实现资源控制的
  • 2)相关 subsystem 演示
  • 3)docker 怎么使用 cgroups 的
  • 4)go 怎么操作 cgroups

后续可以使用 go 实现 docker 的时候,资源控制就会使用 go 和 cgroups 交互来实现。


如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。

搜索公众号【探索云原生】即可订阅


5.参考

cgroups(7) — Linux manual page

Linux Cgroup 系列(02):创建并管理 cgroup

cgroup 源码分析 6——cgroup 中默认控制文件的内核实现分析

Docker 与 Linux Cgroups:资源隔离的魔法之旅的更多相关文章

  1. Docker之Linux Cgroups

    Linux Cgroups介绍 上面是构建Linux容器的namespace技术,它帮进程隔离出自己单独的空间,但Docker又是怎么限制每个空间的大小,保证他们不会互相争抢呢?那么就要用到Linux ...

  2. 【转载】Linux cgroup资源隔离各个击破之 - io隔离

    Linux Cgroup blkio子系统的用法.   blkio子系统支持的两种IO隔离策略 .1. (Completely Fair Queuing 完全公平队列)cfq io调度策略,支持按权重 ...

  3. 【转载】Linux cgroup资源隔离各个击破之 - cpu隔离1

    Linux cgroup 有两个子系统支持CPU隔离.一个是cpu子系统,另一个是cpuset子系统. cpu子系统根据进程设置的调度属性,选择对应的CPU资源调度方法 .1. 完全公平调度 Comp ...

  4. 理解Docker(4):Docker 容器使用 cgroups 限制资源使用

    本系列文章将介绍Docker的有关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 ...

  5. Docker 基础技术之 Linux cgroups 详解

    PS:欢迎大家关注我的公众号:aCloudDeveloper,专注技术分享,努力打造干货分享平台,二维码在文末可以扫,谢谢大家. 推荐大家到公众号阅读,那里阅读体验更好,也沉淀了很多篇干货. 前面两篇 ...

  6. Docker背后的内核知识——cgroups资源限制(转)

    时间 2015-04-20 21:10:00 InfoQ 原文  http://www.infoq.com/cn/articles/docker-kernel-knowledge-cgroups-re ...

  7. 5、Docker 核心原理-资源隔离和限制

    Docker 资源隔离 Docker 是利用linux的LXC技术,内核的Kernel namespace Namespace: PID - 通过PID的namespace隔离,可以嵌套 NET - ...

  8. Docker 容器资源隔离 namespace(十)

    目录 一.简介 Linux Namespace的6大类型 二.Mount Namespace 三.IPC Namespace 四.Network Namespace 五.UTS Namespace 六 ...

  9. 资源管理与调度系统-YARN资源隔离及以YARN为核心的生态系统

    资源管理与调度系统-YARN资源隔离及以YARN为核心的生态系统 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是资源隔离 资源隔离是指为不同任务提供可独立使用的计算资源以 ...

  10. Hadoop YARN资源隔离技术

    YARN对内存资源和CPU资源采用了不同的资源隔离方案.对于内存资源,它是一种限制性资源,它的量的大小直接决定应用程序的死活,因为应用程序到达内存限制,会发生OOM,就会被杀死.CPU资源一般用Cgr ...

随机推荐

  1. VSCODE中无法搜索插件的解决办法

    当前我的使用环境是虚拟机, 如果无法搜索插件但是网络连接是正确的极有可能是代理设置的问题. 解决办法如下: ctrl+, 打开设置 这里填写正确的代理设置.

  2. Java 删除PDF页面 (免费工具分享)

    对PDF页面的增删通常需要借助专门的工具,而这些工具一般需要付费才能使用.那么我们可以通过Java代码免费实现这一功能吗?答案是肯定的.这篇文章就教大家如何使用一个免费的国产Java库来删除PDF中的 ...

  3. Jenkins从Ubuntu迁移至AlmaLinux问题及相关解决记录

    相关背景 之前在Ubuntu平台上搭建了Jenkins(在Ubuntu机器上使用war包安装Jenkins),现在由于一些需求,需要将系统迁移到AlmaLinux平台.由于AlmaLinux属于Cen ...

  4. uni-app学习笔记——路由与页面跳转

    小颖最近在学习小程序,怕自己前看后忘,毕竟还没开始进入项目实践中,就自己瞎倒腾嘻嘻,今天来看下  uni-app  的路由与页面跳转,小颖就简单列举下它们的用法,具体的大家可以看官网哦!啦啦啦啦啦  ...

  5. 【uniapp】【外包杯】学习笔记day07 | 微信小程序轮播图、分类导航、楼层图的开发与实现

    1.创建home分支 2.配置网络请求 由于平台的限制,现需要建立uni-app中使用第三方包请求网络数据请求 在 uni-app 项目中使用 @escook/request-miniprogram  ...

  6. 【uniapp】学习笔记day02 | uniapp搭建

    起因:需要做一个小程序,家人们谁懂啊,老师我真的不会做,由于懒得看视频学习,于是只能看博客学习了. uniapp 好处: 1.不用关心适配问题 2.可以发布到各大平台的小程序 3.上手容易,使用vue ...

  7. Go语言实现GoF设计模式:适配器模式

    本文分享自华为云社区<[Go实现]实践GoF的23种设计模式:适配器模式>,作者:元闰子. 简介 适配器模式(Adapter)是最常用的结构型模式之一,在现实生活中,适配器模式也是处处可见 ...

  8. springBoot——整合mybatis

    spring整合mybatis springBoot整合mybaits 配置文件 spring: datasource: url: jdbc:mysql://localhost:3306/test d ...

  9. The second day learning summary

    1.什么是接口测试? 接口测试是测试系统组件间接口的一种测试.接口测试主要用于外部系统与系统之间以及内部各个子系统之间的交互点,定义特定的交互点,然后通过这些交互点来,通过一些特殊的规则也就是协议,来 ...

  10. 高斯朴素贝叶斯(Gaussian Naive Bayes)原理与实现——垃圾邮件识别实战

    朴素贝叶斯(Naive Bayes): 根据贝叶斯定理和朴素假设提出的朴素贝叶斯模型. 贝叶斯定理: 朴素假设(特征条件独立性假设): 代入可知朴素贝叶斯模型计算公式: 因为朴素贝叶斯是用来分类任务, ...